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