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