referrerpolicy=no-referrer-when-downgrade

pallet_revive/
benchmarking.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Benchmarks for the revive pallet.
19
20#![cfg(feature = "runtime-benchmarks")]
21use crate::{
22	Pallet as Contracts,
23	access_list::{AccessEntry, AccessList, MAX_ACCESS_LIST_ENTRIES, StorageOp, Warmth},
24	call_builder::{CallSetup, Contract, VmBinaryModule, caller_funding, default_deposit_limit},
25	evm::{
26		TransactionLegacyUnsigned, TransactionSigned, TransactionUnsigned,
27		block_hash::EthereumBlockBuilder, block_storage,
28	},
29	exec::{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 origin_is_root() {
870		let input_bytes =
871			ISystem::ISystemCalls::originIsRoot(ISystem::originIsRootCall {}).abi_encode();
872
873		let mut setup = CallSetup::<T>::default();
874		setup.set_origin(ExecOrigin::Root);
875		let (mut ext, _) = setup.ext();
876
877		let result;
878		#[block]
879		{
880			result = run_builtin_precompile(
881				&mut ext,
882				H160(BenchmarkSystem::<T>::MATCHER.base_address()).as_fixed_bytes(),
883				input_bytes,
884			);
885		}
886		let raw_data = result.unwrap().data;
887		let is_root = Bool::abi_decode(&raw_data).expect("decoding failed");
888		assert!(is_root);
889	}
890
891	#[benchmark(pov_mode = Measured)]
892	fn seal_address() {
893		let len = H160::len_bytes();
894		build_runtime!(runtime, memory: [vec![0u8; len as _], ]);
895
896		let result;
897		#[block]
898		{
899			result = runtime.bench_address(memory.as_mut_slice(), 0);
900		}
901		assert_ok!(result);
902		assert_eq!(<H160 as Decode>::decode(&mut &memory[..]).unwrap(), runtime.ext().address());
903	}
904
905	#[benchmark(pov_mode = Measured)]
906	fn weight_left() {
907		let input_bytes =
908			ISystem::ISystemCalls::weightLeft(ISystem::weightLeftCall {}).abi_encode();
909
910		let mut call_setup = CallSetup::<T>::default();
911		let (mut ext, _) = call_setup.ext();
912
913		let weight_left_before = ext.frame_meter().weight_left().unwrap();
914		let result;
915		#[block]
916		{
917			result = run_builtin_precompile(
918				&mut ext,
919				H160(BenchmarkSystem::<T>::MATCHER.base_address()).as_fixed_bytes(),
920				input_bytes,
921			);
922		}
923		let weight_left_after = ext.frame_meter().weight_left().unwrap();
924		assert_ne!(weight_left_after.ref_time(), 0);
925		assert!(weight_left_before.ref_time() > weight_left_after.ref_time());
926
927		let raw_data = result.unwrap().data;
928		type MyTy = (Uint<64>, Uint<64>);
929		let foo = MyTy::abi_decode(&raw_data[..]).unwrap();
930		assert_eq!(weight_left_after.ref_time(), foo.0);
931	}
932
933	#[benchmark(pov_mode = Measured)]
934	fn seal_ref_time_left() {
935		build_runtime!(runtime, memory: [vec![], ]);
936
937		let result;
938		#[block]
939		{
940			result = runtime.bench_ref_time_left(memory.as_mut_slice());
941		}
942		assert_eq!(result.unwrap(), runtime.ext().gas_left());
943	}
944
945	#[benchmark(pov_mode = Measured)]
946	fn seal_balance() {
947		build_runtime!(runtime, contract, memory: [[0u8;32], ]);
948		contract.set_balance(BalanceWithDust::new_unchecked::<T>(
949			Pallet::<T>::min_balance() * 2u32.into(),
950			42u32,
951		));
952
953		let result;
954		#[block]
955		{
956			result = runtime.bench_balance(memory.as_mut_slice(), 0);
957		}
958		assert_ok!(result);
959		assert_eq!(
960			U256::from_little_endian(&memory[..]),
961			Pallet::<T>::convert_native_to_evm(BalanceWithDust::new_unchecked::<T>(
962				Pallet::<T>::min_balance(),
963				42
964			))
965		);
966	}
967
968	#[benchmark(pov_mode = Measured)]
969	fn seal_balance_of() {
970		let len = <sp_core::U256 as MaxEncodedLen>::max_encoded_len();
971		let account = account::<T::AccountId>("target", 0, 0);
972		<T as Config>::AddressMapper::map_no_deposit_unchecked(&account).unwrap();
973
974		let address = T::AddressMapper::to_address(&account);
975		let balance = Pallet::<T>::min_balance() * 2u32.into();
976		T::Currency::set_balance(&account, balance);
977		AccountInfoOf::<T>::insert(&address, AccountInfo { dust: 42, ..Default::default() });
978
979		build_runtime!(runtime, memory: [vec![0u8; len], address.0, ]);
980
981		let result;
982		#[block]
983		{
984			result = runtime.bench_balance_of(memory.as_mut_slice(), len as u32, 0);
985		}
986
987		assert_ok!(result);
988		assert_eq!(
989			U256::from_little_endian(&memory[..len]),
990			Pallet::<T>::convert_native_to_evm(BalanceWithDust::new_unchecked::<T>(
991				Pallet::<T>::min_balance(),
992				42
993			))
994		);
995	}
996
997	#[benchmark(pov_mode = Measured)]
998	fn seal_get_immutable_data(n: Linear<1, { limits::IMMUTABLE_BYTES }>) {
999		let len = n as usize;
1000		let immutable_data = vec![1u8; len];
1001
1002		build_runtime!(runtime, contract, memory: [(len as u32).encode(), vec![0u8; len],]);
1003
1004		<ImmutableDataOf<T>>::insert::<_, BoundedVec<_, _>>(
1005			contract.address,
1006			immutable_data.clone().try_into().unwrap(),
1007		);
1008
1009		let result;
1010		#[block]
1011		{
1012			result = runtime.bench_get_immutable_data(memory.as_mut_slice(), 4, 0 as u32);
1013		}
1014
1015		assert_ok!(result);
1016		assert_eq!(&memory[0..4], (len as u32).encode());
1017		assert_eq!(&memory[4..len + 4], &immutable_data);
1018	}
1019
1020	#[benchmark(pov_mode = Measured)]
1021	fn seal_set_immutable_data(n: Linear<1, { limits::IMMUTABLE_BYTES }>) {
1022		let len = n as usize;
1023		let mut memory = vec![1u8; len];
1024		let mut setup = CallSetup::<T>::default();
1025		let input = setup.data();
1026		let (mut ext, _) = setup.ext();
1027		ext.override_export(crate::exec::ExportedFunction::Constructor);
1028
1029		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, input);
1030
1031		let result;
1032		#[block]
1033		{
1034			result = runtime.bench_set_immutable_data(memory.as_mut_slice(), 0, n);
1035		}
1036
1037		assert_ok!(result);
1038		assert_eq!(&memory[..], &<ImmutableDataOf<T>>::get(setup.contract().address).unwrap()[..]);
1039	}
1040
1041	#[benchmark(pov_mode = Measured)]
1042	fn seal_value_transferred() {
1043		build_runtime!(runtime, memory: [[0u8;32], ]);
1044		let result;
1045		#[block]
1046		{
1047			result = runtime.bench_value_transferred(memory.as_mut_slice(), 0);
1048		}
1049		assert_ok!(result);
1050		assert_eq!(U256::from_little_endian(&memory[..]), runtime.ext().value_transferred());
1051	}
1052
1053	#[benchmark(pov_mode = Measured)]
1054	fn minimum_balance() {
1055		let input_bytes =
1056			ISystem::ISystemCalls::minimumBalance(ISystem::minimumBalanceCall {}).abi_encode();
1057
1058		let mut call_setup = CallSetup::<T>::default();
1059		let (mut ext, _) = call_setup.ext();
1060
1061		let result;
1062		#[block]
1063		{
1064			result = run_builtin_precompile(
1065				&mut ext,
1066				H160(BenchmarkSystem::<T>::MATCHER.base_address()).as_fixed_bytes(),
1067				input_bytes,
1068			);
1069		}
1070		let min: U256 = crate::Pallet::<T>::convert_native_to_evm(T::Currency::minimum_balance());
1071		let min =
1072			crate::precompiles::alloy::primitives::aliases::U256::abi_decode(&min.to_big_endian())
1073				.unwrap();
1074
1075		let raw_data = result.unwrap().data;
1076		let returned_min =
1077			crate::precompiles::alloy::primitives::aliases::U256::abi_decode(&raw_data)
1078				.expect("decoding failed");
1079		assert_eq!(returned_min, min);
1080	}
1081
1082	#[benchmark(pov_mode = Measured)]
1083	fn seal_return_data_size() {
1084		let mut setup = CallSetup::<T>::default();
1085		let (mut ext, _) = setup.ext();
1086		let mut runtime = pvm::Runtime::new(&mut ext, vec![]);
1087		let mut memory = memory!(vec![],);
1088		*runtime.ext().last_frame_output_mut() =
1089			ExecReturnValue { data: vec![42; 256], ..Default::default() };
1090		let result;
1091		#[block]
1092		{
1093			result = runtime.bench_return_data_size(memory.as_mut_slice());
1094		}
1095		assert_eq!(result.unwrap(), 256);
1096	}
1097
1098	#[benchmark(pov_mode = Measured)]
1099	fn seal_call_data_size() {
1100		let mut setup = CallSetup::<T>::default();
1101		let (mut ext, _) = setup.ext();
1102		let mut runtime = pvm::Runtime::new(&mut ext, vec![42u8; 128 as usize]);
1103		let mut memory = memory!(vec![0u8; 4],);
1104		let result;
1105		#[block]
1106		{
1107			result = runtime.bench_call_data_size(memory.as_mut_slice());
1108		}
1109		assert_eq!(result.unwrap(), 128);
1110	}
1111
1112	#[benchmark(pov_mode = Measured)]
1113	fn seal_gas_limit() {
1114		build_runtime!(runtime, memory: []);
1115		let result;
1116		#[block]
1117		{
1118			result = runtime.bench_gas_limit(&mut memory);
1119		}
1120		assert_eq!(U256::from(result.unwrap()), <Pallet<T>>::evm_block_gas_limit());
1121	}
1122
1123	#[benchmark(pov_mode = Measured)]
1124	fn seal_gas_price() {
1125		build_runtime!(runtime, memory: []);
1126		let result;
1127		#[block]
1128		{
1129			result = runtime.bench_gas_price(memory.as_mut_slice());
1130		}
1131		assert_eq!(U256::from(result.unwrap()), <Pallet<T>>::evm_base_fee());
1132	}
1133
1134	#[benchmark(pov_mode = Measured)]
1135	fn seal_base_fee() {
1136		build_runtime!(runtime, memory: [[1u8;32], ]);
1137		let result;
1138		#[block]
1139		{
1140			result = runtime.bench_base_fee(memory.as_mut_slice(), 0);
1141		}
1142		assert_ok!(result);
1143		assert_eq!(U256::from_little_endian(&memory[..]), <crate::Pallet<T>>::evm_base_fee());
1144	}
1145
1146	#[benchmark(pov_mode = Measured)]
1147	fn seal_block_number() {
1148		build_runtime!(runtime, memory: [[0u8;32], ]);
1149		let result;
1150		#[block]
1151		{
1152			result = runtime.bench_block_number(memory.as_mut_slice(), 0);
1153		}
1154		assert_ok!(result);
1155		assert_eq!(U256::from_little_endian(&memory[..]), runtime.ext().block_number());
1156	}
1157
1158	#[benchmark(pov_mode = Measured)]
1159	fn seal_block_author() {
1160		build_runtime!(runtime, memory: [[123u8; 20], ]);
1161
1162		// The pre-runtime digest log is unbounded; usually around 3 items but it can vary.
1163		// To get safe benchmark results despite that, populate it with a bunch of random logs to
1164		// ensure iteration over many items (we just overestimate the cost of the API).
1165		for i in 0..16 {
1166			frame_system::Pallet::<T>::deposit_log(DigestItem::PreRuntime(
1167				[i, i, i, i],
1168				vec![i; 128],
1169			));
1170			frame_system::Pallet::<T>::deposit_log(DigestItem::Consensus(
1171				[i, i, i, i],
1172				vec![i; 128],
1173			));
1174			frame_system::Pallet::<T>::deposit_log(DigestItem::Seal([i, i, i, i], vec![i; 128]));
1175			frame_system::Pallet::<T>::deposit_log(DigestItem::Other(vec![i; 128]));
1176		}
1177
1178		// The content of the pre-runtime digest log depends on the configured consensus.
1179		// However, mismatching logs are simply ignored. Thus we construct fixtures which will
1180		// let the API to return a value in both BABE and AURA consensus.
1181
1182		// Construct a `Digest` log fixture returning some value in BABE
1183		let primary_pre_digest = vec![0; <PrimaryPreDigest as MaxEncodedLen>::max_encoded_len()];
1184		let pre_digest =
1185			PreDigest::Primary(PrimaryPreDigest::decode(&mut &primary_pre_digest[..]).unwrap());
1186		frame_system::Pallet::<T>::deposit_log(DigestItem::PreRuntime(
1187			BABE_ENGINE_ID,
1188			pre_digest.encode(),
1189		));
1190		frame_system::Pallet::<T>::deposit_log(DigestItem::Seal(
1191			BABE_ENGINE_ID,
1192			pre_digest.encode(),
1193		));
1194
1195		// Construct a `Digest` log fixture returning some value in AURA
1196		let slot = Slot::default();
1197		frame_system::Pallet::<T>::deposit_log(DigestItem::PreRuntime(
1198			AURA_ENGINE_ID,
1199			slot.encode(),
1200		));
1201		frame_system::Pallet::<T>::deposit_log(DigestItem::Seal(AURA_ENGINE_ID, slot.encode()));
1202
1203		let result;
1204		#[block]
1205		{
1206			result = runtime.bench_block_author(memory.as_mut_slice(), 0);
1207		}
1208		assert_ok!(result);
1209
1210		let block_author = runtime.ext().block_author();
1211		assert_eq!(&memory[..], block_author.as_bytes());
1212	}
1213
1214	#[benchmark(pov_mode = Measured)]
1215	fn seal_block_hash() {
1216		let mut memory = vec![0u8; 64];
1217		let mut setup = CallSetup::<T>::default();
1218		let input = setup.data();
1219		let (mut ext, _) = setup.ext();
1220		ext.set_block_number(BlockNumberFor::<T>::from(1u32));
1221
1222		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, input);
1223
1224		let block_hash = H256::from([1; 32]);
1225
1226		// Store block hash in pallet-revive BlockHash mapping
1227		crate::BlockHash::<T>::insert(crate::BlockNumberFor::<T>::from(0u32), block_hash);
1228
1229		let result;
1230		#[block]
1231		{
1232			result = runtime.bench_block_hash(memory.as_mut_slice(), 32, 0);
1233		}
1234		assert_ok!(result);
1235		assert_eq!(&memory[..32], &block_hash.0);
1236	}
1237
1238	#[benchmark(pov_mode = Measured)]
1239	fn seal_now() {
1240		build_runtime!(runtime, memory: [[0u8;32], ]);
1241		let result;
1242		#[block]
1243		{
1244			result = runtime.bench_now(memory.as_mut_slice(), 0);
1245		}
1246		assert_ok!(result);
1247		assert_eq!(U256::from_little_endian(&memory[..]), runtime.ext().now());
1248	}
1249
1250	#[benchmark(pov_mode = Measured)]
1251	fn seal_copy_to_contract(n: Linear<0, { limits::code::BLOB_BYTES - 4 }>) {
1252		let mut setup = CallSetup::<T>::default();
1253		let (mut ext, _) = setup.ext();
1254		let mut runtime = pvm::Runtime::new(&mut ext, vec![]);
1255		let mut memory = memory!(n.encode(), vec![0u8; n as usize],);
1256		let result;
1257		#[block]
1258		{
1259			result = runtime.write_sandbox_output(
1260				memory.as_mut_slice(),
1261				4,
1262				0,
1263				&vec![42u8; n as usize],
1264				false,
1265				|_| None,
1266			);
1267		}
1268		assert_ok!(result);
1269		assert_eq!(&memory[..4], &n.encode());
1270		assert_eq!(&memory[4..], &vec![42u8; n as usize]);
1271	}
1272
1273	#[benchmark(pov_mode = Measured)]
1274	fn seal_call_data_load() {
1275		let mut setup = CallSetup::<T>::default();
1276		let (mut ext, _) = setup.ext();
1277		let mut runtime = pvm::Runtime::new(&mut ext, vec![42u8; 32]);
1278		let mut memory = memory!(vec![0u8; 32],);
1279		let result;
1280		#[block]
1281		{
1282			result = runtime.bench_call_data_load(memory.as_mut_slice(), 0, 0);
1283		}
1284		assert_ok!(result);
1285		assert_eq!(&memory[..], &vec![42u8; 32]);
1286	}
1287
1288	#[benchmark(pov_mode = Measured)]
1289	fn seal_call_data_copy(n: Linear<0, { limits::code::BLOB_BYTES }>) {
1290		let mut setup = CallSetup::<T>::default();
1291		let (mut ext, _) = setup.ext();
1292		let mut runtime = pvm::Runtime::new(&mut ext, vec![42u8; n as usize]);
1293		let mut memory = memory!(vec![0u8; n as usize],);
1294		let result;
1295		#[block]
1296		{
1297			result = runtime.bench_call_data_copy(memory.as_mut_slice(), 0, n, 0);
1298		}
1299		assert_ok!(result);
1300		assert_eq!(&memory[..], &vec![42u8; n as usize]);
1301	}
1302
1303	#[benchmark(pov_mode = Measured)]
1304	fn seal_return(n: Linear<0, { limits::CALLDATA_BYTES }>) {
1305		build_runtime!(runtime, memory: [n.to_le_bytes(), vec![42u8; n as usize], ]);
1306
1307		let result;
1308		#[block]
1309		{
1310			result = runtime.bench_seal_return(memory.as_mut_slice(), 0, 0, n);
1311		}
1312
1313		assert!(matches!(
1314			result,
1315			Err(crate::vm::pvm::TrapReason::Return(crate::vm::pvm::ReturnData { .. }))
1316		));
1317	}
1318
1319	/// Benchmark the ocst of terminating a contract.
1320	///
1321	/// `r`: whether the old code will be removed as a result of this operation. (1: yes, 0: no)
1322	#[benchmark(pov_mode = Measured)]
1323	fn seal_terminate(r: Linear<0, 1>) -> Result<(), BenchmarkError> {
1324		let delete_code = r == 1;
1325		let beneficiary = account::<T::AccountId>("beneficiary", 0, 0);
1326
1327		build_runtime!(runtime, instance, memory: [beneficiary.encode(),]);
1328		let code_hash = instance.info()?.code_hash;
1329
1330		// Increment the refcount of the code hash so that it does not get deleted
1331		if !delete_code {
1332			<CodeInfo<T>>::increment_refcount(code_hash).unwrap();
1333		}
1334
1335		let result;
1336		#[block]
1337		{
1338			result = runtime.bench_terminate(memory.as_mut_slice(), 0);
1339		}
1340
1341		assert!(matches!(result, Err(crate::vm::pvm::TrapReason::Termination)));
1342
1343		Ok(())
1344	}
1345
1346	#[benchmark(pov_mode = Measured)]
1347	fn seal_terminate_logic() -> Result<(), BenchmarkError> {
1348		let caller = whitelisted_caller();
1349		let beneficiary = account::<T::AccountId>("beneficiary", 0, 0);
1350		T::AddressMapper::map_no_deposit_unchecked(&beneficiary)?;
1351
1352		build_runtime!(_runtime, instance, _memory: [vec![0u8; 0], ]);
1353		let code_hash = instance.info()?.code_hash;
1354
1355		assert!(PristineCode::<T>::get(code_hash).is_some());
1356
1357		T::Currency::set_balance(&instance.account_id, Pallet::<T>::min_balance() * 10u32.into());
1358
1359		let storage_deposit = T::Currency::balance_on_hold(
1360			&HoldReason::StorageDepositReserve.into(),
1361			&instance.account_id,
1362		);
1363		NativeDepositOf::<T>::insert(&instance.account_id, &caller, storage_deposit);
1364
1365		let mut transaction_meter = TransactionMeter::new(TransactionLimits::WeightAndDeposit {
1366			weight_limit: Default::default(),
1367			deposit_limit: BalanceOf::<T>::max_value(),
1368		})
1369		.unwrap();
1370		let exec_config = ExecConfig::new_substrate_tx();
1371		let contract_account = &instance.account_id;
1372		let origin = &ExecOrigin::from_account_id(caller);
1373		let beneficiary_clone = beneficiary.clone();
1374		let trie_id = instance.info()?.trie_id.clone();
1375		let code_hash = instance.info()?.code_hash;
1376		let only_if_same_tx = false;
1377
1378		let result;
1379		#[block]
1380		{
1381			result = crate::exec::bench_do_terminate::<T>(
1382				&mut transaction_meter,
1383				&exec_config,
1384				contract_account,
1385				&origin,
1386				beneficiary_clone,
1387				trie_id,
1388				code_hash,
1389				only_if_same_tx,
1390			);
1391		}
1392		result.unwrap();
1393
1394		// Check that the contract is removed
1395		assert!(PristineCode::<T>::get(code_hash).is_none());
1396
1397		// Check that the balance has been transferred away
1398		let balance = <T as Config>::Currency::total_balance(&instance.account_id);
1399		assert_eq!(balance, 0u32.into());
1400
1401		// Check that the beneficiary received the balance
1402		let balance = <T as Config>::Currency::balance(&beneficiary);
1403		assert_eq!(balance, Pallet::<T>::min_balance() + Pallet::<T>::min_balance() * 9u32.into());
1404
1405		Ok(())
1406	}
1407
1408	// Benchmark the overhead that topics generate.
1409	// `t`: Number of topics
1410	// `n`: Size of event payload in bytes
1411	#[benchmark(pov_mode = Measured)]
1412	fn seal_deposit_event(
1413		t: Linear<0, { limits::NUM_EVENT_TOPICS as u32 }>,
1414		n: Linear<0, { limits::EVENT_BYTES }>,
1415	) {
1416		let num_topic = t as u32;
1417		let topics = (0..t).map(|i| H256::repeat_byte(i as u8)).collect::<Vec<_>>();
1418		let topics_data =
1419			topics.iter().flat_map(|hash| hash.as_bytes().to_vec()).collect::<Vec<u8>>();
1420		let data = vec![42u8; n as _];
1421		build_runtime!(runtime, instance, memory: [ topics_data, data, ]);
1422
1423		let result;
1424		#[block]
1425		{
1426			result = runtime.bench_deposit_event(
1427				memory.as_mut_slice(),
1428				0, // topics_ptr
1429				num_topic,
1430				topics_data.len() as u32, // data_ptr
1431				n,                        // data_len
1432			);
1433		}
1434		assert_ok!(result);
1435
1436		let events = System::<T>::events();
1437		let record = &events[events.len() - 1];
1438
1439		assert_eq!(
1440			record.event,
1441			crate::Event::ContractEmitted { contract: instance.address, data, topics }.into(),
1442		);
1443	}
1444
1445	enum TrieFill {
1446		Empty,
1447		Full,
1448	}
1449
1450	enum SlotAccess {
1451		Cold,
1452		Hot,
1453	}
1454
1455	fn build_storage_contract<T: Config>(
1456		op: StorageOp,
1457		fill: TrieFill,
1458	) -> Result<(ContractInfo<T>, Vec<u8>, Vec<u8>), BenchmarkError> {
1459		let key = vec![0u8; limits::STORAGE_KEY_BYTES as usize];
1460		let value = vec![1u8; limits::STORAGE_BYTES as usize];
1461		let initial_value = match op {
1462			StorageOp::Read => value.clone(),
1463			StorageOp::Write => vec![42u8; limits::STORAGE_BYTES as usize],
1464		};
1465
1466		let instance = match fill {
1467			TrieFill::Full => {
1468				Contract::<T>::with_unbalanced_storage_trie(VmBinaryModule::dummy(), &key)?
1469			},
1470			TrieFill::Empty => Contract::<T>::new(VmBinaryModule::dummy(), vec![])?,
1471		};
1472		let info = instance.info()?;
1473		info.bench_write_raw(&key, Some(initial_value), false)
1474			.map_err(|_| "Failed to write to storage during setup.")?;
1475		Ok((info, key, value))
1476	}
1477
1478	enum StorageCall {
1479		Clear,
1480		Contains,
1481		Take,
1482	}
1483
1484	fn setup_precompile_bench<T: Config>(
1485		op: StorageCall,
1486		key_byte: u8,
1487		access: SlotAccess,
1488	) -> Result<(CallSetup<T>, Key, Vec<u8>), BenchmarkError> {
1489		let max_key_len = limits::STORAGE_KEY_BYTES;
1490		let key = Key::try_from_var(vec![key_byte; max_key_len as usize])
1491			.map_err(|_| "Key has wrong length")?;
1492		let raw_key = vec![key_byte; max_key_len as usize].into();
1493		let input_bytes = match op {
1494			StorageCall::Clear => {
1495				IStorage::IStorageCalls::clearStorage(IStorage::clearStorageCall {
1496					flags: StorageFlags::empty().bits(),
1497					key: raw_key,
1498					isFixedKey: false,
1499				})
1500			},
1501			StorageCall::Contains => {
1502				IStorage::IStorageCalls::containsStorage(IStorage::containsStorageCall {
1503					flags: StorageFlags::empty().bits(),
1504					key: raw_key,
1505					isFixedKey: false,
1506				})
1507			},
1508			StorageCall::Take => IStorage::IStorageCalls::takeStorage(IStorage::takeStorageCall {
1509				flags: StorageFlags::empty().bits(),
1510				key: raw_key,
1511				isFixedKey: false,
1512			}),
1513		}
1514		.abi_encode();
1515
1516		let call_setup = CallSetup::<T>::default();
1517		if matches!(access, SlotAccess::Hot) {
1518			let info = call_setup.contract().info()?;
1519			frame_benchmarking::add_to_whitelist_child(
1520				info.child_trie_info().storage_key().to_vec(),
1521				key.hash(),
1522			);
1523		}
1524		Ok((call_setup, key, input_bytes))
1525	}
1526
1527	#[benchmark(skip_meta, pov_mode = Measured)]
1528	fn get_storage_empty() -> Result<(), BenchmarkError> {
1529		let (info, key, value) = build_storage_contract::<T>(StorageOp::Read, TrieFill::Empty)?;
1530		let child_trie_info = info.child_trie_info();
1531
1532		let result;
1533		#[block]
1534		{
1535			result = child::get_raw(&child_trie_info, &key);
1536		}
1537
1538		assert_eq!(result, Some(value));
1539		Ok(())
1540	}
1541
1542	#[benchmark(skip_meta, pov_mode = Measured)]
1543	fn get_storage_full() -> Result<(), BenchmarkError> {
1544		let (info, key, value) = build_storage_contract::<T>(StorageOp::Read, TrieFill::Full)?;
1545		let child_trie_info = info.child_trie_info();
1546
1547		let result;
1548		#[block]
1549		{
1550			result = child::get_raw(&child_trie_info, &key);
1551		}
1552
1553		assert_eq!(result, Some(value));
1554		Ok(())
1555	}
1556
1557	#[benchmark(skip_meta, pov_mode = Measured)]
1558	fn set_storage_empty() -> Result<(), BenchmarkError> {
1559		let (info, key, value) = build_storage_contract::<T>(StorageOp::Write, TrieFill::Empty)?;
1560
1561		let val = Some(value.clone());
1562		let result;
1563		#[block]
1564		{
1565			result = info.bench_write_raw(&key, val, true);
1566		}
1567
1568		assert_ok!(result);
1569		assert_eq!(child::get_raw(&info.child_trie_info(), &key).unwrap(), value);
1570		Ok(())
1571	}
1572
1573	#[benchmark(skip_meta, pov_mode = Measured)]
1574	fn set_storage_full() -> Result<(), BenchmarkError> {
1575		let (info, key, value) = build_storage_contract::<T>(StorageOp::Write, TrieFill::Full)?;
1576
1577		let val = Some(value.clone());
1578		let result;
1579		#[block]
1580		{
1581			result = info.bench_write_raw(&key, val, true);
1582		}
1583
1584		assert_ok!(result);
1585		assert_eq!(child::get_raw(&info.child_trie_info(), &key).unwrap(), value);
1586		Ok(())
1587	}
1588
1589	/// Worst-case lookup keys: differing only at the tail, comparisons walk the whole key.
1590	fn shared_prefix_keys(count: usize, suffix_of: impl Fn(u64) -> u64) -> Vec<Vec<u8>> {
1591		(0..count as u64)
1592			.map(|i| {
1593				let mut key = vec![0u8; limits::STORAGE_KEY_BYTES as usize];
1594				key[limits::STORAGE_KEY_BYTES as usize - 8..]
1595					.copy_from_slice(&suffix_of(i).to_be_bytes());
1596				key
1597			})
1598			.collect()
1599	}
1600
1601	/// Probed keys must exist, or reads measure a proof-of-absence walk instead.
1602	/// Whitelisting keeps them out of the DB counters and pre-warms the cache.
1603	fn setup_stored_keys<T: Config>(
1604		count: usize,
1605		value_byte: u8,
1606		suffix_of: impl Fn(u64) -> u64,
1607	) -> Result<(ContractInfo<T>, Vec<Vec<u8>>), BenchmarkError> {
1608		let instance = Contract::<T>::new(VmBinaryModule::dummy(), vec![])?;
1609		let info = instance.info()?;
1610		let child_trie_info = info.child_trie_info();
1611		let value = vec![value_byte; limits::STORAGE_BYTES as usize];
1612		let stored_keys = shared_prefix_keys(count, suffix_of);
1613		for key in &stored_keys {
1614			info.bench_write_raw(key, Some(value.clone()), false)
1615				.map_err(|_| "Failed to write to storage during setup.")?;
1616			frame_benchmarking::add_to_whitelist_child(
1617				child_trie_info.storage_key().to_vec(),
1618				key.clone(),
1619			);
1620		}
1621		Ok((info, stored_keys))
1622	}
1623
1624	#[benchmark(skip_meta, pov_mode = Measured)]
1625	fn overlay_probe_full(
1626		n: Linear<0, { MAX_ACCESS_LIST_ENTRIES as u32 }>,
1627	) -> Result<(), BenchmarkError> {
1628		let value_byte = 42;
1629		// One stored key per probe; odd suffixes, so the even fill never overwrites them.
1630		let (info, stored_keys) =
1631			setup_stored_keys::<T>(MAX_ACCESS_LIST_ENTRIES, value_byte, |i| i * 2 + 1)?;
1632		let child_trie_info = info.child_trie_info();
1633		// The block's PoV budget bounds the overlay entries like the access list cap.
1634		let fill_keys = shared_prefix_keys(MAX_ACCESS_LIST_ENTRIES, |i| (i + 1) * 2);
1635
1636		let mut result = None;
1637		#[block]
1638		{
1639			// The benchmark framework drains the overlay right before the block, so fill here.
1640			for key in &fill_keys {
1641				child::put_raw(&child_trie_info, key, &[0u8]);
1642			}
1643			for i in 0..n {
1644				let index = i as usize % stored_keys.len();
1645				result = child::get_raw(&child_trie_info, &stored_keys[index]);
1646			}
1647		}
1648
1649		if n > 0 {
1650			let expected = vec![value_byte; limits::STORAGE_BYTES as usize];
1651			assert_eq!(result, Some(expected), "the stored value must be read back");
1652		}
1653		Ok(())
1654	}
1655
1656	#[benchmark(skip_meta, pov_mode = Measured)]
1657	fn overlay_probe_empty(
1658		n: Linear<0, { MAX_ACCESS_LIST_ENTRIES as u32 }>,
1659	) -> Result<(), BenchmarkError> {
1660		let value_byte = 42;
1661		// Reads must cost the same as in `overlay_probe_full`, so their shared cost cancels.
1662		let (info, stored_keys) =
1663			setup_stored_keys::<T>(MAX_ACCESS_LIST_ENTRIES, value_byte, |i| i * 2 + 1)?;
1664		let child_trie_info = info.child_trie_info();
1665
1666		let mut result = None;
1667		#[block]
1668		{
1669			for i in 0..n {
1670				let index = i as usize % stored_keys.len();
1671				result = child::get_raw(&child_trie_info, &stored_keys[index]);
1672			}
1673		}
1674
1675		if n > 0 {
1676			let expected = vec![value_byte; limits::STORAGE_BYTES as usize];
1677			assert_eq!(result, Some(expected), "the stored value must be read back");
1678		}
1679		Ok(())
1680	}
1681
1682	// n: new byte size
1683	// o: old byte size
1684	#[benchmark(skip_meta, pov_mode = Measured)]
1685	fn seal_set_storage(
1686		n: Linear<0, { limits::STORAGE_BYTES }>,
1687		o: Linear<0, { limits::STORAGE_BYTES }>,
1688	) -> Result<(), BenchmarkError> {
1689		let max_key_len = limits::STORAGE_KEY_BYTES;
1690		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
1691			.map_err(|_| "Key has wrong length")?;
1692		let value = vec![1u8; n as usize];
1693
1694		build_runtime!(runtime, instance, memory: [ key.unhashed(), value.clone(), ]);
1695		let info = instance.info()?;
1696
1697		info.write(&key, Some(vec![42u8; o as usize]), None, false)
1698			.map_err(|_| "Failed to write to storage during setup.")?;
1699
1700		let result;
1701		#[block]
1702		{
1703			result = runtime.bench_set_storage(
1704				memory.as_mut_slice(),
1705				StorageFlags::empty().bits(),
1706				0,           // key_ptr
1707				max_key_len, // key_len
1708				max_key_len, // value_ptr
1709				n,           // value_len
1710			);
1711		}
1712
1713		assert_ok!(result);
1714		assert_eq!(info.read(&key).unwrap(), value);
1715		Ok(())
1716	}
1717
1718	#[benchmark(skip_meta, pov_mode = Measured)]
1719	fn seal_set_storage_hot(
1720		n: Linear<0, { limits::STORAGE_BYTES }>,
1721		o: Linear<0, { limits::STORAGE_BYTES }>,
1722	) -> Result<(), BenchmarkError> {
1723		let max_key_len = limits::STORAGE_KEY_BYTES;
1724		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
1725			.map_err(|_| "Key has wrong length")?;
1726		let value = vec![1u8; n as usize];
1727
1728		build_runtime!(runtime, instance, memory: [ key.unhashed(), value.clone(), ]);
1729		let info = instance.info()?;
1730
1731		info.write(&key, Some(vec![42u8; o as usize]), None, false)
1732			.map_err(|_| "Failed to write to storage during setup.")?;
1733
1734		frame_benchmarking::add_to_whitelist_child(
1735			info.child_trie_info().storage_key().to_vec(),
1736			key.hash(),
1737		);
1738
1739		// Add the key to access list so the op's touch is hot.
1740		runtime.ext().touch_storage_access(false, &key, StorageOp::Write);
1741
1742		let result;
1743		#[block]
1744		{
1745			result = runtime.bench_set_storage(
1746				memory.as_mut_slice(),
1747				StorageFlags::empty().bits(),
1748				0,           // key_ptr
1749				max_key_len, // key_len
1750				max_key_len, // value_ptr
1751				n,           // value_len
1752			);
1753		}
1754
1755		assert_ok!(result);
1756		assert_eq!(info.read(&key).unwrap(), value);
1757		Ok(())
1758	}
1759
1760	#[benchmark(skip_meta, pov_mode = Measured)]
1761	fn clear_storage(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
1762		let key_byte = 0;
1763		let (mut call_setup, key, input_bytes) =
1764			setup_precompile_bench::<T>(StorageCall::Clear, key_byte, SlotAccess::Cold)?;
1765		let (mut ext, _) = call_setup.ext();
1766		ext.set_storage(&key, Some(vec![42u8; n as usize]), false)
1767			.map_err(|_| "Failed to write to storage during setup.")?;
1768
1769		let result;
1770		#[block]
1771		{
1772			result = run_builtin_precompile(
1773				&mut ext,
1774				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
1775				input_bytes,
1776			);
1777		}
1778		assert_ok!(result);
1779		assert!(ext.get_storage(&key).is_none());
1780
1781		Ok(())
1782	}
1783
1784	#[benchmark(skip_meta, pov_mode = Measured)]
1785	fn clear_storage_hot(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
1786		let key_byte = 0;
1787		let (mut call_setup, key, input_bytes) =
1788			setup_precompile_bench::<T>(StorageCall::Clear, key_byte, SlotAccess::Hot)?;
1789		let (mut ext, _) = call_setup.ext();
1790		ext.set_storage(&key, Some(vec![42u8; n as usize]), false)
1791			.map_err(|_| "Failed to write to storage during setup.")?;
1792
1793		ext.touch_storage_access(false, &key, StorageOp::Write);
1794
1795		let result;
1796		#[block]
1797		{
1798			result = run_builtin_precompile(
1799				&mut ext,
1800				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
1801				input_bytes,
1802			);
1803		}
1804		assert_ok!(result);
1805		assert!(ext.get_storage(&key).is_none());
1806
1807		Ok(())
1808	}
1809
1810	#[benchmark(skip_meta, pov_mode = Measured)]
1811	fn seal_get_storage(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
1812		let max_key_len = limits::STORAGE_KEY_BYTES;
1813		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
1814			.map_err(|_| "Key has wrong length")?;
1815		build_runtime!(runtime, instance, memory: [ key.unhashed(), n.to_le_bytes(), vec![0u8; n as _], ]);
1816		let info = instance.info()?;
1817
1818		info.write(&key, Some(vec![42u8; n as usize]), None, false)
1819			.map_err(|_| "Failed to write to storage during setup.")?;
1820
1821		let out_ptr = max_key_len + 4;
1822		let result;
1823		#[block]
1824		{
1825			result = runtime.bench_get_storage(
1826				memory.as_mut_slice(),
1827				StorageFlags::empty().bits(),
1828				0,           // key_ptr
1829				max_key_len, // key_len
1830				out_ptr,     // out_ptr
1831				max_key_len, // out_len_ptr
1832			);
1833		}
1834
1835		assert_ok!(result);
1836		assert_eq!(&info.read(&key).unwrap(), &memory[out_ptr as usize..]);
1837		Ok(())
1838	}
1839
1840	#[benchmark(skip_meta, pov_mode = Measured)]
1841	fn seal_get_storage_hot(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
1842		let max_key_len = limits::STORAGE_KEY_BYTES;
1843		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
1844			.map_err(|_| "Key has wrong length")?;
1845		build_runtime!(runtime, instance, memory: [ key.unhashed(), n.to_le_bytes(), vec![0u8; n as _], ]);
1846		let info = instance.info()?;
1847
1848		info.write(&key, Some(vec![42u8; n as usize]), None, false)
1849			.map_err(|_| "Failed to write to storage during setup.")?;
1850
1851		frame_benchmarking::add_to_whitelist_child(
1852			info.child_trie_info().storage_key().to_vec(),
1853			key.hash(),
1854		);
1855
1856		runtime.ext().touch_storage_access(false, &key, StorageOp::Read);
1857
1858		let out_ptr = max_key_len + 4;
1859		let result;
1860		#[block]
1861		{
1862			result = runtime.bench_get_storage(
1863				memory.as_mut_slice(),
1864				StorageFlags::empty().bits(),
1865				0,           // key_ptr
1866				max_key_len, // key_len
1867				out_ptr,     // out_ptr
1868				max_key_len, // out_len_ptr
1869			);
1870		}
1871
1872		assert_ok!(result);
1873		assert_eq!(&info.read(&key).unwrap(), &memory[out_ptr as usize..]);
1874		Ok(())
1875	}
1876
1877	#[benchmark(skip_meta, pov_mode = Measured)]
1878	fn contains_storage(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
1879		let key_byte = 0;
1880		let (mut call_setup, key, input_bytes) =
1881			setup_precompile_bench::<T>(StorageCall::Contains, key_byte, SlotAccess::Cold)?;
1882		let (mut ext, _) = call_setup.ext();
1883		ext.set_storage(&key, Some(vec![42u8; n as usize]), false)
1884			.map_err(|_| "Failed to write to storage during setup.")?;
1885
1886		let result;
1887		#[block]
1888		{
1889			result = run_builtin_precompile(
1890				&mut ext,
1891				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
1892				input_bytes,
1893			);
1894		}
1895		assert_ok!(result);
1896		assert!(ext.get_storage(&key).is_some());
1897
1898		Ok(())
1899	}
1900
1901	#[benchmark(skip_meta, pov_mode = Measured)]
1902	fn contains_storage_hot(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
1903		let key_byte = 0;
1904		let (mut call_setup, key, input_bytes) =
1905			setup_precompile_bench::<T>(StorageCall::Contains, key_byte, SlotAccess::Hot)?;
1906		let (mut ext, _) = call_setup.ext();
1907		ext.set_storage(&key, Some(vec![42u8; n as usize]), false)
1908			.map_err(|_| "Failed to write to storage during setup.")?;
1909
1910		ext.touch_storage_access(false, &key, StorageOp::Read);
1911
1912		let result;
1913		#[block]
1914		{
1915			result = run_builtin_precompile(
1916				&mut ext,
1917				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
1918				input_bytes,
1919			);
1920		}
1921		assert_ok!(result);
1922		assert!(ext.get_storage(&key).is_some());
1923
1924		Ok(())
1925	}
1926
1927	#[benchmark(skip_meta, pov_mode = Measured)]
1928	fn take_storage(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
1929		let key_byte = 3;
1930		let (mut call_setup, key, input_bytes) =
1931			setup_precompile_bench::<T>(StorageCall::Take, key_byte, SlotAccess::Cold)?;
1932		let (mut ext, _) = call_setup.ext();
1933		ext.set_storage(&key, Some(vec![42u8; n as usize]), false)
1934			.map_err(|_| "Failed to write to storage during setup.")?;
1935
1936		let result;
1937		#[block]
1938		{
1939			result = run_builtin_precompile(
1940				&mut ext,
1941				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
1942				input_bytes,
1943			);
1944		}
1945		assert_ok!(result);
1946		assert!(ext.get_storage(&key).is_none());
1947
1948		Ok(())
1949	}
1950
1951	#[benchmark(skip_meta, pov_mode = Measured)]
1952	fn take_storage_hot(n: Linear<0, { limits::STORAGE_BYTES }>) -> Result<(), BenchmarkError> {
1953		let key_byte = 3;
1954		let (mut call_setup, key, input_bytes) =
1955			setup_precompile_bench::<T>(StorageCall::Take, key_byte, SlotAccess::Hot)?;
1956		let (mut ext, _) = call_setup.ext();
1957		ext.set_storage(&key, Some(vec![42u8; n as usize]), false)
1958			.map_err(|_| "Failed to write to storage during setup.")?;
1959
1960		ext.touch_storage_access(false, &key, StorageOp::Write);
1961
1962		let result;
1963		#[block]
1964		{
1965			result = run_builtin_precompile(
1966				&mut ext,
1967				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
1968				input_bytes,
1969			);
1970		}
1971		assert_ok!(result);
1972		assert!(ext.get_storage(&key).is_none());
1973
1974		Ok(())
1975	}
1976
1977	fn worst_case_slot() -> crate::access_list::Slot {
1978		let key = Key::try_from_var(vec![0xFFu8; limits::STORAGE_KEY_BYTES as usize])
1979			.expect("key fits STORAGE_KEY_BYTES bound; qed");
1980		crate::access_list::Slot::from(&key)
1981	}
1982
1983	fn near_full_access_list() -> crate::access_list::AccessList {
1984		let mut al = AccessList::new();
1985		for i in 0..(MAX_ACCESS_LIST_ENTRIES - 1) {
1986			al.touch(
1987				AccessEntry { slot: worst_case_slot(), address: H160::from_low_u64_be(i as u64) },
1988				StorageOp::Read,
1989			);
1990		}
1991		al
1992	}
1993
1994	#[benchmark(pov_mode = Ignored)]
1995	fn access_list_touch_cold_full() -> Result<(), BenchmarkError> {
1996		let mut al = near_full_access_list();
1997		// Insert a new entry (u64::MAX is past the fill range, so the touch is cold).
1998		let entry =
1999			AccessEntry { slot: worst_case_slot(), address: H160::from_low_u64_be(u64::MAX) };
2000		let outcome;
2001		#[block]
2002		{
2003			outcome = al.touch(entry, StorageOp::Read);
2004		}
2005		assert!(outcome.is_cold());
2006		Ok(())
2007	}
2008
2009	#[benchmark(pov_mode = Ignored)]
2010	fn access_list_touch_hot_full() -> Result<(), BenchmarkError> {
2011		let mut al = near_full_access_list();
2012		// Worst-case hot touch: the rightmost key and the write upgrades the read-paid entry.
2013		let entry = AccessEntry {
2014			slot: worst_case_slot(),
2015			address: H160::from_low_u64_be(MAX_ACCESS_LIST_ENTRIES as u64 - 2),
2016		};
2017		let outcome;
2018		#[block]
2019		{
2020			outcome = al.touch(entry.clone(), StorageOp::Write);
2021		}
2022		assert_eq!(
2023			outcome,
2024			Warmth::Hot { charged: StorageOp::Read },
2025			"the fill seeded this entry read-paid"
2026		);
2027		assert_eq!(
2028			al.peek(&entry),
2029			Warmth::Hot { charged: StorageOp::Write },
2030			"the write upgraded the entry"
2031		);
2032		Ok(())
2033	}
2034
2035	#[benchmark(pov_mode = Ignored)]
2036	fn access_list_touch_cold_empty() -> Result<(), BenchmarkError> {
2037		let mut al = AccessList::new();
2038		let entry =
2039			AccessEntry { slot: worst_case_slot(), address: H160::from_low_u64_be(u64::MAX) };
2040		let outcome;
2041		#[block]
2042		{
2043			outcome = al.touch(entry, StorageOp::Read);
2044		}
2045		assert!(outcome.is_cold());
2046		Ok(())
2047	}
2048
2049	#[benchmark(pov_mode = Ignored)]
2050	fn access_list_touch_hot_single_element() -> Result<(), BenchmarkError> {
2051		let mut al = AccessList::new();
2052		let entry =
2053			AccessEntry { slot: worst_case_slot(), address: H160::from_low_u64_be(u64::MAX) };
2054		al.touch(entry.clone(), StorageOp::Read);
2055		let outcome;
2056		#[block]
2057		{
2058			outcome = al.touch(entry, StorageOp::Read);
2059		}
2060		assert!(!outcome.is_cold());
2061		Ok(())
2062	}
2063
2064	// Per-entry rollback cost, prepaid by every cold touch since a frame revert
2065	// can't charge gas itself. Isolated by reverting a frame with exactly one
2066	// journaled entry on top of a near-full `AccessList`.
2067	#[benchmark(pov_mode = Ignored)]
2068	fn access_list_rollback_amortization() -> Result<(), BenchmarkError> {
2069		let mut al = near_full_access_list();
2070		al.enter_frame();
2071		al.touch(
2072			AccessEntry { slot: worst_case_slot(), address: H160::from_low_u64_be(u64::MAX) },
2073			StorageOp::Read,
2074		);
2075		#[block]
2076		{
2077			al.rollback_frame();
2078		}
2079		Ok(())
2080	}
2081
2082	// We use both full and empty benchmarks here instead of benchmarking transient_storage
2083	// (BTreeMap) directly. This approach is necessary because benchmarking this BTreeMap is very
2084	// slow. Additionally, we use linear regression for our benchmarks, and the BTreeMap's log(n)
2085	// complexity can introduce approximation errors.
2086	#[benchmark(pov_mode = Ignored)]
2087	fn set_transient_storage_empty() -> Result<(), BenchmarkError> {
2088		let max_value_len = limits::STORAGE_BYTES;
2089		let max_key_len = limits::STORAGE_KEY_BYTES;
2090		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2091			.map_err(|_| "Key has wrong length")?;
2092		let value = Some(vec![42u8; max_value_len as _]);
2093		let mut setup = CallSetup::<T>::default();
2094		let (mut ext, _) = setup.ext();
2095		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2096		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2097		let result;
2098		#[block]
2099		{
2100			result = runtime.ext().set_transient_storage(&key, value, false);
2101		}
2102
2103		assert_eq!(result, Ok(WriteOutcome::New));
2104		assert_eq!(runtime.ext().get_transient_storage(&key), Some(vec![42u8; max_value_len as _]));
2105		Ok(())
2106	}
2107
2108	#[benchmark(pov_mode = Ignored)]
2109	fn set_transient_storage_full() -> Result<(), BenchmarkError> {
2110		let max_value_len = limits::STORAGE_BYTES;
2111		let max_key_len = limits::STORAGE_KEY_BYTES;
2112		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2113			.map_err(|_| "Key has wrong length")?;
2114		let value = Some(vec![42u8; max_value_len as _]);
2115		let mut setup = CallSetup::<T>::default();
2116		setup.set_transient_storage_size(limits::TRANSIENT_STORAGE_BYTES);
2117		let (mut ext, _) = setup.ext();
2118		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2119		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2120		let result;
2121		#[block]
2122		{
2123			result = runtime.ext().set_transient_storage(&key, value, false);
2124		}
2125
2126		assert_eq!(result, Ok(WriteOutcome::New));
2127		assert_eq!(runtime.ext().get_transient_storage(&key), Some(vec![42u8; max_value_len as _]));
2128		Ok(())
2129	}
2130
2131	#[benchmark(pov_mode = Ignored)]
2132	fn get_transient_storage_empty() -> Result<(), BenchmarkError> {
2133		let max_value_len = limits::STORAGE_BYTES;
2134		let max_key_len = limits::STORAGE_KEY_BYTES;
2135		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2136			.map_err(|_| "Key has wrong length")?;
2137
2138		let mut setup = CallSetup::<T>::default();
2139		let (mut ext, _) = setup.ext();
2140		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2141		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2142		runtime
2143			.ext()
2144			.set_transient_storage(&key, Some(vec![42u8; max_value_len as _]), false)
2145			.map_err(|_| "Failed to write to transient storage during setup.")?;
2146		let result;
2147		#[block]
2148		{
2149			result = runtime.ext().get_transient_storage(&key);
2150		}
2151
2152		assert_eq!(result, Some(vec![42u8; max_value_len as _]));
2153		Ok(())
2154	}
2155
2156	#[benchmark(pov_mode = Ignored)]
2157	fn get_transient_storage_full() -> Result<(), BenchmarkError> {
2158		let max_value_len = limits::STORAGE_BYTES;
2159		let max_key_len = limits::STORAGE_KEY_BYTES;
2160		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2161			.map_err(|_| "Key has wrong length")?;
2162
2163		let mut setup = CallSetup::<T>::default();
2164		setup.set_transient_storage_size(limits::TRANSIENT_STORAGE_BYTES);
2165		let (mut ext, _) = setup.ext();
2166		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2167		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2168		runtime
2169			.ext()
2170			.set_transient_storage(&key, Some(vec![42u8; max_value_len as _]), false)
2171			.map_err(|_| "Failed to write to transient storage during setup.")?;
2172		let result;
2173		#[block]
2174		{
2175			result = runtime.ext().get_transient_storage(&key);
2176		}
2177
2178		assert_eq!(result, Some(vec![42u8; max_value_len as _]));
2179		Ok(())
2180	}
2181
2182	// The weight of journal rollbacks should be taken into account when setting storage.
2183	#[benchmark(pov_mode = Ignored)]
2184	fn rollback_transient_storage() -> Result<(), BenchmarkError> {
2185		let max_value_len = limits::STORAGE_BYTES;
2186		let max_key_len = limits::STORAGE_KEY_BYTES;
2187		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2188			.map_err(|_| "Key has wrong length")?;
2189
2190		let mut setup = CallSetup::<T>::default();
2191		setup.set_transient_storage_size(limits::TRANSIENT_STORAGE_BYTES);
2192		let (mut ext, _) = setup.ext();
2193		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2194		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2195		runtime.ext().transient_storage().start_transaction();
2196		runtime
2197			.ext()
2198			.set_transient_storage(&key, Some(vec![42u8; max_value_len as _]), false)
2199			.map_err(|_| "Failed to write to transient storage during setup.")?;
2200		#[block]
2201		{
2202			runtime.ext().transient_storage().rollback_transaction();
2203		}
2204
2205		assert_eq!(runtime.ext().get_transient_storage(&key), None);
2206		Ok(())
2207	}
2208
2209	// n: new byte size
2210	// o: old byte size
2211	#[benchmark(pov_mode = Measured)]
2212	fn seal_set_transient_storage(
2213		n: Linear<0, { limits::STORAGE_BYTES }>,
2214		o: Linear<0, { limits::STORAGE_BYTES }>,
2215	) -> Result<(), BenchmarkError> {
2216		let max_key_len = limits::STORAGE_KEY_BYTES;
2217		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2218			.map_err(|_| "Key has wrong length")?;
2219		let value = vec![1u8; n as usize];
2220		build_runtime!(runtime, memory: [ key.unhashed(), value.clone(), ]);
2221		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2222		runtime
2223			.ext()
2224			.set_transient_storage(&key, Some(vec![42u8; o as usize]), false)
2225			.map_err(|_| "Failed to write to transient storage during setup.")?;
2226
2227		let result;
2228		#[block]
2229		{
2230			result = runtime.bench_set_storage(
2231				memory.as_mut_slice(),
2232				StorageFlags::TRANSIENT.bits(),
2233				0,           // key_ptr
2234				max_key_len, // key_len
2235				max_key_len, // value_ptr
2236				n,           // value_len
2237			);
2238		}
2239
2240		assert_ok!(result);
2241		assert_eq!(runtime.ext().get_transient_storage(&key).unwrap(), value);
2242		Ok(())
2243	}
2244
2245	#[benchmark(pov_mode = Measured)]
2246	fn seal_clear_transient_storage(
2247		n: Linear<0, { limits::STORAGE_BYTES }>,
2248	) -> Result<(), BenchmarkError> {
2249		let max_key_len = limits::STORAGE_KEY_BYTES;
2250		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2251			.map_err(|_| "Key has wrong length")?;
2252		let input_bytes = IStorage::IStorageCalls::clearStorage(IStorage::clearStorageCall {
2253			flags: StorageFlags::TRANSIENT.bits(),
2254			key: vec![0u8; max_key_len as usize].into(),
2255			isFixedKey: false,
2256		})
2257		.abi_encode();
2258
2259		let mut call_setup = CallSetup::<T>::default();
2260		let (mut ext, _) = call_setup.ext();
2261		ext.set_transient_storage(&key, Some(vec![42u8; n as usize]), false)
2262			.map_err(|_| "Failed to write to transient storage during setup.")?;
2263
2264		let result;
2265		#[block]
2266		{
2267			result = run_builtin_precompile(
2268				&mut ext,
2269				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
2270				input_bytes,
2271			);
2272		}
2273		assert_ok!(result);
2274		assert!(ext.get_transient_storage(&key).is_none());
2275
2276		Ok(())
2277	}
2278
2279	#[benchmark(pov_mode = Measured)]
2280	fn seal_get_transient_storage(
2281		n: Linear<0, { limits::STORAGE_BYTES }>,
2282	) -> Result<(), BenchmarkError> {
2283		let max_key_len = limits::STORAGE_KEY_BYTES;
2284		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2285			.map_err(|_| "Key has wrong length")?;
2286		build_runtime!(runtime, memory: [ key.unhashed(), n.to_le_bytes(), vec![0u8; n as _], ]);
2287		runtime.ext().transient_storage().meter().current_mut().limit = u32::MAX;
2288		runtime
2289			.ext()
2290			.set_transient_storage(&key, Some(vec![42u8; n as usize]), false)
2291			.map_err(|_| "Failed to write to transient storage during setup.")?;
2292
2293		let out_ptr = max_key_len + 4;
2294		let result;
2295		#[block]
2296		{
2297			result = runtime.bench_get_storage(
2298				memory.as_mut_slice(),
2299				StorageFlags::TRANSIENT.bits(),
2300				0,           // key_ptr
2301				max_key_len, // key_len
2302				out_ptr,     // out_ptr
2303				max_key_len, // out_len_ptr
2304			);
2305		}
2306
2307		assert_ok!(result);
2308		assert_eq!(
2309			&runtime.ext().get_transient_storage(&key).unwrap(),
2310			&memory[out_ptr as usize..]
2311		);
2312		Ok(())
2313	}
2314
2315	#[benchmark(pov_mode = Measured)]
2316	fn seal_contains_transient_storage(
2317		n: Linear<0, { limits::STORAGE_BYTES }>,
2318	) -> Result<(), BenchmarkError> {
2319		let max_key_len = limits::STORAGE_KEY_BYTES;
2320		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2321			.map_err(|_| "Key has wrong length")?;
2322
2323		let input_bytes = IStorage::IStorageCalls::containsStorage(IStorage::containsStorageCall {
2324			flags: StorageFlags::TRANSIENT.bits(),
2325			key: vec![0u8; max_key_len as usize].into(),
2326			isFixedKey: false,
2327		})
2328		.abi_encode();
2329
2330		let mut call_setup = CallSetup::<T>::default();
2331		let (mut ext, _) = call_setup.ext();
2332		ext.set_transient_storage(&key, Some(vec![42u8; n as usize]), false)
2333			.map_err(|_| "Failed to write to transient storage during setup.")?;
2334
2335		let result;
2336		#[block]
2337		{
2338			result = run_builtin_precompile(
2339				&mut ext,
2340				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
2341				input_bytes,
2342			);
2343		}
2344		assert!(result.is_ok());
2345		assert!(ext.get_transient_storage(&key).is_some());
2346
2347		Ok(())
2348	}
2349
2350	#[benchmark(pov_mode = Measured)]
2351	fn seal_take_transient_storage(
2352		n: Linear<0, { limits::STORAGE_BYTES }>,
2353	) -> Result<(), BenchmarkError> {
2354		let n = limits::STORAGE_BYTES;
2355		let value = vec![42u8; n as usize];
2356		let max_key_len = limits::STORAGE_KEY_BYTES;
2357		let key = Key::try_from_var(vec![0u8; max_key_len as usize])
2358			.map_err(|_| "Key has wrong length")?;
2359
2360		let input_bytes = IStorage::IStorageCalls::takeStorage(IStorage::takeStorageCall {
2361			flags: StorageFlags::TRANSIENT.bits(),
2362			key: vec![0u8; max_key_len as usize].into(),
2363			isFixedKey: false,
2364		})
2365		.abi_encode();
2366
2367		let mut call_setup = CallSetup::<T>::default();
2368		let (mut ext, _) = call_setup.ext();
2369		ext.set_transient_storage(&key, Some(value), false)
2370			.map_err(|_| "Failed to write to transient storage during setup.")?;
2371
2372		let result;
2373		#[block]
2374		{
2375			result = run_builtin_precompile(
2376				&mut ext,
2377				H160(BenchmarkStorage::<T>::MATCHER.base_address()).as_fixed_bytes(),
2378				input_bytes,
2379			);
2380		}
2381		assert!(result.is_ok());
2382		assert!(ext.get_transient_storage(&key).is_none());
2383
2384		Ok(())
2385	}
2386
2387	// t: with or without some value to transfer
2388	// d: with or without dust value to transfer
2389	// i: size of the input data
2390	#[benchmark(pov_mode = Measured)]
2391	fn seal_call(t: Linear<0, 1>, d: Linear<0, 1>, i: Linear<0, { limits::code::BLOB_BYTES }>) {
2392		let Contract { account_id: callee, address: callee_addr, .. } =
2393			Contract::<T>::with_index(1, VmBinaryModule::dummy(), vec![]).unwrap();
2394
2395		let callee_bytes = callee.encode();
2396		let callee_len = callee_bytes.len() as u32;
2397
2398		let value: BalanceOf<T> = (1_000_000u32 * t).into();
2399		let dust = 100u32 * d;
2400		let evm_value =
2401			Pallet::<T>::convert_native_to_evm(BalanceWithDust::new_unchecked::<T>(value, dust));
2402		let value_bytes = evm_value.encode();
2403
2404		let deposit: BalanceOf<T> = (u32::MAX - 100).into();
2405		let deposit_bytes = Into::<U256>::into(deposit).encode();
2406		let deposit_len = deposit_bytes.len() as u32;
2407
2408		let mut setup = CallSetup::<T>::default();
2409		setup.set_storage_deposit_limit(deposit);
2410		// We benchmark the overhead of cloning the input. Not passing it to the contract.
2411		// This is why we set the input here instead of passig it as pointer to the `bench_call`.
2412		setup.set_data(vec![42; i as usize]);
2413		setup.set_origin(ExecOrigin::from_account_id(setup.contract().account_id.clone()));
2414		setup.set_balance(value + 1u32.into() + Pallet::<T>::min_balance());
2415
2416		let (mut ext, _) = setup.ext();
2417		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2418		let mut memory = memory!(callee_bytes, deposit_bytes, value_bytes,);
2419
2420		let result;
2421		#[block]
2422		{
2423			result = runtime.bench_call(
2424				memory.as_mut_slice(),
2425				pack_hi_lo(CallFlags::CLONE_INPUT.bits(), 0), // flags + callee
2426				u64::MAX,                                     // ref_time_limit
2427				u64::MAX,                                     // proof_size_limit
2428				pack_hi_lo(callee_len, callee_len + deposit_len), // deposit_ptr + value_pr
2429				pack_hi_lo(0, 0),                             // input len + data ptr
2430				pack_hi_lo(0, SENTINEL),                      // output len + data ptr
2431			);
2432		}
2433
2434		assert_eq!(result.unwrap(), ReturnErrorCode::Success);
2435		assert_eq!(
2436			Pallet::<T>::evm_balance(&callee_addr),
2437			evm_value,
2438			"{callee_addr:?} balance should hold {evm_value:?}"
2439		);
2440	}
2441
2442	// d: 1 if the associated pre-compile has a contract info that needs to be loaded
2443	// i: size of the input data
2444	#[benchmark(pov_mode = Measured)]
2445	fn seal_call_precompile(d: Linear<0, 1>, i: Linear<0, { limits::CALLDATA_BYTES - 100 }>) {
2446		use alloy_core::sol_types::SolInterface;
2447		use precompiles::{BenchmarkNoInfo, BenchmarkWithInfo, BuiltinPrecompile, IBenchmarking};
2448
2449		let callee_bytes = if d == 1 {
2450			BenchmarkWithInfo::<T>::MATCHER.base_address().to_vec()
2451		} else {
2452			BenchmarkNoInfo::<T>::MATCHER.base_address().to_vec()
2453		};
2454		let callee_len = callee_bytes.len() as u32;
2455
2456		let deposit: BalanceOf<T> = (u32::MAX - 100).into();
2457		let deposit_bytes = Into::<U256>::into(deposit).encode();
2458		let deposit_len = deposit_bytes.len() as u32;
2459
2460		let value: BalanceOf<T> = Zero::zero();
2461		let value_bytes = Into::<U256>::into(value).encode();
2462		let value_len = value_bytes.len() as u32;
2463
2464		let input_bytes = IBenchmarking::IBenchmarkingCalls::bench(IBenchmarking::benchCall {
2465			input: vec![42_u8; i as usize].into(),
2466		})
2467		.abi_encode();
2468		let input_len = input_bytes.len() as u32;
2469
2470		let mut setup = CallSetup::<T>::default();
2471		setup.set_storage_deposit_limit(deposit);
2472
2473		let (mut ext, _) = setup.ext();
2474		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2475		let mut memory = memory!(callee_bytes, deposit_bytes, value_bytes, input_bytes,);
2476
2477		let mut do_benchmark = || {
2478			runtime.bench_call(
2479				memory.as_mut_slice(),
2480				pack_hi_lo(0, 0), // flags + callee
2481				u64::MAX,         // ref_time_limit
2482				u64::MAX,         // proof_size_limit
2483				pack_hi_lo(callee_len, callee_len + deposit_len), /* deposit_ptr +
2484				                   * value_pr */
2485				pack_hi_lo(input_len, callee_len + deposit_len + value_len), /* input len +
2486				                                                              * input ptr */
2487				pack_hi_lo(0, SENTINEL), // output len + output ptr
2488			)
2489		};
2490
2491		// first call of the pre-compile will create its contract info and account
2492		// so we make sure to create it
2493		assert_eq!(do_benchmark().unwrap(), ReturnErrorCode::Success);
2494
2495		let result;
2496		#[block]
2497		{
2498			result = do_benchmark();
2499		}
2500
2501		assert_eq!(result.unwrap(), ReturnErrorCode::Success);
2502	}
2503
2504	#[benchmark(pov_mode = Measured)]
2505	fn seal_delegate_call() -> Result<(), BenchmarkError> {
2506		let Contract { account_id: address, .. } =
2507			Contract::<T>::with_index(1, VmBinaryModule::dummy(), vec![]).unwrap();
2508
2509		let address_bytes = address.encode();
2510		let address_len = address_bytes.len() as u32;
2511
2512		let deposit: BalanceOf<T> = (u32::MAX - 100).into();
2513		let deposit_bytes = Into::<U256>::into(deposit).encode();
2514
2515		let mut setup = CallSetup::<T>::default();
2516		setup.set_storage_deposit_limit(deposit);
2517		setup.set_origin(ExecOrigin::from_account_id(setup.contract().account_id.clone()));
2518
2519		let (mut ext, _) = setup.ext();
2520		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2521		let mut memory = memory!(address_bytes, deposit_bytes,);
2522
2523		let result;
2524		#[block]
2525		{
2526			result = runtime.bench_delegate_call(
2527				memory.as_mut_slice(),
2528				pack_hi_lo(0, 0),        // flags + address ptr
2529				u64::MAX,                // ref_time_limit
2530				u64::MAX,                // proof_size_limit
2531				address_len,             // deposit_ptr
2532				pack_hi_lo(0, 0),        // input len + data ptr
2533				pack_hi_lo(0, SENTINEL), // output len + ptr
2534			);
2535		}
2536
2537		assert_eq!(result.unwrap(), ReturnErrorCode::Success);
2538		Ok(())
2539	}
2540
2541	// t: with or without some value to transfer
2542	// d: with or without dust value to transfer
2543	// i: size of the input data
2544	#[benchmark(pov_mode = Measured)]
2545	fn seal_instantiate(
2546		t: Linear<0, 1>,
2547		d: Linear<0, 1>,
2548		i: Linear<0, { limits::CALLDATA_BYTES }>,
2549	) -> Result<(), BenchmarkError> {
2550		let code = VmBinaryModule::dummy();
2551		let hash = Contract::<T>::with_index(1, VmBinaryModule::dummy(), vec![])?.info()?.code_hash;
2552		let hash_bytes = hash.encode();
2553
2554		let value: BalanceOf<T> = (1_000_000u32 * t).into();
2555		let dust = 100u32 * d;
2556		let evm_value =
2557			Pallet::<T>::convert_native_to_evm(BalanceWithDust::new_unchecked::<T>(value, dust));
2558		let value_bytes = evm_value.encode();
2559		let value_len = value_bytes.len() as u32;
2560
2561		let deposit: BalanceOf<T> = BalanceOf::<T>::max_value();
2562		let deposit_bytes = Into::<U256>::into(deposit).encode();
2563		let deposit_len = deposit_bytes.len() as u32;
2564
2565		let mut setup = CallSetup::<T>::default();
2566		setup.set_origin(ExecOrigin::from_account_id(setup.contract().account_id.clone()));
2567		setup.set_balance(value + 1u32.into() + (Pallet::<T>::min_balance() * 2u32.into()));
2568
2569		let account_id = &setup.contract().account_id.clone();
2570		let (mut ext, _) = setup.ext();
2571		let mut runtime = pvm::Runtime::<_, [u8]>::new(&mut ext, vec![]);
2572
2573		let input = vec![42u8; i as _];
2574		let input_len = hash_bytes.len() as u32 + input.len() as u32;
2575		let salt = [42u8; 32];
2576		let deployer = T::AddressMapper::to_address(&account_id);
2577		let addr = crate::address::create2(&deployer, &code.code, &input, &salt);
2578		let mut memory = memory!(hash_bytes, input, deposit_bytes, value_bytes, salt,);
2579
2580		let mut offset = {
2581			let mut current = 0u32;
2582			move |after: u32| {
2583				current += after;
2584				current
2585			}
2586		};
2587
2588		assert!(AccountInfoOf::<T>::get(&addr).is_none());
2589
2590		let result;
2591		#[block]
2592		{
2593			result = runtime.bench_instantiate(
2594				memory.as_mut_slice(),
2595				u64::MAX,                                           // ref_time_limit
2596				u64::MAX,                                           // proof_size_limit
2597				pack_hi_lo(offset(input_len), offset(deposit_len)), // deposit_ptr + value_ptr
2598				pack_hi_lo(input_len, 0),                           // input_data_len + input_data
2599				pack_hi_lo(0, SENTINEL),                            // output_len_ptr + output_ptr
2600				pack_hi_lo(SENTINEL, offset(value_len)),            // address_ptr + salt_ptr
2601			);
2602		}
2603
2604		assert_eq!(result.unwrap(), ReturnErrorCode::Success);
2605		assert!(AccountInfo::<T>::load_contract(&addr).is_some());
2606
2607		assert_eq!(
2608			Pallet::<T>::evm_balance(&addr),
2609			evm_value,
2610			"{addr:?} balance should hold {evm_value:?}"
2611		);
2612		Ok(())
2613	}
2614
2615	// t: with or without some value to transfer
2616	// d: with or without dust value to transfer
2617	// i: size of the init code (max 49152 bytes per EIP-3860)
2618	#[benchmark(pov_mode = Measured)]
2619	fn evm_instantiate(
2620		t: Linear<0, 1>,
2621		d: Linear<0, 1>,
2622		i: Linear<{ 10 * 1024 }, { 48 * 1024 }>,
2623	) -> Result<(), BenchmarkError> {
2624		use crate::vm::evm::instructions::BENCH_INIT_CODE;
2625		let mut setup = CallSetup::<T>::new(VmBinaryModule::evm_init_code_for_runtime_size(0));
2626		setup.set_origin(ExecOrigin::from_account_id(setup.contract().account_id.clone()));
2627		setup.set_balance(caller_funding::<T>());
2628
2629		let (mut ext, _) = setup.ext();
2630		let mut interpreter = Interpreter::new(Default::default(), Default::default(), &mut ext);
2631
2632		let value = {
2633			let value: BalanceOf<T> = (1_000_000u32 * t).into();
2634			let dust = 100u32 * d;
2635			Pallet::<T>::convert_native_to_evm(BalanceWithDust::new_unchecked::<T>(value, dust))
2636		};
2637
2638		let init_code = vec![BENCH_INIT_CODE; i as usize];
2639		let _ = interpreter.memory.resize(0, init_code.len());
2640		let salt = U256::from(42u64);
2641		interpreter.memory.set_data(0, 0, init_code.len(), &init_code);
2642
2643		// Setup stack for create instruction [value, offset, size, salt]
2644		let _ = interpreter.stack.push(salt);
2645		let _ = interpreter.stack.push(U256::from(init_code.len()));
2646		let _ = interpreter.stack.push(U256::zero());
2647		let _ = interpreter.stack.push(value);
2648
2649		let result;
2650		#[block]
2651		{
2652			result = instructions::contract::create::<true, _>(&mut interpreter);
2653		}
2654
2655		assert!(result.is_continue());
2656		let addr = interpreter.stack.top().unwrap().into_address();
2657		assert!(AccountInfo::<T>::load_contract(&addr).is_some());
2658		assert_eq!(Pallet::<T>::code(&addr).len(), revm::primitives::eip170::MAX_CODE_SIZE);
2659		assert_eq!(Pallet::<T>::evm_balance(&addr), value, "balance should hold {value:?}");
2660		Ok(())
2661	}
2662
2663	// `n`: Input to hash in bytes
2664	#[benchmark(pov_mode = Measured)]
2665	fn sha2_256(n: Linear<0, { limits::code::BLOB_BYTES }>) {
2666		let input = vec![0u8; n as usize];
2667		let mut call_setup = CallSetup::<T>::default();
2668		let (mut ext, _) = call_setup.ext();
2669
2670		let result;
2671		#[block]
2672		{
2673			result = run_builtin_precompile(
2674				&mut ext,
2675				H160::from_low_u64_be(2).as_fixed_bytes(),
2676				input.clone(),
2677			);
2678		}
2679		assert_eq!(sp_io::hashing::sha2_256(&input).to_vec(), result.unwrap().data);
2680	}
2681
2682	#[benchmark(pov_mode = Measured)]
2683	fn identity(n: Linear<0, { limits::code::BLOB_BYTES }>) {
2684		let input = vec![0u8; n as usize];
2685		let mut call_setup = CallSetup::<T>::default();
2686		let (mut ext, _) = call_setup.ext();
2687
2688		let result;
2689		#[block]
2690		{
2691			result = run_builtin_precompile(
2692				&mut ext,
2693				H160::from_low_u64_be(4).as_fixed_bytes(),
2694				input.clone(),
2695			);
2696		}
2697		assert_eq!(input, result.unwrap().data);
2698	}
2699
2700	// `n`: Input to hash in bytes
2701	#[benchmark(pov_mode = Measured)]
2702	fn ripemd_160(n: Linear<0, { limits::code::BLOB_BYTES }>) {
2703		use ripemd::Digest;
2704		let input = vec![0u8; n as usize];
2705		let mut call_setup = CallSetup::<T>::default();
2706		let (mut ext, _) = call_setup.ext();
2707
2708		let result;
2709		#[block]
2710		{
2711			result = run_builtin_precompile(
2712				&mut ext,
2713				H160::from_low_u64_be(3).as_fixed_bytes(),
2714				input.clone(),
2715			);
2716		}
2717		let mut expected = [0u8; 32];
2718		expected[12..32].copy_from_slice(&ripemd::Ripemd160::digest(input));
2719
2720		assert_eq!(expected.to_vec(), result.unwrap().data);
2721	}
2722
2723	// `n`: Input to hash in bytes
2724	#[benchmark(pov_mode = Measured)]
2725	fn seal_hash_keccak_256(n: Linear<0, { limits::code::BLOB_BYTES }>) {
2726		build_runtime!(runtime, memory: [[0u8; 32], vec![0u8; n as usize], ]);
2727
2728		let result;
2729		#[block]
2730		{
2731			result = runtime.bench_hash_keccak_256(memory.as_mut_slice(), 32, n, 0);
2732		}
2733		assert_eq!(sp_io::hashing::keccak_256(&memory[32..]), &memory[0..32]);
2734		assert_ok!(result);
2735	}
2736
2737	// `n`: Input to hash in bytes
2738	#[benchmark(pov_mode = Measured)]
2739	fn hash_blake2_256(n: Linear<0, { limits::code::BLOB_BYTES }>) {
2740		let input = vec![0u8; n as usize];
2741		let input_bytes = ISystem::ISystemCalls::hashBlake256(ISystem::hashBlake256Call {
2742			input: input.clone().into(),
2743		})
2744		.abi_encode();
2745
2746		let mut call_setup = CallSetup::<T>::default();
2747		let (mut ext, _) = call_setup.ext();
2748
2749		let result;
2750		#[block]
2751		{
2752			result = run_builtin_precompile(
2753				&mut ext,
2754				H160(BenchmarkSystem::<T>::MATCHER.base_address()).as_fixed_bytes(),
2755				input_bytes,
2756			);
2757		}
2758		let truth: [u8; 32] = sp_io::hashing::blake2_256(&input);
2759		let truth = FixedBytes::<32>::abi_encode(&truth);
2760		let truth = FixedBytes::<32>::abi_decode(&truth[..]).expect("decoding failed");
2761
2762		let raw_data = result.unwrap().data;
2763		let ret_hash = FixedBytes::<32>::abi_decode(&raw_data[..]).expect("decoding failed");
2764		assert_eq!(truth, ret_hash);
2765	}
2766
2767	// `n`: Input to hash in bytes
2768	#[benchmark(pov_mode = Measured)]
2769	fn hash_blake2_128(n: Linear<0, { limits::code::BLOB_BYTES }>) {
2770		let input = vec![0u8; n as usize];
2771		let input_bytes = ISystem::ISystemCalls::hashBlake128(ISystem::hashBlake128Call {
2772			input: input.clone().into(),
2773		})
2774		.abi_encode();
2775
2776		let mut call_setup = CallSetup::<T>::default();
2777		let (mut ext, _) = call_setup.ext();
2778
2779		let result;
2780		#[block]
2781		{
2782			result = run_builtin_precompile(
2783				&mut ext,
2784				H160(BenchmarkSystem::<T>::MATCHER.base_address()).as_fixed_bytes(),
2785				input_bytes,
2786			);
2787		}
2788		let truth: [u8; 16] = sp_io::hashing::blake2_128(&input);
2789		let truth = FixedBytes::<16>::abi_encode(&truth);
2790		let truth = FixedBytes::<16>::abi_decode(&truth[..]).expect("decoding failed");
2791
2792		let raw_data = result.unwrap().data;
2793		let ret_hash = FixedBytes::<16>::abi_decode(&raw_data[..]).expect("decoding failed");
2794		assert_eq!(truth, ret_hash);
2795	}
2796
2797	// `n`: Message input length to verify in bytes.
2798	// need some buffer so the code size does not exceed the max code size.
2799	#[benchmark(pov_mode = Measured)]
2800	fn seal_sr25519_verify(n: Linear<0, { limits::code::BLOB_BYTES - 255 }>) {
2801		let message = (0..n).zip((32u8..127u8).cycle()).map(|(_, c)| c).collect::<Vec<_>>();
2802		let message_len = message.len() as u32;
2803
2804		let key_type = sp_core::crypto::KeyTypeId(*b"code");
2805		let pub_key = sp_io::crypto::sr25519_generate(key_type, None);
2806		let sig =
2807			sp_io::crypto::sr25519_sign(key_type, &pub_key, &message).expect("Generates signature");
2808		let sig = AsRef::<[u8; 64]>::as_ref(&sig).to_vec();
2809		let sig_len = sig.len() as u32;
2810
2811		build_runtime!(runtime, memory: [sig, pub_key.to_vec(), message, ]);
2812
2813		let result;
2814		#[block]
2815		{
2816			result = runtime.bench_sr25519_verify(
2817				memory.as_mut_slice(),
2818				0,                              // signature_ptr
2819				sig_len,                        // pub_key_ptr
2820				message_len,                    // message_len
2821				sig_len + pub_key.len() as u32, // message_ptr
2822			);
2823		}
2824
2825		assert_eq!(result.unwrap(), ReturnErrorCode::Success);
2826	}
2827
2828	#[benchmark(pov_mode = Measured)]
2829	fn ecdsa_recover() {
2830		use hex_literal::hex;
2831		let input = hex!("18c547e4f7b0f325ad1e56f57e26c745b09a3e503d86e00e5255ff7f715d3d1c000000000000000000000000000000000000000000000000000000000000001c73b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75feeb940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549").to_vec();
2832		let expected = hex!("000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b");
2833		let mut call_setup = CallSetup::<T>::default();
2834		let (mut ext, _) = call_setup.ext();
2835
2836		let result;
2837
2838		#[block]
2839		{
2840			result =
2841				run_builtin_precompile(&mut ext, H160::from_low_u64_be(1).as_fixed_bytes(), input);
2842		}
2843
2844		assert_eq!(result.unwrap().data, expected);
2845	}
2846
2847	#[benchmark(pov_mode = Measured)]
2848	fn p256_verify() {
2849		use hex_literal::hex;
2850		let input = hex!("4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e").to_vec();
2851		let expected = U256::one().to_big_endian();
2852		let mut call_setup = CallSetup::<T>::default();
2853		let (mut ext, _) = call_setup.ext();
2854
2855		let result;
2856
2857		#[block]
2858		{
2859			result = run_builtin_precompile(
2860				&mut ext,
2861				H160::from_low_u64_be(0x100).as_fixed_bytes(),
2862				input,
2863			);
2864		}
2865
2866		assert_eq!(result.unwrap().data, expected);
2867	}
2868
2869	#[benchmark(pov_mode = Measured)]
2870	fn bn128_add() {
2871		use hex_literal::hex;
2872		let input = hex!("089142debb13c461f61523586a60732d8b69c5b38a3380a74da7b2961d867dbf2d5fc7bbc013c16d7945f190b232eacc25da675c0eb093fe6b9f1b4b4e107b3625f8c89ea3437f44f8fc8b6bfbb6312074dc6f983809a5e809ff4e1d076dd5850b38c7ced6e4daef9c4347f370d6d8b58f4b1d8dc61a3c59d651a0644a2a27cf").to_vec();
2873		let expected = hex!(
2874			"0a6678fd675aa4d8f0d03a1feb921a27f38ebdcb860cc083653519655acd6d79172fd5b3b2bfdd44e43bcec3eace9347608f9f0a16f1e184cb3f52e6f259cbeb"
2875		);
2876		let mut call_setup = CallSetup::<T>::default();
2877		let (mut ext, _) = call_setup.ext();
2878
2879		let result;
2880		#[block]
2881		{
2882			result =
2883				run_builtin_precompile(&mut ext, H160::from_low_u64_be(6).as_fixed_bytes(), input);
2884		}
2885
2886		assert_eq!(result.unwrap().data, expected);
2887	}
2888
2889	#[benchmark(pov_mode = Measured)]
2890	fn bn128_mul() {
2891		use hex_literal::hex;
2892		let input = hex!("089142debb13c461f61523586a60732d8b69c5b38a3380a74da7b2961d867dbf2d5fc7bbc013c16d7945f190b232eacc25da675c0eb093fe6b9f1b4b4e107b36ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff").to_vec();
2893		let expected = hex!(
2894			"0bf982b98a2757878c051bfe7eee228b12bc69274b918f08d9fcb21e9184ddc10b17c77cbf3c19d5d27e18cbd4a8c336afb488d0e92c18d56e64dd4ea5c437e6"
2895		);
2896		let mut call_setup = CallSetup::<T>::default();
2897		let (mut ext, _) = call_setup.ext();
2898
2899		let result;
2900		#[block]
2901		{
2902			result =
2903				run_builtin_precompile(&mut ext, H160::from_low_u64_be(7).as_fixed_bytes(), input);
2904		}
2905
2906		assert_eq!(result.unwrap().data, expected);
2907	}
2908
2909	// `n`: pairings to perform
2910	#[benchmark(pov_mode = Measured)]
2911	fn bn128_pairing(n: Linear<0, { 20 }>) {
2912		fn generate_random_ecpairs(n: usize) -> Vec<u8> {
2913			use bn::{AffineG1, AffineG2, Fr, G1, G2, Group};
2914			use rand::SeedableRng;
2915			use rand_pcg::Pcg64;
2916			let mut rng = Pcg64::seed_from_u64(1);
2917
2918			let mut buffer = vec![0u8; n * 192];
2919
2920			let mut write = |element: &bn::Fq, offset: &mut usize| {
2921				element.to_big_endian(&mut buffer[*offset..*offset + 32]).unwrap();
2922				*offset += 32
2923			};
2924
2925			for i in 0..n {
2926				let mut offset = i * 192;
2927				let scalar = Fr::random(&mut rng);
2928
2929				let g1 = G1::one() * scalar;
2930				let g2 = G2::one() * scalar;
2931				let a = AffineG1::from_jacobian(g1).expect("G1 point should be on curve");
2932				let b = AffineG2::from_jacobian(g2).expect("G2 point should be on curve");
2933
2934				write(&a.x(), &mut offset);
2935				write(&a.y(), &mut offset);
2936				write(&b.x().imaginary(), &mut offset);
2937				write(&b.x().real(), &mut offset);
2938				write(&b.y().imaginary(), &mut offset);
2939				write(&b.y().real(), &mut offset);
2940			}
2941
2942			buffer
2943		}
2944
2945		let input = generate_random_ecpairs(n as usize);
2946		let mut call_setup = CallSetup::<T>::default();
2947		let (mut ext, _) = call_setup.ext();
2948
2949		let result;
2950		#[block]
2951		{
2952			result =
2953				run_builtin_precompile(&mut ext, H160::from_low_u64_be(8).as_fixed_bytes(), input);
2954		}
2955		assert_ok!(result);
2956	}
2957
2958	// `n`: number of rounds to perform
2959	#[benchmark(pov_mode = Measured)]
2960	fn blake2f(n: Linear<0, 1200>) {
2961		use hex_literal::hex;
2962		let input = hex!(
2963			"48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b61626300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000001"
2964		);
2965		let input = n.to_be_bytes().to_vec().into_iter().chain(input.to_vec()).collect::<Vec<_>>();
2966		let mut call_setup = CallSetup::<T>::default();
2967		let (mut ext, _) = call_setup.ext();
2968
2969		let result;
2970		#[block]
2971		{
2972			result =
2973				run_builtin_precompile(&mut ext, H160::from_low_u64_be(9).as_fixed_bytes(), input);
2974		}
2975		assert_ok!(result);
2976	}
2977
2978	// Only calling the function itself for the list of
2979	// generated different ECDSA keys.
2980	// This is a slow call: We reduce the number of runs.
2981	#[benchmark(pov_mode = Measured)]
2982	fn seal_ecdsa_to_eth_address() {
2983		let key_type = sp_core::crypto::KeyTypeId(*b"code");
2984		let pub_key_bytes = sp_io::crypto::ecdsa_generate(key_type, None).0;
2985		build_runtime!(runtime, memory: [[0u8; 20], pub_key_bytes,]);
2986
2987		let result;
2988		#[block]
2989		{
2990			result = runtime.bench_ecdsa_to_eth_address(
2991				memory.as_mut_slice(),
2992				20, // key_ptr
2993				0,  // output_ptr
2994			);
2995		}
2996
2997		assert_ok!(result);
2998		assert_eq!(&memory[..20], runtime.ext().ecdsa_to_eth_address(&pub_key_bytes).unwrap());
2999	}
3000
3001	/// Benchmark the cost of executing `r` noop (JUMPDEST) instructions.
3002	#[benchmark(pov_mode = Measured)]
3003	fn evm_opcode(r: Linear<0, 10_000>) -> Result<(), BenchmarkError> {
3004		let module = VmBinaryModule::evm_noop(r);
3005		let inputs = vec![];
3006
3007		let code = Bytecode::new_raw(revm::primitives::Bytes::from(module.code.clone()));
3008		let mut setup = CallSetup::<T>::new(module);
3009		let (mut ext, _) = setup.ext();
3010
3011		let result;
3012		#[block]
3013		{
3014			result = evm::call(code, &mut ext, inputs);
3015		}
3016
3017		assert!(result.is_ok());
3018		Ok(())
3019	}
3020
3021	// Benchmark the execution of instructions.
3022	//
3023	// It benchmarks the absolute worst case by allocating a lot of memory
3024	// and then accessing it so that each instruction generates two cache misses.
3025	#[benchmark(pov_mode = Ignored)]
3026	fn instr(r: Linear<0, 10_000>) {
3027		use rand::{SeedableRng, seq::SliceRandom};
3028		use rand_pcg::Pcg64;
3029
3030		// Ideally, this needs to be bigger than the cache.
3031		const MEMORY_SIZE: u64 = sp_core::MAX_POSSIBLE_ALLOCATION as u64;
3032
3033		// This is benchmarked for x86-64.
3034		const CACHE_LINE_SIZE: u64 = 64;
3035
3036		// An 8 byte load from this misalignment will reach into the subsequent line.
3037		const MISALIGNMENT: u64 = 60;
3038
3039		// We only need one address per cache line.
3040		// -1 because we skip the first address
3041		const NUM_ADDRESSES: u64 = (MEMORY_SIZE - MISALIGNMENT) / CACHE_LINE_SIZE - 1;
3042
3043		assert!(
3044			u64::from(r) <= NUM_ADDRESSES / 2,
3045			"If we do too many iterations we run into the risk of loading from warm cache lines",
3046		);
3047
3048		let mut setup = CallSetup::<T>::new(VmBinaryModule::instr(true));
3049		let (mut ext, module) = setup.ext();
3050		let mut prepared =
3051			CallSetup::<T>::prepare_call(&mut ext, module, Vec::new(), MEMORY_SIZE as u32);
3052
3053		assert!(
3054			u64::from(prepared.aux_data_base()) & (CACHE_LINE_SIZE - 1) == 0,
3055			"aux data base must be cache aligned"
3056		);
3057
3058		// Addresses data will be located inside the aux data.
3059		let misaligned_base = u64::from(prepared.aux_data_base()) + MISALIGNMENT;
3060
3061		// Create all possible addresses and shuffle them. This makes sure
3062		// the accesses are random but no address is accessed more than once.
3063		// we skip the first address since it is our entry point
3064		let mut addresses = Vec::with_capacity(NUM_ADDRESSES as usize);
3065		for i in 1..NUM_ADDRESSES {
3066			let addr = (misaligned_base + i * CACHE_LINE_SIZE).to_le_bytes();
3067			addresses.push(addr);
3068		}
3069		let mut rng = Pcg64::seed_from_u64(1337);
3070		addresses.shuffle(&mut rng);
3071
3072		// The addresses need to be padded to be one cache line apart.
3073		let mut memory = Vec::with_capacity((NUM_ADDRESSES * CACHE_LINE_SIZE) as usize);
3074		for address in addresses {
3075			memory.extend_from_slice(&address);
3076			memory.resize(memory.len() + CACHE_LINE_SIZE as usize - address.len(), 0);
3077		}
3078
3079		// Copies `memory` to `aux_data_base + MISALIGNMENT`.
3080		// Sets `a0 = MISALIGNMENT` and `a1 = r`.
3081		prepared
3082			.setup_aux_data(memory.as_slice(), MISALIGNMENT as u32, r.into())
3083			.unwrap();
3084
3085		#[block]
3086		{
3087			prepared.call().unwrap();
3088		}
3089	}
3090
3091	#[benchmark(pov_mode = Ignored)]
3092	fn instr_empty_loop(r: Linear<0, 10_000>) {
3093		let mut setup = CallSetup::<T>::new(VmBinaryModule::instr(false));
3094		let (mut ext, module) = setup.ext();
3095		let mut prepared = CallSetup::<T>::prepare_call(&mut ext, module, Vec::new(), 0);
3096		prepared.setup_aux_data(&[], 0, r.into()).unwrap();
3097
3098		#[block]
3099		{
3100			prepared.call().unwrap();
3101		}
3102	}
3103
3104	#[benchmark(pov_mode = Measured)]
3105	fn extcodecopy(n: Linear<1_000, { 100 * 1024 }>) -> Result<(), BenchmarkError> {
3106		// The caller contract; `CallSetup` whitelists its `AccountInfoOf`.
3107		let mut setup = CallSetup::<T>::new(VmBinaryModule::dummy());
3108		// Copy a contract other than the caller, so its `AccountInfoOf` read is counted.
3109		let target = Contract::<T>::with_index(1, VmBinaryModule::sized(n), vec![])?;
3110
3111		let (mut ext, _) = setup.ext();
3112		let mut interpreter = Interpreter::new(Default::default(), Default::default(), &mut ext);
3113
3114		// Setup stack for extcodecopy instruction: [address, dest_offset, offset, size]
3115		let _ = interpreter.stack.push(U256::from(n));
3116		let _ = interpreter.stack.push(U256::from(0u32));
3117		let _ = interpreter.stack.push(U256::from(0u32));
3118		let _ = interpreter.stack.push(target.address);
3119
3120		let result;
3121		#[block]
3122		{
3123			result = instructions::host::extcodecopy(&mut interpreter);
3124		}
3125
3126		assert!(result.is_continue());
3127		assert_eq!(
3128			*interpreter.memory.slice(0..n as usize),
3129			PristineCode::<T>::get(target.info()?.code_hash).unwrap()[0..n as usize],
3130			"Memory should contain the target contract's code after extcodecopy"
3131		);
3132
3133		Ok(())
3134	}
3135
3136	#[benchmark]
3137	fn v1_migration_step() {
3138		use crate::migrations::v1;
3139		let addr = H160::from([1u8; 20]);
3140		let contract_info = ContractInfo::new(&addr, 1u32.into(), Default::default()).unwrap();
3141
3142		v1::old::ContractInfoOf::<T>::insert(addr, contract_info.clone());
3143		let mut meter = WeightMeter::new();
3144		assert_eq!(AccountInfo::<T>::load_contract(&addr), None);
3145
3146		#[block]
3147		{
3148			v1::Migration::<T>::step(None, &mut meter).unwrap();
3149		}
3150
3151		assert_eq!(v1::old::ContractInfoOf::<T>::get(&addr), None);
3152		assert_eq!(AccountInfo::<T>::load_contract(&addr).unwrap(), contract_info);
3153
3154		// uses twice the weight once for migration and then for checking if there is another key.
3155		assert_eq!(meter.consumed(), <T as Config>::WeightInfo::v1_migration_step() * 2);
3156	}
3157
3158	#[benchmark]
3159	fn v2_migration_step() {
3160		use crate::migrations::v2;
3161		let code_hash = H256::from([0; 32]);
3162		let old_code_info = v2::Migration::<T>::create_old_code_info(
3163			whitelisted_caller(),
3164			1000u32.into(),
3165			1,
3166			100,
3167			0,
3168		);
3169		v2::Migration::<T>::insert_old_code_info(code_hash, old_code_info.clone());
3170		let mut meter = WeightMeter::new();
3171
3172		#[block]
3173		{
3174			v2::Migration::<T>::step(None, &mut meter).unwrap();
3175		}
3176
3177		v2::Migration::<T>::assert_migrated_code_info(code_hash, &old_code_info);
3178
3179		// uses twice the weight once for migration and then for checking if there is another key.
3180		assert_eq!(meter.consumed(), <T as Config>::WeightInfo::v2_migration_step() * 2);
3181	}
3182
3183	#[benchmark]
3184	fn v3_migration_step() {
3185		use crate::migrations::v3;
3186		// Remove all pre-existing accounts
3187		let _ = frame_system::Account::<T>::clear(u32::MAX, None);
3188
3189		let account = account::<T::AccountId>("target", 0, 0);
3190		T::Currency::mint_into(&account, Pallet::<T>::min_balance())
3191			.expect("should mint into account");
3192
3193		// clear the mapping so the migration has work to do
3194		let addr = T::AddressMapper::to_address(&account);
3195		crate::OriginalAccount::<T>::remove(addr);
3196
3197		assert!(!T::AddressMapper::is_mapped(&account));
3198		let mut meter = WeightMeter::new();
3199
3200		#[block]
3201		{
3202			v3::Migration::<T>::step(None, &mut meter).unwrap();
3203		}
3204
3205		assert!(T::AddressMapper::is_mapped(&account));
3206
3207		// uses twice the weight: once for migration and then for checking if there is another key.
3208		assert_eq!(meter.consumed(), <T as Config>::WeightInfo::v3_migration_step() * 2);
3209	}
3210
3211	/// One iteration of v4 phase 1: credit the uploader's [`NativeDepositOf`] bucket.
3212	///
3213	/// Seeds two codes and primes the cursor with the first stored entry so the benched
3214	/// iteration exercises the `iter_from` path that dominates phase 1 in production.
3215	#[benchmark]
3216	fn v4_code_upload_step() {
3217		use crate::migrations::v4;
3218
3219		let _ = CodeInfoOf::<T>::clear(u32::MAX, None);
3220
3221		let owner: T::AccountId = whitelisted_caller();
3222		let deposit: BalanceOf<T> = 1_000u32.into();
3223
3224		let pallet_account = Pallet::<T>::account_id();
3225		T::Currency::mint_into(&pallet_account, Pallet::<T>::min_balance()).unwrap();
3226		T::Currency::mint_into(&pallet_account, deposit).unwrap();
3227		T::Currency::hold(&HoldReason::CodeUploadDepositReserve.into(), &pallet_account, deposit)
3228			.unwrap();
3229
3230		CodeInfoOf::<T>::insert(
3231			H256::from([1u8; 32]),
3232			CodeInfo::<T>::new_with_deposit(owner.clone(), deposit),
3233		);
3234		CodeInfoOf::<T>::insert(
3235			H256::from([2u8; 32]),
3236			CodeInfo::<T>::new_with_deposit(owner.clone(), deposit),
3237		);
3238
3239		let first = match v4::Migration::<T>::step_once(None) {
3240			Some(v4::Cursor::CodeUpload(h)) => h,
3241			other => panic!("expected CodeUpload cursor, got {other:?}"),
3242		};
3243		let cursor = Some(v4::Cursor::CodeUpload(first));
3244
3245		#[block]
3246		{
3247			let _ = v4::Migration::<T>::step_once(cursor);
3248		}
3249
3250		assert_eq!(
3251			NativeDepositOf::<T>::get(&pallet_account, &owner),
3252			deposit + deposit,
3253			"both code uploads credited to owner",
3254		);
3255	}
3256
3257	/// One iteration of v4 phase 2: burn native hold, mint and hold PGAS for a single contract.
3258	///
3259	/// Seeds two contracts and primes the cursor with the first stored entry so the benched
3260	/// iteration exercises the `iter_from` path that dominates phase 2 in production.
3261	#[benchmark]
3262	fn v4_contract_step() {
3263		use crate::migrations::v4;
3264
3265		let _ = AccountInfoOf::<T>::clear(u32::MAX, None);
3266
3267		let code_hash = H256::from([0u8; 32]);
3268		let deposit: BalanceOf<T> = 1_000u32.into();
3269
3270		for byte in [0x41u8, 0x42u8] {
3271			let addr = H160::from([byte; 20]);
3272			let contract_account = T::AddressMapper::to_account_id(&addr);
3273			let info =
3274				ContractInfo::<T>::new(&addr, 1u32.into(), code_hash).expect("fresh contract info");
3275			AccountInfoOf::<T>::insert(
3276				addr,
3277				crate::storage::AccountInfo::<T> {
3278					account_type: crate::storage::AccountType::Contract(info),
3279					dust: 0,
3280				},
3281			);
3282			T::Currency::mint_into(&contract_account, Pallet::<T>::min_balance()).unwrap();
3283			T::Currency::mint_into(&contract_account, deposit).unwrap();
3284			T::Currency::hold(
3285				&HoldReason::StorageDepositReserve.into(),
3286				&contract_account,
3287				deposit,
3288			)
3289			.unwrap();
3290		}
3291
3292		let first = match v4::Migration::<T>::step_once(Some(v4::Cursor::Contract(None))) {
3293			Some(v4::Cursor::Contract(Some(addr))) => addr,
3294			other => panic!("expected Contract cursor, got {other:?}"),
3295		};
3296		let cursor = Some(v4::Cursor::Contract(Some(first)));
3297
3298		#[block]
3299		{
3300			let _ = v4::Migration::<T>::step_once(cursor);
3301		}
3302
3303		// `migrate_native_to_pgas` is a no-op for `Deposit = ()`, so the hold only clears on
3304		// PGAS-backed runtimes. On non-PGAS runtimes the benchmark still measures the iter cost.
3305		if T::Deposit::SUPPORTS_PGAS {
3306			for byte in [0x41u8, 0x42u8] {
3307				let addr = H160::from([byte; 20]);
3308				let contract_account = T::AddressMapper::to_account_id(&addr);
3309				assert_eq!(
3310					T::Currency::balance_on_hold(
3311						&HoldReason::StorageDepositReserve.into(),
3312						&contract_account,
3313					),
3314					0u32.into(),
3315					"native storage deposit burned for {addr:?}",
3316				);
3317			}
3318		}
3319	}
3320
3321	/// One iteration of v4 phase 3: rewrite a legacy [`v4::old::DeletionQueue`] entry into the
3322	/// new [`DeletionQueue`] format.
3323	///
3324	/// Seeds two legacy entries and primes the cursor with the first stored entry so the benched
3325	/// iteration exercises the `iter_from` path.
3326	#[benchmark]
3327	fn v4_deletion_queue_step() {
3328		use crate::migrations::v4;
3329
3330		let _ = v4::old::DeletionQueue::<T>::clear(u32::MAX, None);
3331
3332		let trie_a: TrieId = vec![0xAAu8; 16].try_into().unwrap();
3333		let trie_b: TrieId = vec![0xBBu8; 24].try_into().unwrap();
3334		v4::old::DeletionQueue::<T>::insert(0u32, trie_a);
3335		v4::old::DeletionQueue::<T>::insert(1u32, trie_b);
3336
3337		let first = match v4::Migration::<T>::step_once(Some(v4::Cursor::DeletionQueue(None))) {
3338			Some(v4::Cursor::DeletionQueue(Some(key))) => key,
3339			other => panic!("expected DeletionQueue cursor, got {other:?}"),
3340		};
3341		let cursor = Some(v4::Cursor::DeletionQueue(Some(first)));
3342
3343		#[block]
3344		{
3345			let _ = v4::Migration::<T>::step_once(cursor);
3346		}
3347
3348		assert!(
3349			DeletionQueue::<T>::get(0u32).is_some() && DeletionQueue::<T>::get(1u32).is_some(),
3350			"both legacy entries rewritten into the new format",
3351		);
3352	}
3353
3354	/// Helper function to create a test signer for finalize_block benchmark
3355	fn create_test_signer<T: Config>() -> (T::AccountId, SigningKey, H160) {
3356		use hex_literal::hex;
3357		// dev::alith()
3358		let signer_account_id = hex!("f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac");
3359		let signer_priv_key =
3360			hex!("5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133");
3361
3362		let signer_key = SigningKey::from_bytes(&signer_priv_key.into()).expect("valid key");
3363
3364		let signer_address = H160::from_slice(&signer_account_id);
3365		let signer_caller = T::AddressMapper::to_fallback_account_id(&signer_address);
3366
3367		(signer_caller, signer_key, signer_address)
3368	}
3369
3370	/// Helper function to create and sign a transaction for finalize_block benchmark
3371	fn create_signed_transaction<T: Config>(
3372		signer_key: &SigningKey,
3373		target_address: H160,
3374		value: U256,
3375		input_data: Vec<u8>,
3376	) -> Vec<u8> {
3377		let unsigned_tx: TransactionUnsigned = TransactionLegacyUnsigned {
3378			to: Some(target_address),
3379			value,
3380			chain_id: Some(T::ChainId::get().into()),
3381			input: input_data.into(),
3382			..Default::default()
3383		}
3384		.into();
3385
3386		let hashed_payload = sp_io::hashing::keccak_256(&unsigned_tx.unsigned_payload());
3387		let (signature, recovery_id) =
3388			signer_key.sign_prehash_recoverable(&hashed_payload).expect("signing success");
3389
3390		let mut sig_bytes = [0u8; 65];
3391		sig_bytes[..64].copy_from_slice(&signature.to_bytes());
3392		sig_bytes[64] = recovery_id.to_byte();
3393
3394		let signed_tx = unsigned_tx.with_signature(sig_bytes);
3395
3396		signed_tx.signed_payload()
3397	}
3398
3399	/// Helper function to generate common finalize_block benchmark setup
3400	fn setup_finalize_block_benchmark<T>()
3401	-> Result<(Contract<T>, BalanceOf<T>, U256, SigningKey, BlockNumberFor<T>), BenchmarkError>
3402	where
3403		BalanceOf<T>: Into<U256> + TryFrom<U256>,
3404		T: Config,
3405		MomentOf<T>: Into<U256>,
3406		<T as frame_system::Config>::Hash: frame_support::traits::IsType<H256>,
3407	{
3408		// Setup test signer
3409		let (signer_caller, signer_key, _signer_address) = create_test_signer::<T>();
3410		whitelist_account!(signer_caller);
3411
3412		// Setup contract instance
3413		let instance =
3414			Contract::<T>::with_caller(signer_caller.clone(), VmBinaryModule::dummy(), vec![])?;
3415		let storage_deposit = default_deposit_limit::<T>();
3416		let value = Pallet::<T>::min_balance();
3417		let evm_value =
3418			Pallet::<T>::convert_native_to_evm(BalanceWithDust::new_unchecked::<T>(value, 0));
3419
3420		// Setup block
3421		let current_block = BlockNumberFor::<T>::from(1u32);
3422		frame_system::Pallet::<T>::set_block_number(current_block);
3423
3424		Ok((instance, storage_deposit, evm_value, signer_key, current_block))
3425	}
3426
3427	/// Benchmark the `on_finalize` hook scaling with number of transactions.
3428	///
3429	/// This benchmark measures the marginal computational cost of adding transactions
3430	/// to a block during finalization, with fixed payload size to isolate transaction
3431	/// count scaling effects.
3432	///
3433	/// ## Parameters:
3434	/// - `n`: Number of transactions in the block (0-200)
3435	///
3436	/// ## Test Setup:
3437	/// - Creates `n` transactions with fixed 100-byte payloads
3438	/// - Pre-populates block builder storage with test data
3439	///
3440	/// ## Usage:
3441	/// Use this with `on_finalize_per_byte` to calculate total cost:
3442	/// `total_cost = base + (n × per_tx_cost) + (total_bytes × per_byte_cost)`
3443	#[benchmark(pov_mode = Measured)]
3444	fn on_finalize_per_transaction(n: Linear<0, 200>) -> Result<(), BenchmarkError> {
3445		let (instance, _storage_deposit, evm_value, signer_key, current_block) =
3446			setup_finalize_block_benchmark::<T>()?;
3447
3448		// Fixed payload size to isolate transaction count effects
3449		let fixed_payload_size = 100usize;
3450
3451		// Pre-populate InflightTransactions with n transactions of fixed size
3452		if n > 0 {
3453			// Initialize block
3454			let _ = Pallet::<T>::on_initialize(current_block);
3455
3456			// Create input data of fixed size for consistent transaction payloads
3457			let input_data = vec![0x42u8; fixed_payload_size];
3458			let receipt_gas_info = ReceiptGasInfo {
3459				gas_used: U256::from(1_000_000),
3460				effective_gas_price: Pallet::<T>::evm_base_fee(),
3461			};
3462
3463			for _ in 0..n {
3464				// Create real signed transaction with fixed-size input data
3465				let signed_transaction = create_signed_transaction::<T>(
3466					&signer_key,
3467					instance.address,
3468					evm_value,
3469					input_data.clone(),
3470				);
3471
3472				// Store transaction
3473				let _ = block_storage::bench_with_ethereum_context(|| {
3474					let (encoded_logs, bloom) =
3475						block_storage::get_receipt_details().unwrap_or_default();
3476
3477					let block_builder_ir = EthBlockBuilderIR::<T>::get();
3478					let mut block_builder = EthereumBlockBuilder::<T>::from_ir(block_builder_ir);
3479
3480					block_builder.process_transaction(
3481						signed_transaction,
3482						true,
3483						receipt_gas_info.clone(),
3484						encoded_logs,
3485						bloom,
3486					);
3487
3488					EthBlockBuilderIR::<T>::put(block_builder.to_ir());
3489				});
3490			}
3491		}
3492
3493		#[block]
3494		{
3495			// Measure only the finalization cost with n transactions of fixed size
3496			let _ = Pallet::<T>::on_finalize(current_block);
3497		}
3498
3499		// Verify transaction count
3500		assert_eq!(Pallet::<T>::eth_block().transactions.len(), n as usize);
3501
3502		Ok(())
3503	}
3504
3505	/// Benchmark the `on_finalize` hook scaling with transaction payload size.
3506	///
3507	/// This benchmark measures the marginal computational cost of processing
3508	/// larger transaction payloads during finalization, with fixed transaction count
3509	/// to isolate payload size scaling effects.
3510	///
3511	/// ## Parameters:
3512	/// - `d`: Payload size per transaction in bytes (0-1000)
3513	///
3514	/// ## Test Setup:
3515	/// - Creates 10 transactions with payload size `d`
3516	/// - Pre-populates block builder storage with test data
3517	///
3518	/// ## Usage:
3519	/// Use this with `on_finalize_per_transaction` to calculate total cost:
3520	/// `total_cost = base + (n × per_tx_cost) + (total_bytes × per_byte_cost)`
3521	#[benchmark(pov_mode = Measured)]
3522	fn on_finalize_per_transaction_data(d: Linear<0, 1000>) -> Result<(), BenchmarkError> {
3523		let (instance, _storage_deposit, evm_value, signer_key, current_block) =
3524			setup_finalize_block_benchmark::<T>()?;
3525
3526		// Fixed transaction count to isolate payload size effects
3527		let fixed_tx_count = 10u32;
3528
3529		// Initialize block
3530		let _ = Pallet::<T>::on_initialize(current_block);
3531
3532		// Create input data of variable size p for realistic transaction payloads
3533		let input_data = vec![0x42u8; d as usize];
3534		let receipt_gas_info = ReceiptGasInfo {
3535			gas_used: U256::from(1_000_000),
3536			effective_gas_price: Pallet::<T>::evm_base_fee(),
3537		};
3538
3539		for _ in 0..fixed_tx_count {
3540			// Create real signed transaction with variable-size input data
3541			let signed_transaction = create_signed_transaction::<T>(
3542				&signer_key,
3543				instance.address,
3544				evm_value,
3545				input_data.clone(),
3546			);
3547
3548			// Store transaction
3549			let _ = block_storage::bench_with_ethereum_context(|| {
3550				let (encoded_logs, bloom) =
3551					block_storage::get_receipt_details().unwrap_or_default();
3552
3553				let block_builder_ir = EthBlockBuilderIR::<T>::get();
3554				let mut block_builder = EthereumBlockBuilder::<T>::from_ir(block_builder_ir);
3555
3556				block_builder.process_transaction(
3557					signed_transaction,
3558					true,
3559					receipt_gas_info.clone(),
3560					encoded_logs,
3561					bloom,
3562				);
3563
3564				EthBlockBuilderIR::<T>::put(block_builder.to_ir());
3565			});
3566		}
3567
3568		#[block]
3569		{
3570			// Measure only the finalization cost with fixed count, variable payload size
3571			let _ = Pallet::<T>::on_finalize(current_block);
3572		}
3573
3574		// Verify transaction count
3575		assert_eq!(Pallet::<T>::eth_block().transactions.len(), fixed_tx_count as usize);
3576
3577		Ok(())
3578	}
3579
3580	/// Benchmark the `on_finalize` per-event costs.
3581	///
3582	/// This benchmark measures the computational cost of processing events
3583	/// within the finalization process, isolating the overhead of event count.
3584	/// Uses a single transaction with varying numbers of minimal events.
3585	///
3586	/// ## Parameters:
3587	/// - `e`: Number of events per transaction
3588	///
3589	/// ## Test Setup:
3590	/// - Creates 1 transaction with `e` ContractEmitted events
3591	/// - Each event contains minimal data (no topics, empty data field)
3592	///
3593	/// ## Usage:
3594	/// Measures the per-event processing overhead during finalization
3595	/// - Fixed cost: `on_finalize_per_event(0)` - baseline finalization cost
3596	/// - Per event: `on_finalize_per_event(e)` - linear scaling with event count
3597	#[benchmark(pov_mode = Measured)]
3598	fn on_finalize_per_event(e: Linear<0, 100>) -> Result<(), BenchmarkError> {
3599		let (instance, _storage_deposit, evm_value, signer_key, current_block) =
3600			setup_finalize_block_benchmark::<T>()?;
3601
3602		// Create a single transaction with e events, each with minimal data
3603		let input_data = vec![0x42u8; 100];
3604		let signed_transaction = create_signed_transaction::<T>(
3605			&signer_key,
3606			instance.address,
3607			evm_value,
3608			input_data.clone(),
3609		);
3610
3611		let receipt_gas_info = ReceiptGasInfo {
3612			gas_used: U256::from(1_000_000),
3613			effective_gas_price: Pallet::<T>::evm_base_fee(),
3614		};
3615
3616		// Store transaction
3617		let _ = block_storage::bench_with_ethereum_context(|| {
3618			let (encoded_logs, bloom) = block_storage::get_receipt_details().unwrap_or_default();
3619
3620			let block_builder_ir = EthBlockBuilderIR::<T>::get();
3621			let mut block_builder = EthereumBlockBuilder::<T>::from_ir(block_builder_ir);
3622
3623			block_builder.process_transaction(
3624				signed_transaction,
3625				true,
3626				receipt_gas_info.clone(),
3627				encoded_logs,
3628				bloom,
3629			);
3630
3631			EthBlockBuilderIR::<T>::put(block_builder.to_ir());
3632		});
3633
3634		// Create e events with minimal data to isolate event count overhead
3635		for _ in 0..e {
3636			block_storage::capture_ethereum_log(&instance.address, &vec![], &vec![]);
3637		}
3638
3639		#[block]
3640		{
3641			// Initialize block
3642			let _ = Pallet::<T>::on_initialize(current_block);
3643
3644			// Measure the finalization cost with e events
3645			let _ = Pallet::<T>::on_finalize(current_block);
3646		}
3647
3648		// Verify transaction count
3649		assert_eq!(Pallet::<T>::eth_block().transactions.len(), 1);
3650
3651		Ok(())
3652	}
3653
3654	/// ## Test Setup:
3655	/// - Creates 1 transaction with 1 ContractEmitted event
3656	/// - Event contains `d` total bytes of data across data field and topics
3657	///
3658	/// ## Usage:
3659	/// Measures the per-byte event data processing overhead during finalization
3660	/// - Fixed cost: `on_finalize_per_event_data(0)` - baseline cost with empty event
3661	/// - Per byte: `on_finalize_per_event_data(d)` - linear scaling with data size
3662	#[benchmark(pov_mode = Measured)]
3663	fn on_finalize_per_event_data(d: Linear<0, 16384>) -> Result<(), BenchmarkError> {
3664		let (instance, _storage_deposit, evm_value, signer_key, current_block) =
3665			setup_finalize_block_benchmark::<T>()?;
3666
3667		// Create a single transaction with one event containing d bytes of data
3668		let input_data = vec![0x42u8; 100];
3669		let signed_transaction = create_signed_transaction::<T>(
3670			&signer_key,
3671			instance.address,
3672			evm_value,
3673			input_data.clone(),
3674		);
3675
3676		let receipt_gas_info = ReceiptGasInfo {
3677			gas_used: U256::from(1_000_000),
3678			effective_gas_price: Pallet::<T>::evm_base_fee(),
3679		};
3680
3681		// Store transaction
3682		let _ = block_storage::bench_with_ethereum_context(|| {
3683			let (encoded_logs, bloom) = block_storage::get_receipt_details().unwrap_or_default();
3684
3685			let block_builder_ir = EthBlockBuilderIR::<T>::get();
3686			let mut block_builder = EthereumBlockBuilder::<T>::from_ir(block_builder_ir);
3687
3688			block_builder.process_transaction(
3689				signed_transaction,
3690				true,
3691				receipt_gas_info,
3692				encoded_logs,
3693				bloom,
3694			);
3695
3696			EthBlockBuilderIR::<T>::put(block_builder.to_ir());
3697		});
3698
3699		// Create one event with d bytes of data distributed across topics and data field
3700		let (event_data, topics) = if d < 32 {
3701			// If total data is less than 32 bytes, put all in data field
3702			(vec![0x42u8; d as usize], vec![])
3703		} else {
3704			// Fill topics first, then put remaining bytes in data field
3705			let num_topics = core::cmp::min(limits::NUM_EVENT_TOPICS, d / 32);
3706			let topic_bytes_used = num_topics * 32;
3707			let data_bytes_remaining = d - topic_bytes_used;
3708
3709			// Create topics filled with sequential data
3710			let mut topics = Vec::new();
3711			for topic_index in 0..num_topics {
3712				let topic_data = [topic_index as u8; 32];
3713				topics.push(H256::from(topic_data));
3714			}
3715
3716			// Remaining bytes go to data field
3717			let event_data = vec![0x42u8; data_bytes_remaining as usize];
3718
3719			(event_data, topics)
3720		};
3721
3722		block_storage::capture_ethereum_log(&instance.address, &event_data, &topics);
3723
3724		#[block]
3725		{
3726			// Initialize block
3727			let _ = Pallet::<T>::on_initialize(current_block);
3728
3729			// Measure the finalization cost with d bytes of event data
3730			let _ = Pallet::<T>::on_finalize(current_block);
3731		}
3732
3733		// Verify transaction count
3734		assert_eq!(Pallet::<T>::eth_block().transactions.len(), 1);
3735
3736		Ok(())
3737	}
3738
3739	impl_benchmark_test_suite!(
3740		Contracts,
3741		crate::tests::ExtBuilder::default().build(),
3742		crate::tests::Test,
3743	);
3744}