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