pallet_evm/runner/
stack.rs

1// This file is part of Frontier.
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//! EVM stack-based runner.
19
20use alloc::{
21	boxed::Box,
22	collections::{btree_map::BTreeMap, btree_set::BTreeSet},
23	vec::Vec,
24};
25use core::{marker::PhantomData, mem};
26use ethereum::AuthorizationList;
27use evm::{
28	backend::Backend as BackendT,
29	executor::stack::{Accessed, StackExecutor, StackState as StackStateT, StackSubstateMetadata},
30	gasometer::{GasCost, StorageTarget},
31	ExitError, ExitReason, ExternalOperation, Opcode, Transfer,
32};
33// Cumulus
34use cumulus_primitives_storage_weight_reclaim::get_proof_size;
35// Substrate
36use frame_support::{
37	traits::{
38		tokens::{currency::Currency, ExistenceRequirement},
39		Get, Time,
40	},
41	weights::Weight,
42};
43use sp_core::{H160, H256, U256};
44use sp_runtime::traits::UniqueSaturatedInto;
45// Frontier
46use fp_evm::{
47	AccessedStorage, CallInfo, CreateInfo, ExecutionInfoV2, IsPrecompileResult, Log, PrecompileSet,
48	Vicinity, WeightInfo, ACCOUNT_BASIC_PROOF_SIZE, ACCOUNT_CODES_KEY_SIZE,
49	ACCOUNT_CODES_METADATA_PROOF_SIZE, ACCOUNT_STORAGE_PROOF_SIZE, IS_EMPTY_CHECK_PROOF_SIZE,
50	WRITE_PROOF_SIZE,
51};
52
53use super::meter::StorageMeter;
54use crate::{
55	runner::Runner as RunnerT, AccountCodes, AccountCodesMetadata, AccountProvider,
56	AccountStorages, AddressMapping, BalanceOf, BlockHashMapping, Config, EnsureCreateOrigin,
57	Error, Event, FeeCalculator, OnChargeEVMTransaction, OnCreate, Pallet, RunnerError,
58};
59
60#[cfg(feature = "forbid-evm-reentrancy")]
61environmental::environmental!(IN_EVM: bool);
62
63#[derive(Default)]
64pub struct Runner<T: Config> {
65	_marker: PhantomData<T>,
66}
67
68impl<T: Config> Runner<T>
69where
70	BalanceOf<T>: TryFrom<U256> + Into<U256>,
71{
72	#[allow(clippy::let_and_return)]
73	/// Execute an already validated EVM operation.
74	fn execute<'config, 'precompiles, F, R>(
75		source: H160,
76		value: U256,
77		gas_limit: u64,
78		max_fee_per_gas: Option<U256>,
79		max_priority_fee_per_gas: Option<U256>,
80		config: &'config evm::Config,
81		precompiles: &'precompiles T::PrecompilesType,
82		is_transactional: bool,
83		weight_limit: Option<Weight>,
84		proof_size_base_cost: Option<u64>,
85		measured_proof_size_before: u64,
86		f: F,
87	) -> Result<ExecutionInfoV2<R>, RunnerError<Error<T>>>
88	where
89		F: FnOnce(
90			&mut StackExecutor<
91				'config,
92				'precompiles,
93				SubstrateStackState<'_, 'config, T>,
94				T::PrecompilesType,
95			>,
96		) -> (ExitReason, R),
97		R: Default,
98	{
99		let (base_fee, weight) = T::FeeCalculator::min_gas_price();
100
101		#[cfg(not(feature = "forbid-evm-reentrancy"))]
102		let res = Self::execute_inner(
103			source,
104			value,
105			gas_limit,
106			max_fee_per_gas,
107			max_priority_fee_per_gas,
108			config,
109			precompiles,
110			is_transactional,
111			f,
112			base_fee,
113			weight,
114			weight_limit,
115			proof_size_base_cost,
116			measured_proof_size_before,
117		);
118
119		#[cfg(feature = "forbid-evm-reentrancy")]
120		let res = IN_EVM::using_once(&mut false, || {
121			IN_EVM::with(|in_evm| {
122				if *in_evm {
123					return Err(RunnerError {
124						error: Error::<T>::Reentrancy,
125						weight,
126					});
127				}
128				*in_evm = true;
129				Ok(())
130			})
131			// This should always return `Some`, but let's play it safe.
132			.unwrap_or(Ok(()))?;
133
134			// Ensure that we always release the lock whenever we finish processing
135			sp_core::defer! {
136				IN_EVM::with(|in_evm| {
137					*in_evm = false;
138				});
139			}
140
141			Self::execute_inner(
142				source,
143				value,
144				gas_limit,
145				max_fee_per_gas,
146				max_priority_fee_per_gas,
147				config,
148				precompiles,
149				is_transactional,
150				f,
151				base_fee,
152				weight,
153				weight_limit,
154				proof_size_base_cost,
155				measured_proof_size_before,
156			)
157		});
158
159		res
160	}
161
162	// Execute an already validated EVM operation.
163	fn execute_inner<'config, 'precompiles, F, R>(
164		source: H160,
165		value: U256,
166		mut gas_limit: u64,
167		max_fee_per_gas: Option<U256>,
168		max_priority_fee_per_gas: Option<U256>,
169		config: &'config evm::Config,
170		precompiles: &'precompiles T::PrecompilesType,
171		is_transactional: bool,
172		f: F,
173		base_fee: U256,
174		weight: Weight,
175		weight_limit: Option<Weight>,
176		proof_size_base_cost: Option<u64>,
177		measured_proof_size_before: u64,
178	) -> Result<ExecutionInfoV2<R>, RunnerError<Error<T>>>
179	where
180		F: FnOnce(
181			&mut StackExecutor<
182				'config,
183				'precompiles,
184				SubstrateStackState<'_, 'config, T>,
185				T::PrecompilesType,
186			>,
187		) -> (ExitReason, R),
188		R: Default,
189	{
190		// Used to record the external costs in the evm through the StackState implementation
191		let maybe_weight_info =
192			WeightInfo::new_from_weight_limit(weight_limit, proof_size_base_cost).map_err(
193				|_| RunnerError {
194					error: Error::<T>::GasLimitTooLow,
195					weight,
196				},
197			)?;
198		// The precompile check is only used for transactional invocations. However, here we always
199		// execute the check, because the check has side effects.
200		match precompiles.is_precompile(source, gas_limit) {
201			IsPrecompileResult::Answer { extra_cost, .. } => {
202				gas_limit = gas_limit.saturating_sub(extra_cost);
203			}
204			IsPrecompileResult::OutOfGas => {
205				return Ok(ExecutionInfoV2 {
206					exit_reason: ExitError::OutOfGas.into(),
207					value: Default::default(),
208					used_gas: fp_evm::UsedGas {
209						standard: gas_limit.into(),
210						effective: gas_limit.into(),
211					},
212					weight_info: maybe_weight_info,
213					logs: Default::default(),
214				})
215			}
216		};
217
218		// Only check the restrictions of EIP-3607 if the source of the EVM operation is from an external transaction.
219		// If the source of this EVM operation is from an internal call, like from `eth_call` or `eth_estimateGas` RPC,
220		// we will skip the checks for the EIP-3607.
221		//
222		// EIP-3607: https://eips.ethereum.org/EIPS/eip-3607
223		// Do not allow transactions for which `tx.sender` has any code deployed.
224		// Exception: Allow transactions from EOAs whose code is a valid delegation indicator (0xef0100 || address).
225		if is_transactional {
226			// Check if the account has code deployed
227			if let Some(metadata) = <AccountCodesMetadata<T>>::get(source) {
228				if metadata.size > 0 {
229					// Account has code, check if it's a valid delegation
230					let is_delegation = metadata.size
231						== evm::delegation::EIP_7702_DELEGATION_SIZE as u64
232						&& <AccountCodes<T>>::get(source)
233							.starts_with(evm::delegation::EIP_7702_DELEGATION_PREFIX);
234
235					if !is_delegation {
236						return Err(RunnerError {
237							error: Error::<T>::TransactionMustComeFromEOA,
238							weight,
239						});
240					}
241				}
242			}
243		}
244
245		let total_fee_per_gas = if is_transactional {
246			match (max_fee_per_gas, max_priority_fee_per_gas) {
247				// Zero max_fee_per_gas for validated transactional calls exist in XCM -> EVM
248				// because fees are already withdrawn in the xcm-executor.
249				(Some(max_fee), _) if max_fee.is_zero() => U256::zero(),
250				// With no tip, we pay exactly the base_fee
251				(Some(_), None) => base_fee,
252				// With tip, we include as much of the tip on top of base_fee that we can, never
253				// exceeding max_fee_per_gas
254				(Some(max_fee_per_gas), Some(max_priority_fee_per_gas)) => {
255					let actual_priority_fee_per_gas = max_fee_per_gas
256						.saturating_sub(base_fee)
257						.min(max_priority_fee_per_gas);
258
259					base_fee.saturating_add(actual_priority_fee_per_gas)
260				}
261				_ => {
262					return Err(RunnerError {
263						error: Error::<T>::GasPriceTooLow,
264						weight,
265					})
266				}
267			}
268		} else {
269			// Gas price check is skipped for non-transactional calls or creates
270			Default::default()
271		};
272
273		// After eip-1559 we make sure the account can pay both the evm execution and priority fees.
274		let total_fee =
275			total_fee_per_gas
276				.checked_mul(U256::from(gas_limit))
277				.ok_or(RunnerError {
278					error: Error::<T>::FeeOverflow,
279					weight,
280				})?;
281
282		// Deduct fee from the `source` account. Returns `None` if `total_fee` is Zero.
283		let fee = T::OnChargeTransaction::withdraw_fee(&source, total_fee)
284			.map_err(|e| RunnerError { error: e, weight })?;
285
286		let vicinity = Vicinity {
287			gas_price: base_fee,
288			origin: source,
289		};
290
291		// Compute the storage limit based on the gas limit and the storage growth ratio.
292		let storage_growth_ratio = T::GasLimitStorageGrowthRatio::get();
293		let storage_limit = if storage_growth_ratio > 0 {
294			let storage_limit = gas_limit.saturating_div(storage_growth_ratio);
295			Some(storage_limit)
296		} else {
297			None
298		};
299
300		let metadata = StackSubstateMetadata::new(gas_limit, config);
301		let state = SubstrateStackState::new(&vicinity, metadata, maybe_weight_info, storage_limit);
302		let mut executor = StackExecutor::new_with_precompiles(state, config, precompiles);
303
304		// Execute the EVM call.
305		let (reason, retv, used_gas, effective_gas) = fp_evm::handle_storage_oog::<R, _>(
306			gas_limit,
307			|| {
308				let (reason, retv) = f(&mut executor);
309
310				// Compute the storage gas cost based on the storage growth.
311				let storage_gas = match &executor.state().storage_meter {
312					Some(storage_meter) => storage_meter.storage_to_gas(storage_growth_ratio),
313					None => 0,
314				};
315
316				let estimated_proof_size = executor
317					.state()
318					.weight_info()
319					.unwrap_or_default()
320					.proof_size_usage
321					.unwrap_or_default();
322
323				// Obtain the actual proof size usage using the ProofSizeExt host-function or fallback
324				// and use the estimated proof size
325				let actual_proof_size = if let Some(measured_proof_size_after) = get_proof_size() {
326					// actual_proof_size = proof_size_base_cost + proof_size measured with ProofSizeExt
327					let actual_proof_size =
328						proof_size_base_cost.unwrap_or_default().saturating_add(
329							measured_proof_size_after.saturating_sub(measured_proof_size_before),
330						);
331
332					log::trace!(
333						target: "evm",
334						"Proof size computation: (estimated: {estimated_proof_size}, actual: {actual_proof_size})"
335					);
336
337					// If the proof_size calculated from the host-function gives an higher cost than
338					// the estimated proof_size, we should use the estimated proof_size to compute
339					// the PoV gas.
340					//
341					// TODO: The estimated proof_size should always be an overestimate
342					if actual_proof_size > estimated_proof_size {
343						log::debug!(
344							target: "evm",
345							"Proof size underestimation detected! (estimated: {estimated_proof_size}, actual: {actual_proof_size}, diff: {})",
346							actual_proof_size.saturating_sub(estimated_proof_size)
347						);
348						estimated_proof_size
349					} else {
350						actual_proof_size
351					}
352				} else {
353					estimated_proof_size
354				};
355
356				// Post execution.
357				let pov_gas = actual_proof_size.saturating_mul(T::GasLimitPovSizeRatio::get());
358				let used_gas = executor.used_gas();
359				let effective_gas = core::cmp::max(core::cmp::max(used_gas, pov_gas), storage_gas);
360
361				log::debug!(
362					target: "evm",
363					"Calculating effective gas: max(used: {used_gas}, pov: {pov_gas}, storage: {storage_gas}) = {effective_gas}"
364				);
365
366				(reason, retv, used_gas, U256::from(effective_gas))
367			},
368		);
369
370		let actual_fee = effective_gas.saturating_mul(total_fee_per_gas);
371		let actual_base_fee = effective_gas.saturating_mul(base_fee);
372
373		log::debug!(
374			target: "evm",
375			"Execution {reason:?} [source: {source:?}, value: {value}, gas_limit: {gas_limit}, actual_fee: {actual_fee}, used_gas: {used_gas}, effective_gas: {effective_gas}, base_fee: {base_fee}, total_fee_per_gas: {total_fee_per_gas}, is_transactional: {is_transactional}]"
376		);
377		// The difference between initially withdrawn and the actual cost is refunded.
378		//
379		// Considered the following request:
380		// +-----------+---------+--------------+
381		// | Gas_limit | Max_Fee | Max_Priority |
382		// +-----------+---------+--------------+
383		// |        20 |      10 |            6 |
384		// +-----------+---------+--------------+
385		//
386		// And execution:
387		// +----------+----------+
388		// | Gas_used | Base_Fee |
389		// +----------+----------+
390		// |        5 |        2 |
391		// +----------+----------+
392		//
393		// Initially withdrawn 10 * 20 = 200.
394		// Actual cost (2 + 6) * 5 = 40.
395		// Refunded 200 - 40 = 160.
396		// Tip 5 * 6 = 30.
397		// Burned 200 - (160 + 30) = 10. Which is equivalent to gas_used * base_fee.
398		let actual_priority_fee = T::OnChargeTransaction::correct_and_deposit_fee(
399			&source,
400			// Actual fee after evm execution, including tip.
401			actual_fee,
402			// Base fee.
403			actual_base_fee,
404			// Fee initially withdrawn.
405			fee,
406		);
407		T::OnChargeTransaction::pay_priority_fee(actual_priority_fee);
408
409		let state = executor.into_state();
410
411		for address in &state.substate.deletes {
412			log::debug!(
413				target: "evm",
414				"Deleting account at {address:?}"
415			);
416			Pallet::<T>::remove_account(address)
417		}
418
419		for log in &state.substate.logs {
420			log::trace!(
421				target: "evm",
422				"Inserting log for {:?}, topics ({}) {:?}, data ({}): {:?}]",
423				log.address,
424				log.topics.len(),
425				log.topics,
426				log.data.len(),
427				log.data
428			);
429			Pallet::<T>::deposit_event(Event::<T>::Log {
430				log: Log {
431					address: log.address,
432					topics: log.topics.clone(),
433					data: log.data.clone(),
434				},
435			});
436		}
437
438		Ok(ExecutionInfoV2 {
439			value: retv,
440			exit_reason: reason,
441			used_gas: fp_evm::UsedGas {
442				standard: used_gas.into(),
443				effective: effective_gas,
444			},
445			weight_info: state.weight_info(),
446			logs: state.substate.logs,
447		})
448	}
449}
450
451impl<T: Config> RunnerT<T> for Runner<T>
452where
453	BalanceOf<T>: TryFrom<U256> + Into<U256>,
454{
455	type Error = Error<T>;
456
457	fn validate(
458		source: H160,
459		target: Option<H160>,
460		input: Vec<u8>,
461		value: U256,
462		gas_limit: u64,
463		max_fee_per_gas: Option<U256>,
464		max_priority_fee_per_gas: Option<U256>,
465		nonce: Option<U256>,
466		access_list: Vec<(H160, Vec<H256>)>,
467		authorization_list: Vec<(U256, H160, U256, Option<H160>)>,
468		is_transactional: bool,
469		weight_limit: Option<Weight>,
470		proof_size_base_cost: Option<u64>,
471		evm_config: &evm::Config,
472	) -> Result<(), RunnerError<Self::Error>> {
473		let (base_fee, mut weight) = T::FeeCalculator::min_gas_price();
474		let (source_account, inner_weight) = Pallet::<T>::account_basic(&source);
475		weight = weight.saturating_add(inner_weight);
476
477		let _ = fp_evm::CheckEvmTransaction::<Self::Error>::new(
478			fp_evm::CheckEvmTransactionConfig {
479				evm_config,
480				block_gas_limit: T::BlockGasLimit::get(),
481				base_fee,
482				chain_id: T::ChainId::get(),
483				is_transactional,
484			},
485			fp_evm::CheckEvmTransactionInput {
486				chain_id: Some(T::ChainId::get()),
487				to: target,
488				input,
489				nonce: nonce.unwrap_or(source_account.nonce),
490				gas_limit: gas_limit.into(),
491				gas_price: None,
492				max_fee_per_gas,
493				max_priority_fee_per_gas,
494				value,
495				access_list,
496				authorization_list,
497			},
498			weight_limit,
499			proof_size_base_cost,
500		)
501		.validate_in_block_for(&source_account)
502		.and_then(|v| v.with_base_fee())
503		.and_then(|v| v.with_balance_for(&source_account))
504		.map_err(|error| RunnerError { error, weight })?;
505		Ok(())
506	}
507
508	fn call(
509		source: H160,
510		target: H160,
511		input: Vec<u8>,
512		value: U256,
513		gas_limit: u64,
514		max_fee_per_gas: Option<U256>,
515		max_priority_fee_per_gas: Option<U256>,
516		nonce: Option<U256>,
517		access_list: Vec<(H160, Vec<H256>)>,
518		authorization_list: AuthorizationList,
519		is_transactional: bool,
520		validate: bool,
521		weight_limit: Option<Weight>,
522		proof_size_base_cost: Option<u64>,
523		config: &evm::Config,
524	) -> Result<CallInfo, RunnerError<Self::Error>> {
525		let measured_proof_size_before = get_proof_size().unwrap_or_default();
526
527		let authorization_list = authorization_list
528			.iter()
529			.map(|d| {
530				(
531					U256::from(d.chain_id),
532					d.address,
533					d.nonce,
534					d.authorizing_address().ok(),
535				)
536			})
537			.collect::<Vec<(U256, sp_core::H160, U256, Option<sp_core::H160>)>>();
538
539		if validate {
540			Self::validate(
541				source,
542				Some(target),
543				input.clone(),
544				value,
545				gas_limit,
546				max_fee_per_gas,
547				max_priority_fee_per_gas,
548				nonce,
549				access_list.clone(),
550				authorization_list.clone(),
551				is_transactional,
552				weight_limit,
553				proof_size_base_cost,
554				config,
555			)?;
556		}
557
558		let precompiles = T::PrecompilesValue::get();
559		Self::execute(
560			source,
561			value,
562			gas_limit,
563			max_fee_per_gas,
564			max_priority_fee_per_gas,
565			config,
566			&precompiles,
567			is_transactional,
568			weight_limit,
569			proof_size_base_cost,
570			measured_proof_size_before,
571			|executor| {
572				executor.transact_call(
573					source,
574					target,
575					value,
576					input,
577					gas_limit,
578					access_list,
579					authorization_list,
580				)
581			},
582		)
583	}
584
585	fn create(
586		source: H160,
587		init: Vec<u8>,
588		value: U256,
589		gas_limit: u64,
590		max_fee_per_gas: Option<U256>,
591		max_priority_fee_per_gas: Option<U256>,
592		nonce: Option<U256>,
593		access_list: Vec<(H160, Vec<H256>)>,
594		authorization_list: AuthorizationList,
595		is_transactional: bool,
596		validate: bool,
597		weight_limit: Option<Weight>,
598		proof_size_base_cost: Option<u64>,
599		config: &evm::Config,
600	) -> Result<CreateInfo, RunnerError<Self::Error>> {
601		let measured_proof_size_before = get_proof_size().unwrap_or_default();
602		let (_, weight) = T::FeeCalculator::min_gas_price();
603
604		T::CreateOriginFilter::check_create_origin(&source)
605			.map_err(|error| RunnerError { error, weight })?;
606
607		let authorization_list = authorization_list
608			.iter()
609			.map(|d| {
610				(
611					U256::from(d.chain_id),
612					d.address,
613					d.nonce,
614					d.authorizing_address().ok(),
615				)
616			})
617			.collect::<Vec<(U256, sp_core::H160, U256, Option<sp_core::H160>)>>();
618
619		if validate {
620			Self::validate(
621				source,
622				None,
623				init.clone(),
624				value,
625				gas_limit,
626				max_fee_per_gas,
627				max_priority_fee_per_gas,
628				nonce,
629				access_list.clone(),
630				authorization_list.clone(),
631				is_transactional,
632				weight_limit,
633				proof_size_base_cost,
634				config,
635			)?;
636		}
637
638		let precompiles = T::PrecompilesValue::get();
639		Self::execute(
640			source,
641			value,
642			gas_limit,
643			max_fee_per_gas,
644			max_priority_fee_per_gas,
645			config,
646			&precompiles,
647			is_transactional,
648			weight_limit,
649			proof_size_base_cost,
650			measured_proof_size_before,
651			|executor| {
652				let address = executor.create_address(evm::CreateScheme::Legacy { caller: source });
653				T::OnCreate::on_create(source, address);
654				let (reason, _) = executor.transact_create(
655					source,
656					value,
657					init,
658					gas_limit,
659					access_list,
660					authorization_list,
661				);
662				(reason, address)
663			},
664		)
665	}
666
667	fn create2(
668		source: H160,
669		init: Vec<u8>,
670		salt: H256,
671		value: U256,
672		gas_limit: u64,
673		max_fee_per_gas: Option<U256>,
674		max_priority_fee_per_gas: Option<U256>,
675		nonce: Option<U256>,
676		access_list: Vec<(H160, Vec<H256>)>,
677		authorization_list: AuthorizationList,
678		is_transactional: bool,
679		validate: bool,
680		weight_limit: Option<Weight>,
681		proof_size_base_cost: Option<u64>,
682		config: &evm::Config,
683	) -> Result<CreateInfo, RunnerError<Self::Error>> {
684		let measured_proof_size_before = get_proof_size().unwrap_or_default();
685		let (_, weight) = T::FeeCalculator::min_gas_price();
686
687		T::CreateOriginFilter::check_create_origin(&source)
688			.map_err(|error| RunnerError { error, weight })?;
689
690		let authorization_list = authorization_list
691			.iter()
692			.map(|d| {
693				(
694					U256::from(d.chain_id),
695					d.address,
696					d.nonce,
697					d.authorizing_address().ok(),
698				)
699			})
700			.collect::<Vec<(U256, sp_core::H160, U256, Option<sp_core::H160>)>>();
701
702		if validate {
703			Self::validate(
704				source,
705				None,
706				init.clone(),
707				value,
708				gas_limit,
709				max_fee_per_gas,
710				max_priority_fee_per_gas,
711				nonce,
712				access_list.clone(),
713				authorization_list.clone(),
714				is_transactional,
715				weight_limit,
716				proof_size_base_cost,
717				config,
718			)?;
719		}
720
721		let precompiles = T::PrecompilesValue::get();
722		let code_hash = H256::from(sp_io::hashing::keccak_256(&init));
723		Self::execute(
724			source,
725			value,
726			gas_limit,
727			max_fee_per_gas,
728			max_priority_fee_per_gas,
729			config,
730			&precompiles,
731			is_transactional,
732			weight_limit,
733			proof_size_base_cost,
734			measured_proof_size_before,
735			|executor| {
736				let address = executor.create_address(evm::CreateScheme::Create2 {
737					caller: source,
738					code_hash,
739					salt,
740				});
741				T::OnCreate::on_create(source, address);
742				let (reason, _) = executor.transact_create2(
743					source,
744					value,
745					init,
746					salt,
747					gas_limit,
748					access_list,
749					authorization_list,
750				);
751				(reason, address)
752			},
753		)
754	}
755}
756
757struct SubstrateStackSubstate<'config> {
758	metadata: StackSubstateMetadata<'config>,
759	deletes: BTreeSet<H160>,
760	creates: BTreeSet<H160>,
761	logs: Vec<Log>,
762	parent: Option<Box<SubstrateStackSubstate<'config>>>,
763}
764
765impl<'config> SubstrateStackSubstate<'config> {
766	pub fn metadata(&self) -> &StackSubstateMetadata<'config> {
767		&self.metadata
768	}
769
770	pub fn metadata_mut(&mut self) -> &mut StackSubstateMetadata<'config> {
771		&mut self.metadata
772	}
773
774	pub fn enter(&mut self, gas_limit: u64, is_static: bool) {
775		let mut entering = Self {
776			metadata: self.metadata.spit_child(gas_limit, is_static),
777			parent: None,
778			deletes: BTreeSet::new(),
779			creates: BTreeSet::new(),
780			logs: Vec::new(),
781		};
782		mem::swap(&mut entering, self);
783
784		self.parent = Some(Box::new(entering));
785
786		sp_io::storage::start_transaction();
787	}
788
789	pub fn exit_commit(&mut self) -> Result<(), ExitError> {
790		let mut exited = *self.parent.take().expect("Cannot commit on root substate");
791		mem::swap(&mut exited, self);
792
793		self.metadata.swallow_commit(exited.metadata)?;
794		self.logs.append(&mut exited.logs);
795		self.deletes.append(&mut exited.deletes);
796		self.creates.append(&mut exited.creates);
797		sp_io::storage::commit_transaction();
798		Ok(())
799	}
800
801	pub fn exit_revert(&mut self) -> Result<(), ExitError> {
802		let mut exited = *self.parent.take().expect("Cannot discard on root substate");
803		mem::swap(&mut exited, self);
804		self.metadata.swallow_revert(exited.metadata)?;
805
806		sp_io::storage::rollback_transaction();
807		Ok(())
808	}
809
810	pub fn exit_discard(&mut self) -> Result<(), ExitError> {
811		let mut exited = *self.parent.take().expect("Cannot discard on root substate");
812		mem::swap(&mut exited, self);
813		self.metadata.swallow_discard(exited.metadata)?;
814
815		sp_io::storage::rollback_transaction();
816		Ok(())
817	}
818
819	pub fn deleted(&self, address: H160) -> bool {
820		if self.deletes.contains(&address) {
821			return true;
822		}
823
824		if let Some(parent) = self.parent.as_ref() {
825			return parent.deleted(address);
826		}
827
828		false
829	}
830
831	pub fn created(&self, address: H160) -> bool {
832		if self.creates.contains(&address) {
833			return true;
834		}
835
836		if let Some(parent) = self.parent.as_ref() {
837			return parent.created(address);
838		}
839
840		false
841	}
842
843	pub fn set_deleted(&mut self, address: H160) {
844		self.deletes.insert(address);
845	}
846
847	pub fn set_created(&mut self, address: H160) {
848		self.creates.insert(address);
849	}
850
851	pub fn log(&mut self, address: H160, topics: Vec<H256>, data: Vec<u8>) {
852		self.logs.push(Log {
853			address,
854			topics,
855			data,
856		});
857	}
858
859	fn recursive_is_cold<F: Fn(&Accessed) -> bool>(&self, f: &F) -> bool {
860		let local_is_accessed = self.metadata.accessed().as_ref().map(f).unwrap_or(false);
861		if local_is_accessed {
862			false
863		} else {
864			self.parent
865				.as_ref()
866				.map(|p| p.recursive_is_cold(f))
867				.unwrap_or(true)
868		}
869	}
870}
871
872#[derive(Default, Clone, Eq, PartialEq)]
873pub struct Recorded {
874	account_codes: Vec<H160>,
875	account_storages: BTreeMap<(H160, H256), bool>,
876}
877
878/// Substrate backend for EVM.
879pub struct SubstrateStackState<'vicinity, 'config, T> {
880	vicinity: &'vicinity Vicinity,
881	substate: SubstrateStackSubstate<'config>,
882	original_storage: BTreeMap<(H160, H256), H256>,
883	transient_storage: BTreeMap<(H160, H256), H256>,
884	recorded: Recorded,
885	weight_info: Option<WeightInfo>,
886	storage_meter: Option<StorageMeter>,
887	_marker: PhantomData<T>,
888}
889
890impl<'vicinity, 'config, T: Config> SubstrateStackState<'vicinity, 'config, T> {
891	/// Create a new backend with given vicinity.
892	pub fn new(
893		vicinity: &'vicinity Vicinity,
894		metadata: StackSubstateMetadata<'config>,
895		weight_info: Option<WeightInfo>,
896		storage_limit: Option<u64>,
897	) -> Self {
898		let storage_meter = storage_limit.map(StorageMeter::new);
899		Self {
900			vicinity,
901			substate: SubstrateStackSubstate {
902				metadata,
903				deletes: BTreeSet::new(),
904				creates: BTreeSet::new(),
905				logs: Vec::new(),
906				parent: None,
907			},
908			_marker: PhantomData,
909			original_storage: BTreeMap::new(),
910			transient_storage: BTreeMap::new(),
911			recorded: Default::default(),
912			weight_info,
913			storage_meter,
914		}
915	}
916
917	pub fn weight_info(&self) -> Option<WeightInfo> {
918		self.weight_info
919	}
920
921	pub fn recorded(&self) -> &Recorded {
922		&self.recorded
923	}
924
925	pub fn info_mut(&mut self) -> (&mut Option<WeightInfo>, &mut Recorded) {
926		(&mut self.weight_info, &mut self.recorded)
927	}
928
929	fn record_address_code_read(
930		address: H160,
931		weight_info: &mut WeightInfo,
932		recorded: &mut Recorded,
933		create_contract_limit: u64,
934	) -> Result<(), ExitError> {
935		let maybe_record = !recorded.account_codes.contains(&address);
936		// Skip if the address has been already recorded this block
937		if maybe_record {
938			// First we record account emptiness check.
939			// Transfers to EOAs with standard 21_000 gas limit are able to
940			// pay for this pov size.
941			weight_info.try_record_proof_size_or_fail(IS_EMPTY_CHECK_PROOF_SIZE)?;
942			if <AccountCodes<T>>::decode_len(address).unwrap_or(0) == 0 {
943				return Ok(());
944			}
945
946			weight_info.try_record_proof_size_or_fail(ACCOUNT_CODES_METADATA_PROOF_SIZE)?;
947			if let Some(meta) = <AccountCodesMetadata<T>>::get(address) {
948				weight_info.try_record_proof_size_or_fail(meta.size)?;
949			} else {
950				weight_info.try_record_proof_size_or_fail(create_contract_limit)?;
951
952				let actual_size = Pallet::<T>::account_code_metadata(address).size;
953				if actual_size > create_contract_limit {
954					fp_evm::set_storage_oog();
955					return Err(ExitError::OutOfGas);
956				}
957				// Refund unused proof size
958				weight_info.refund_proof_size(create_contract_limit.saturating_sub(actual_size));
959			}
960			recorded.account_codes.push(address);
961		}
962		Ok(())
963	}
964}
965
966impl<T: Config> BackendT for SubstrateStackState<'_, '_, T>
967where
968	BalanceOf<T>: TryFrom<U256> + Into<U256>,
969{
970	fn gas_price(&self) -> U256 {
971		self.vicinity.gas_price
972	}
973	fn origin(&self) -> H160 {
974		self.vicinity.origin
975	}
976
977	fn block_hash(&self, number: U256) -> H256 {
978		if number > U256::from(u32::MAX) {
979			H256::default()
980		} else {
981			T::BlockHashMapping::block_hash(number.as_u32())
982		}
983	}
984
985	fn block_number(&self) -> U256 {
986		let number: u128 = frame_system::Pallet::<T>::block_number().unique_saturated_into();
987		U256::from(number)
988	}
989
990	fn block_coinbase(&self) -> H160 {
991		Pallet::<T>::find_author()
992	}
993
994	fn block_timestamp(&self) -> U256 {
995		let now: u128 = T::Timestamp::now().unique_saturated_into();
996		U256::from(now / 1000)
997	}
998
999	fn block_difficulty(&self) -> U256 {
1000		U256::zero()
1001	}
1002
1003	fn block_randomness(&self) -> Option<H256> {
1004		None
1005	}
1006
1007	fn block_gas_limit(&self) -> U256 {
1008		T::BlockGasLimit::get()
1009	}
1010
1011	fn block_base_fee_per_gas(&self) -> U256 {
1012		let (base_fee, _) = T::FeeCalculator::min_gas_price();
1013		base_fee
1014	}
1015
1016	fn chain_id(&self) -> U256 {
1017		U256::from(T::ChainId::get())
1018	}
1019
1020	fn exists(&self, _address: H160) -> bool {
1021		true
1022	}
1023
1024	fn basic(&self, address: H160) -> evm::backend::Basic {
1025		let (account, _) = Pallet::<T>::account_basic(&address);
1026
1027		evm::backend::Basic {
1028			balance: account.balance,
1029			nonce: account.nonce,
1030		}
1031	}
1032
1033	fn code(&self, address: H160) -> Vec<u8> {
1034		<AccountCodes<T>>::get(address)
1035	}
1036
1037	fn storage(&self, address: H160, index: H256) -> H256 {
1038		<AccountStorages<T>>::get(address, index)
1039	}
1040
1041	fn transient_storage(&self, address: H160, index: H256) -> H256 {
1042		self.transient_storage
1043			.get(&(address, index))
1044			.copied()
1045			.unwrap_or_default()
1046	}
1047
1048	fn original_storage(&self, address: H160, index: H256) -> Option<H256> {
1049		Some(
1050			self.original_storage
1051				.get(&(address, index))
1052				.cloned()
1053				.unwrap_or_else(|| self.storage(address, index)),
1054		)
1055	}
1056}
1057
1058impl<'config, T: Config> StackStateT<'config> for SubstrateStackState<'_, 'config, T>
1059where
1060	BalanceOf<T>: TryFrom<U256> + Into<U256>,
1061{
1062	fn metadata(&self) -> &StackSubstateMetadata<'config> {
1063		self.substate.metadata()
1064	}
1065
1066	fn metadata_mut(&mut self) -> &mut StackSubstateMetadata<'config> {
1067		self.substate.metadata_mut()
1068	}
1069
1070	fn enter(&mut self, gas_limit: u64, is_static: bool) {
1071		self.substate.enter(gas_limit, is_static)
1072	}
1073
1074	fn exit_commit(&mut self) -> Result<(), ExitError> {
1075		self.substate.exit_commit()
1076	}
1077
1078	fn exit_revert(&mut self) -> Result<(), ExitError> {
1079		self.substate.exit_revert()
1080	}
1081
1082	fn exit_discard(&mut self) -> Result<(), ExitError> {
1083		self.substate.exit_discard()
1084	}
1085
1086	fn is_empty(&self, address: H160) -> bool {
1087		Pallet::<T>::is_account_empty(&address)
1088	}
1089
1090	fn deleted(&self, address: H160) -> bool {
1091		self.substate.deleted(address)
1092	}
1093
1094	fn created(&self, address: H160) -> bool {
1095		self.substate.created(address)
1096	}
1097
1098	fn inc_nonce(&mut self, address: H160) -> Result<(), ExitError> {
1099		let account_id = T::AddressMapping::into_account_id(address);
1100		T::AccountProvider::inc_account_nonce(&account_id);
1101		Ok(())
1102	}
1103
1104	fn set_storage(&mut self, address: H160, index: H256, value: H256) {
1105		// We cache the current value if this is the first time we modify it
1106		// in the transaction.
1107		use alloc::collections::btree_map::Entry::Vacant;
1108		if let Vacant(e) = self.original_storage.entry((address, index)) {
1109			let original = <AccountStorages<T>>::get(address, index);
1110			// No need to cache if same value.
1111			if original != value {
1112				e.insert(original);
1113			}
1114		}
1115
1116		// Then we insert or remove the entry based on the value.
1117		if value == H256::default() {
1118			log::debug!(
1119				target: "evm",
1120				"Removing storage for {address:?} [index: {index:?}]"
1121			);
1122			<AccountStorages<T>>::remove(address, index);
1123		} else {
1124			log::debug!(
1125				target: "evm",
1126				"Updating storage for {address:?} [index: {index:?}, value: {value:?}]"
1127			);
1128			<AccountStorages<T>>::insert(address, index, value);
1129		}
1130	}
1131
1132	fn set_transient_storage(&mut self, address: H160, key: H256, value: H256) {
1133		self.transient_storage.insert((address, key), value);
1134	}
1135
1136	fn reset_storage(&mut self, address: H160) {
1137		#[allow(deprecated)]
1138		let _ = <AccountStorages<T>>::remove_prefix(address, None);
1139	}
1140
1141	fn log(&mut self, address: H160, topics: Vec<H256>, data: Vec<u8>) {
1142		self.substate.log(address, topics, data)
1143	}
1144
1145	fn set_deleted(&mut self, address: H160) {
1146		self.substate.set_deleted(address)
1147	}
1148
1149	fn set_created(&mut self, address: H160) {
1150		self.substate.set_created(address);
1151	}
1152
1153	fn set_code(
1154		&mut self,
1155		address: H160,
1156		code: Vec<u8>,
1157		caller: Option<H160>,
1158	) -> Result<(), ExitError> {
1159		log::debug!(
1160			target: "evm",
1161			"Inserting code ({} bytes) at {:?}",
1162			code.len(),
1163			address
1164		);
1165
1166		Pallet::<T>::create_account(address, code, caller)
1167	}
1168
1169	fn set_delegation(
1170		&mut self,
1171		authority: H160,
1172		delegation: evm::delegation::Delegation,
1173	) -> Result<(), ExitError> {
1174		log::debug!(
1175			target: "evm",
1176			"Inserting delegation (23 bytes) at {:?}",
1177			delegation.address()
1178		);
1179
1180		let meta = crate::CodeMetadata::from_code(&delegation.to_bytes());
1181		<AccountCodesMetadata<T>>::insert(authority, meta);
1182		<AccountCodes<T>>::insert(authority, delegation.to_bytes());
1183		Ok(())
1184	}
1185
1186	fn reset_delegation(&mut self, address: H160) -> Result<(), ExitError> {
1187		log::debug!(
1188			target: "evm",
1189			"Resetting delegation at {address:?}"
1190		);
1191
1192		Pallet::<T>::remove_account_code(&address);
1193		Ok(())
1194	}
1195
1196	fn transfer(&mut self, transfer: Transfer) -> Result<(), ExitError> {
1197		let source = T::AddressMapping::into_account_id(transfer.source);
1198		let target = T::AddressMapping::into_account_id(transfer.target);
1199		T::Currency::transfer(
1200			&source,
1201			&target,
1202			transfer
1203				.value
1204				.try_into()
1205				.map_err(|_| ExitError::OutOfFund)?,
1206			ExistenceRequirement::AllowDeath,
1207		)
1208		.map_err(|_| ExitError::OutOfFund)
1209	}
1210
1211	fn reset_balance(&mut self, _address: H160) {
1212		// Do nothing on reset balance in Substrate.
1213		//
1214		// This function exists in EVM because a design issue
1215		// (arguably a bug) in SELFDESTRUCT that can cause total
1216		// issuance to be reduced. We do not need to replicate this.
1217	}
1218
1219	fn touch(&mut self, _address: H160) {
1220		// Do nothing on touch in Substrate.
1221		//
1222		// EVM pallet considers all accounts to exist, and distinguish
1223		// only empty and non-empty accounts. This avoids many of the
1224		// subtle issues in EIP-161.
1225	}
1226
1227	fn is_cold(&self, address: H160) -> bool {
1228		self.substate
1229			.recursive_is_cold(&|a| a.accessed_addresses.contains(&address))
1230	}
1231
1232	fn is_storage_cold(&self, address: H160, key: H256) -> bool {
1233		self.substate
1234			.recursive_is_cold(&|a: &Accessed| a.accessed_storage.contains(&(address, key)))
1235	}
1236
1237	fn code_size(&self, address: H160) -> U256 {
1238		// EIP-7702: EXTCODESIZE does NOT follow delegations
1239		// Return the actual code size at the address, including delegation designators
1240		U256::from(<Pallet<T>>::account_code_metadata(address).size)
1241	}
1242
1243	fn code_hash(&self, address: H160) -> H256 {
1244		// EIP-7702: EXTCODEHASH does NOT follow delegations
1245		// Return the hash of the actual code at the address, including delegation designators
1246		<Pallet<T>>::account_code_metadata(address).hash
1247	}
1248
1249	fn record_external_operation(&mut self, op: evm::ExternalOperation) -> Result<(), ExitError> {
1250		let size_limit: u64 = self
1251			.metadata()
1252			.gasometer()
1253			.config()
1254			.create_contract_limit
1255			.unwrap_or_default() as u64;
1256		let (weight_info, recorded) = self.info_mut();
1257
1258		if let Some(weight_info) = weight_info {
1259			match op {
1260				ExternalOperation::AccountBasicRead => {
1261					weight_info.try_record_proof_size_or_fail(ACCOUNT_BASIC_PROOF_SIZE)?
1262				}
1263				ExternalOperation::AddressCodeRead(address) => {
1264					Self::record_address_code_read(address, weight_info, recorded, size_limit)?;
1265				}
1266				ExternalOperation::IsEmpty => {
1267					weight_info.try_record_proof_size_or_fail(IS_EMPTY_CHECK_PROOF_SIZE)?
1268				}
1269				ExternalOperation::Write(len) => {
1270					weight_info.try_record_proof_size_or_fail(WRITE_PROOF_SIZE)?;
1271
1272					if let Some(storage_meter) = self.storage_meter.as_mut() {
1273						// Record the number of bytes written to storage when deploying a contract.
1274						let storage_growth = ACCOUNT_CODES_KEY_SIZE
1275							.saturating_add(ACCOUNT_CODES_METADATA_PROOF_SIZE)
1276							.saturating_add(len.as_u64());
1277						storage_meter
1278							.record(storage_growth)
1279							.map_err(|_| ExitError::OutOfGas)?;
1280					}
1281				}
1282				ExternalOperation::DelegationResolution(address) => {
1283					Self::record_address_code_read(address, weight_info, recorded, size_limit)?;
1284				}
1285			};
1286		}
1287		Ok(())
1288	}
1289
1290	fn record_external_dynamic_opcode_cost(
1291		&mut self,
1292		opcode: Opcode,
1293		gas_cost: GasCost,
1294		target: evm::gasometer::StorageTarget,
1295	) -> Result<(), ExitError> {
1296		if let Some(storage_meter) = self.storage_meter.as_mut() {
1297			storage_meter
1298				.record_dynamic_opcode_cost(opcode, gas_cost, target)
1299				.map_err(|_| ExitError::OutOfGas)?;
1300		}
1301
1302		// If account code or storage slot is in the overlay it is already accounted for and early exit
1303		let accessed_storage: Option<AccessedStorage> = match target {
1304			StorageTarget::Address(address) => {
1305				if self.recorded().account_codes.contains(&address) {
1306					return Ok(());
1307				} else {
1308					Some(AccessedStorage::AccountCodes(address))
1309				}
1310			}
1311			StorageTarget::Slot(address, index) => {
1312				if self
1313					.recorded()
1314					.account_storages
1315					.contains_key(&(address, index))
1316				{
1317					return Ok(());
1318				} else {
1319					Some(AccessedStorage::AccountStorages((address, index)))
1320				}
1321			}
1322			_ => None,
1323		};
1324
1325		let size_limit: u64 = self
1326			.metadata()
1327			.gasometer()
1328			.config()
1329			.create_contract_limit
1330			.unwrap_or_default() as u64;
1331		let (weight_info, recorded) = self.info_mut();
1332
1333		if let Some(weight_info) = weight_info {
1334			// proof_size_limit is None indicates no need to record proof size, return directly.
1335			if weight_info.proof_size_limit.is_none() {
1336				return Ok(());
1337			};
1338
1339			let mut record_account_codes_proof_size =
1340				|address: H160, empty_check: bool| -> Result<(), ExitError> {
1341					let mut base_size = ACCOUNT_CODES_METADATA_PROOF_SIZE;
1342					if empty_check {
1343						base_size = base_size.saturating_add(IS_EMPTY_CHECK_PROOF_SIZE);
1344					}
1345					weight_info.try_record_proof_size_or_fail(base_size)?;
1346
1347					if let Some(meta) = <AccountCodesMetadata<T>>::get(address) {
1348						weight_info.try_record_proof_size_or_fail(meta.size)?;
1349					} else if let Some(remaining_proof_size) = weight_info.remaining_proof_size() {
1350						let pre_size = remaining_proof_size.min(size_limit);
1351						weight_info.try_record_proof_size_or_fail(pre_size)?;
1352
1353						let actual_size = Pallet::<T>::account_code_metadata(address).size;
1354						if actual_size > pre_size {
1355							return Err(ExitError::OutOfGas);
1356						}
1357						// Refund unused proof size
1358						weight_info.refund_proof_size(pre_size.saturating_sub(actual_size));
1359					}
1360
1361					Ok(())
1362				};
1363
1364			// Proof size is fixed length for writes (a 32-byte hash in a merkle trie), and
1365			// the full key/value for reads. For read and writes over the same storage, the full value
1366			// is included.
1367			// For cold reads involving code (call, callcode, staticcall and delegatecall):
1368			//	- We depend on https://github.com/paritytech/frontier/pull/893
1369			//	- Try to get the cached size or compute it on the fly
1370			//	- We record the actual size after caching, refunding the difference between it and the initially deducted
1371			//	contract size limit.
1372			match opcode {
1373				Opcode::BALANCE => {
1374					weight_info.try_record_proof_size_or_fail(ACCOUNT_BASIC_PROOF_SIZE)?;
1375				}
1376				Opcode::EXTCODESIZE | Opcode::EXTCODECOPY | Opcode::EXTCODEHASH => {
1377					if let Some(AccessedStorage::AccountCodes(address)) = accessed_storage {
1378						record_account_codes_proof_size(address, false)?;
1379						recorded.account_codes.push(address);
1380					}
1381				}
1382				Opcode::CALLCODE | Opcode::CALL | Opcode::DELEGATECALL | Opcode::STATICCALL => {
1383					if let Some(AccessedStorage::AccountCodes(address)) = accessed_storage {
1384						record_account_codes_proof_size(address, true)?;
1385						recorded.account_codes.push(address);
1386					}
1387				}
1388				Opcode::SLOAD => {
1389					if let Some(AccessedStorage::AccountStorages((address, index))) =
1390						accessed_storage
1391					{
1392						weight_info.try_record_proof_size_or_fail(ACCOUNT_STORAGE_PROOF_SIZE)?;
1393						recorded.account_storages.insert((address, index), true);
1394					}
1395				}
1396				Opcode::SSTORE => {
1397					if let Some(AccessedStorage::AccountStorages((address, index))) =
1398						accessed_storage
1399					{
1400						let size = WRITE_PROOF_SIZE.saturating_add(ACCOUNT_STORAGE_PROOF_SIZE);
1401						weight_info.try_record_proof_size_or_fail(size)?;
1402						recorded.account_storages.insert((address, index), true);
1403					}
1404				}
1405				Opcode::CREATE | Opcode::CREATE2 => {
1406					weight_info.try_record_proof_size_or_fail(WRITE_PROOF_SIZE)?;
1407				}
1408				// When calling SUICIDE a target account will receive the self destructing
1409				// address's balance. We need to account for both:
1410				//	- Target basic account read
1411				//	- 5 bytes of `decode_len`
1412				Opcode::SUICIDE => {
1413					weight_info.try_record_proof_size_or_fail(IS_EMPTY_CHECK_PROOF_SIZE)?;
1414				}
1415				// Rest of dynamic opcodes that do not involve proof size recording, do nothing
1416				_ => return Ok(()),
1417			};
1418		}
1419
1420		Ok(())
1421	}
1422
1423	fn record_external_cost(
1424		&mut self,
1425		ref_time: Option<u64>,
1426		proof_size: Option<u64>,
1427		storage_growth: Option<u64>,
1428	) -> Result<(), ExitError> {
1429		let weight_info = if let (Some(weight_info), _) = self.info_mut() {
1430			weight_info
1431		} else {
1432			return Ok(());
1433		};
1434
1435		if let Some(amount) = ref_time {
1436			weight_info.try_record_ref_time_or_fail(amount)?;
1437		}
1438		if let Some(amount) = proof_size {
1439			weight_info.try_record_proof_size_or_fail(amount)?;
1440		}
1441		if let Some(storage_meter) = self.storage_meter.as_mut() {
1442			if let Some(amount) = storage_growth {
1443				storage_meter
1444					.record(amount)
1445					.map_err(|_| ExitError::OutOfGas)?;
1446			}
1447		}
1448		Ok(())
1449	}
1450
1451	fn refund_external_cost(&mut self, ref_time: Option<u64>, proof_size: Option<u64>) {
1452		if let Some(weight_info) = self.weight_info.as_mut() {
1453			if let Some(amount) = ref_time {
1454				weight_info.refund_ref_time(amount);
1455			}
1456			if let Some(amount) = proof_size {
1457				weight_info.refund_proof_size(amount);
1458			}
1459		}
1460	}
1461}
1462
1463#[cfg(feature = "forbid-evm-reentrancy")]
1464#[cfg(test)]
1465mod tests {
1466	use super::*;
1467	use crate::mock::{MockPrecompileSet, Test};
1468	use evm::ExitSucceed;
1469	use sp_io::TestExternalities;
1470
1471	macro_rules! assert_matches {
1472		( $left:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )? $(,)? ) => {
1473			match $left {
1474				$( $pattern )|+ $( if $guard )? => {}
1475				ref left_val => panic!("assertion failed: `{:?}` does not match `{}`",
1476					left_val, stringify!($($pattern)|+ $(if $guard)?))
1477			}
1478		};
1479		( $left:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )?, $($arg:tt)+ ) => {
1480			match $left {
1481				$( $pattern )|+ $( if $guard )? => {}
1482				ref left_val => panic!("assertion failed: `{:?}` does not match `{}`",
1483					left_val, stringify!($($pattern)|+ $(if $guard)?))
1484			}
1485		};
1486	}
1487
1488	#[test]
1489	fn test_evm_reentrancy() {
1490		TestExternalities::new_empty().execute_with(|| {
1491			let config = evm::Config::istanbul();
1492
1493			let measured_proof_size_before = get_proof_size().unwrap_or_default();
1494			// Should fail with the appropriate error if there is reentrancy
1495			let res = Runner::<Test>::execute(
1496				H160::default(),
1497				U256::default(),
1498				100_000,
1499				None,
1500				None,
1501				&config,
1502				&MockPrecompileSet,
1503				false,
1504				None,
1505				None,
1506				measured_proof_size_before,
1507				|_| {
1508					let measured_proof_size_before2 = get_proof_size().unwrap_or_default();
1509					let res = Runner::<Test>::execute(
1510						H160::default(),
1511						U256::default(),
1512						100_000,
1513						None,
1514						None,
1515						&config,
1516						&MockPrecompileSet,
1517						false,
1518						None,
1519						None,
1520						measured_proof_size_before2,
1521						|_| (ExitReason::Succeed(ExitSucceed::Stopped), ()),
1522					);
1523					assert_matches!(
1524						res,
1525						Err(RunnerError {
1526							error: Error::<Test>::Reentrancy,
1527							..
1528						})
1529					);
1530					(ExitReason::Error(ExitError::CallTooDeep), ())
1531				},
1532			);
1533			assert_matches!(
1534				res,
1535				Ok(ExecutionInfoV2 {
1536					exit_reason: ExitReason::Error(ExitError::CallTooDeep),
1537					..
1538				})
1539			);
1540
1541			let measured_proof_size_before = get_proof_size().unwrap_or_default();
1542			// Should succeed if there is no reentrancy
1543			let res = Runner::<Test>::execute(
1544				H160::default(),
1545				U256::default(),
1546				100_000,
1547				None,
1548				None,
1549				&config,
1550				&MockPrecompileSet,
1551				false,
1552				None,
1553				None,
1554				measured_proof_size_before,
1555				|_| (ExitReason::Succeed(ExitSucceed::Stopped), ()),
1556			);
1557			assert!(res.is_ok());
1558		});
1559	}
1560}