pallet_ethereum/
lib.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//! # Ethereum pallet
19//!
20//! The Ethereum pallet works together with EVM pallet to provide full emulation
21//! for Ethereum block processing.
22
23// Ensure we're `no_std` when compiling for Wasm.
24#![cfg_attr(not(feature = "std"), no_std)]
25#![allow(
26	clippy::comparison_chain,
27	clippy::large_enum_variant,
28	clippy::useless_conversion
29)]
30#![warn(unused_crate_dependencies)]
31
32extern crate alloc;
33
34#[cfg(all(feature = "std", test))]
35mod mock;
36#[cfg(all(feature = "std", test))]
37mod tests;
38
39use alloc::{vec, vec::Vec};
40use core::marker::PhantomData;
41pub use ethereum::{
42	AccessListItem, BlockV3 as Block, LegacyTransactionMessage, Log, ReceiptV4 as Receipt,
43	TransactionAction, TransactionV3 as Transaction,
44};
45use ethereum_types::{Bloom, BloomInput, H160, H256, H64, U256};
46use evm::ExitReason;
47use scale_codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
48use scale_info::TypeInfo;
49// Substrate
50use frame_support::{
51	dispatch::{
52		DispatchErrorWithPostInfo, DispatchInfo, DispatchResultWithPostInfo, Pays, PostDispatchInfo,
53	},
54	traits::{EnsureOrigin, Get, Time},
55	weights::Weight,
56};
57use frame_system::{pallet_prelude::OriginFor, CheckWeight, WeightInfo};
58use sp_runtime::{
59	generic::DigestItem,
60	traits::{DispatchInfoOf, Dispatchable, One, Saturating, UniqueSaturatedInto, Zero},
61	transaction_validity::{
62		InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransactionBuilder,
63	},
64	RuntimeDebug, SaturatedConversion,
65};
66use sp_version::RuntimeVersion;
67// Frontier
68use fp_consensus::{PostLog, PreLog, FRONTIER_ENGINE_ID};
69pub use fp_ethereum::TransactionData;
70use fp_ethereum::ValidatedTransaction as ValidatedTransactionT;
71use fp_evm::{
72	CallOrCreateInfo, CheckEvmTransaction, CheckEvmTransactionConfig, TransactionValidationError,
73};
74pub use fp_rpc::TransactionStatus;
75use fp_storage::{EthereumStorageSchema, PALLET_ETHEREUM_SCHEMA};
76use frame_support::traits::PalletInfoAccess;
77use pallet_evm::{BlockHashMapping, FeeCalculator, GasWeightMapping, Runner};
78
79#[derive(Clone, Eq, PartialEq, RuntimeDebug)]
80#[derive(Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo)]
81pub enum RawOrigin {
82	EthereumTransaction(H160),
83}
84
85pub fn ensure_ethereum_transaction<OuterOrigin>(o: OuterOrigin) -> Result<H160, &'static str>
86where
87	OuterOrigin: Into<Result<RawOrigin, OuterOrigin>>,
88{
89	match o.into() {
90		Ok(RawOrigin::EthereumTransaction(n)) => Ok(n),
91		_ => Err("bad origin: expected to be an Ethereum transaction"),
92	}
93}
94
95pub struct EnsureEthereumTransaction;
96impl<O: Into<Result<RawOrigin, O>> + From<RawOrigin>> EnsureOrigin<O>
97	for EnsureEthereumTransaction
98{
99	type Success = H160;
100	fn try_origin(o: O) -> Result<Self::Success, O> {
101		o.into().map(|o| match o {
102			RawOrigin::EthereumTransaction(id) => id,
103		})
104	}
105
106	#[cfg(feature = "runtime-benchmarks")]
107	fn try_successful_origin() -> Result<O, ()> {
108		Ok(O::from(RawOrigin::EthereumTransaction(Default::default())))
109	}
110}
111
112impl<T> Call<T>
113where
114	OriginFor<T>: Into<Result<RawOrigin, OriginFor<T>>>,
115	T: Send + Sync + Config,
116	T::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
117{
118	pub fn is_self_contained(&self) -> bool {
119		matches!(self, Call::transact { .. })
120	}
121
122	pub fn check_self_contained(&self) -> Option<Result<H160, TransactionValidityError>> {
123		if let Call::transact { transaction } = self {
124			let check = || {
125				let origin = Pallet::<T>::recover_signer(transaction).ok_or(
126					InvalidTransaction::Custom(TransactionValidationError::InvalidSignature as u8),
127				)?;
128
129				Ok(origin)
130			};
131
132			Some(check())
133		} else {
134			None
135		}
136	}
137
138	pub fn pre_dispatch_self_contained(
139		&self,
140		origin: &H160,
141		dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
142		len: usize,
143	) -> Option<Result<(), TransactionValidityError>> {
144		if let Call::transact { transaction } = self {
145			if let Err(e) =
146				CheckWeight::<T>::do_validate(dispatch_info, len).and_then(|(_, next_len)| {
147					CheckWeight::<T>::do_prepare(dispatch_info, len, next_len)
148				}) {
149				return Some(Err(e));
150			}
151
152			Some(Pallet::<T>::validate_transaction_in_block(
153				*origin,
154				transaction,
155			))
156		} else {
157			None
158		}
159	}
160
161	pub fn validate_self_contained(
162		&self,
163		origin: &H160,
164		dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
165		len: usize,
166	) -> Option<TransactionValidity> {
167		if let Call::transact { transaction } = self {
168			if let Err(e) = CheckWeight::<T>::do_validate(dispatch_info, len) {
169				return Some(Err(e));
170			}
171
172			Some(Pallet::<T>::validate_transaction_in_pool(
173				*origin,
174				transaction,
175			))
176		} else {
177			None
178		}
179	}
180}
181
182#[derive(Copy, Clone, Eq, PartialEq, Default)]
183pub enum PostLogContent {
184	#[default]
185	BlockAndTxnHashes,
186	OnlyBlockHash,
187}
188
189pub use self::pallet::*;
190
191#[frame_support::pallet]
192pub mod pallet {
193	use super::*;
194	use frame_support::pallet_prelude::*;
195	use frame_system::pallet_prelude::*;
196
197	#[pallet::pallet]
198	#[pallet::without_storage_info]
199	pub struct Pallet<T>(PhantomData<T>);
200
201	#[pallet::origin]
202	pub type Origin = RawOrigin;
203
204	#[pallet::config(with_default)]
205	pub trait Config: frame_system::Config + pallet_evm::Config {
206		/// How Ethereum state root is calculated.
207		type StateRoot: Get<H256>;
208		/// What's included in the PostLog.
209		type PostLogContent: Get<PostLogContent>;
210		/// The maximum length of the extra data in the Executed event.
211		type ExtraDataLength: Get<u32>;
212	}
213
214	pub mod config_preludes {
215		use super::*;
216		use frame_support::{derive_impl, parameter_types};
217
218		pub struct TestDefaultConfig;
219
220		#[derive_impl(frame_system::config_preludes::TestDefaultConfig, no_aggregated_types)]
221		impl frame_system::DefaultConfig for TestDefaultConfig {}
222
223		#[derive_impl(pallet_evm::config_preludes::TestDefaultConfig, no_aggregated_types)]
224		impl pallet_evm::DefaultConfig for TestDefaultConfig {}
225
226		parameter_types! {
227			pub const PostBlockAndTxnHashes: PostLogContent = PostLogContent::BlockAndTxnHashes;
228		}
229
230		#[register_default_impl(TestDefaultConfig)]
231		impl DefaultConfig for TestDefaultConfig {
232			type StateRoot = IntermediateStateRoot<Self::Version>;
233			type PostLogContent = PostBlockAndTxnHashes;
234			type ExtraDataLength = ConstU32<30>;
235		}
236	}
237
238	#[pallet::hooks]
239	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
240		fn on_finalize(n: BlockNumberFor<T>) {
241			<Pallet<T>>::store_block(
242				match fp_consensus::find_pre_log(&frame_system::Pallet::<T>::digest()) {
243					Ok(_) => None,
244					Err(_) => Some(T::PostLogContent::get()),
245				},
246				U256::from(UniqueSaturatedInto::<u128>::unique_saturated_into(
247					frame_system::Pallet::<T>::block_number(),
248				)),
249			);
250			// move block hash pruning window by one block
251			let block_hash_count = T::BlockHashCount::get();
252			let to_remove = n
253				.saturating_sub(block_hash_count)
254				.saturating_sub(One::one());
255			// keep genesis hash
256			if !to_remove.is_zero() {
257				<BlockHash<T>>::remove(U256::from(
258					UniqueSaturatedInto::<u32>::unique_saturated_into(to_remove),
259				));
260			}
261		}
262
263		fn on_initialize(_: BlockNumberFor<T>) -> Weight {
264			let mut weight = T::SystemWeightInfo::kill_storage(1);
265
266			// If the digest contain an existing ethereum block(encoded as PreLog), If contains,
267			// execute the imported block firstly and disable transact dispatch function.
268			if let Ok(log) = fp_consensus::find_pre_log(&frame_system::Pallet::<T>::digest()) {
269				let PreLog::Block(block) = log;
270
271				for transaction in block.transactions {
272					let source = Self::recover_signer(&transaction).expect(
273						"pre-block transaction signature invalid; the block cannot be built",
274					);
275
276					Self::validate_transaction_in_block(source, &transaction).expect(
277						"pre-block transaction verification failed; the block cannot be built",
278					);
279					let (r, _) = Self::apply_validated_transaction(source, transaction, None)
280						.expect("pre-block apply transaction failed; the block cannot be built");
281
282					weight = weight.saturating_add(r.actual_weight.unwrap_or_default());
283				}
284			}
285			// Account for `on_finalize` weight:
286			//	- read: frame_system::Pallet::<T>::digest()
287			//	- read: frame_system::Pallet::<T>::block_number()
288			//	- write: <Pallet<T>>::store_block()
289			//	- write: <BlockHash<T>>::remove()
290			weight.saturating_add(T::DbWeight::get().reads_writes(2, 2))
291		}
292
293		fn on_runtime_upgrade() -> Weight {
294			frame_support::storage::unhashed::put::<EthereumStorageSchema>(
295				PALLET_ETHEREUM_SCHEMA,
296				&EthereumStorageSchema::V3,
297			);
298
299			T::DbWeight::get().writes(1)
300		}
301	}
302
303	#[pallet::call]
304	impl<T: Config> Pallet<T>
305	where
306		OriginFor<T>: Into<Result<RawOrigin, OriginFor<T>>>,
307	{
308		/// Transact an Ethereum transaction.
309		#[pallet::call_index(0)]
310		#[pallet::weight({
311			let without_base_extrinsic_weight = true;
312			<T as pallet_evm::Config>::GasWeightMapping::gas_to_weight({
313				let transaction_data: TransactionData = transaction.into();
314				transaction_data.gas_limit.unique_saturated_into()
315			}, without_base_extrinsic_weight)
316		})]
317		pub fn transact(
318			origin: OriginFor<T>,
319			transaction: Transaction,
320		) -> DispatchResultWithPostInfo {
321			let source = ensure_ethereum_transaction(origin)?;
322			// Disable transact functionality if PreLog exist.
323			assert!(
324				fp_consensus::find_pre_log(&frame_system::Pallet::<T>::digest()).is_err(),
325				"pre log already exists; block is invalid",
326			);
327
328			Self::apply_validated_transaction(source, transaction, None)
329				.map(|(post_info, _)| post_info)
330		}
331	}
332
333	#[pallet::event]
334	#[pallet::generate_deposit(pub(super) fn deposit_event)]
335	pub enum Event {
336		/// An ethereum transaction was successfully executed.
337		Executed {
338			from: H160,
339			to: H160,
340			transaction_hash: H256,
341			exit_reason: ExitReason,
342			extra_data: Vec<u8>,
343		},
344	}
345
346	#[pallet::error]
347	pub enum Error<T> {
348		/// Signature is invalid.
349		InvalidSignature,
350		/// Pre-log is present, therefore transact is not allowed.
351		PreLogExists,
352	}
353
354	/// Mapping from transaction index to transaction in the current building block.
355	#[pallet::storage]
356	pub type Pending<T: Config> =
357		CountedStorageMap<_, Identity, u32, (Transaction, TransactionStatus, Receipt), OptionQuery>;
358
359	/// The current Ethereum block.
360	#[pallet::storage]
361	pub type CurrentBlock<T: Config> = StorageValue<_, ethereum::BlockV3>;
362
363	/// The current Ethereum receipts.
364	#[pallet::storage]
365	pub type CurrentReceipts<T: Config> = StorageValue<_, Vec<Receipt>>;
366
367	/// The current transaction statuses.
368	#[pallet::storage]
369	pub type CurrentTransactionStatuses<T: Config> = StorageValue<_, Vec<TransactionStatus>>;
370
371	// Mapping for block number and hashes.
372	#[pallet::storage]
373	pub type BlockHash<T: Config> = StorageMap<_, Twox64Concat, U256, H256, ValueQuery>;
374
375	#[pallet::genesis_config]
376	#[derive(frame_support::DefaultNoBound)]
377	pub struct GenesisConfig<T> {
378		#[serde(skip)]
379		pub _marker: PhantomData<T>,
380	}
381
382	#[pallet::genesis_build]
383	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
384		fn build(&self) {
385			<Pallet<T>>::store_block(None, U256::zero());
386			frame_support::storage::unhashed::put::<EthereumStorageSchema>(
387				PALLET_ETHEREUM_SCHEMA,
388				&EthereumStorageSchema::V3,
389			);
390		}
391	}
392}
393
394impl<T: Config> Pallet<T> {
395	pub fn transaction_weight(transaction_data: &TransactionData) -> (Option<Weight>, Option<u64>) {
396		match <T as pallet_evm::Config>::GasWeightMapping::gas_to_weight(
397			transaction_data.gas_limit.unique_saturated_into(),
398			true,
399		) {
400			weight_limit if weight_limit.proof_size() > 0 => (
401				Some(weight_limit),
402				Some(transaction_data.proof_size_base_cost()),
403			),
404			_ => (None, None),
405		}
406	}
407
408	fn recover_signer(transaction: &Transaction) -> Option<H160> {
409		let mut sig = [0u8; 65];
410		let mut msg = [0u8; 32];
411		match transaction {
412			Transaction::Legacy(t) => {
413				sig[0..32].copy_from_slice(&t.signature.r()[..]);
414				sig[32..64].copy_from_slice(&t.signature.s()[..]);
415				sig[64] = t.signature.standard_v();
416				msg.copy_from_slice(
417					&ethereum::LegacyTransactionMessage::from(t.clone()).hash()[..],
418				);
419			}
420			Transaction::EIP2930(t) => {
421				sig[0..32].copy_from_slice(&t.signature.r()[..]);
422				sig[32..64].copy_from_slice(&t.signature.s()[..]);
423				sig[64] = t.signature.odd_y_parity() as u8;
424				msg.copy_from_slice(
425					&ethereum::EIP2930TransactionMessage::from(t.clone()).hash()[..],
426				);
427			}
428			Transaction::EIP1559(t) => {
429				sig[0..32].copy_from_slice(&t.signature.r()[..]);
430				sig[32..64].copy_from_slice(&t.signature.s()[..]);
431				sig[64] = t.signature.odd_y_parity() as u8;
432				msg.copy_from_slice(
433					&ethereum::EIP1559TransactionMessage::from(t.clone()).hash()[..],
434				);
435			}
436			Transaction::EIP7702(t) => {
437				sig[0..32].copy_from_slice(&t.signature.r()[..]);
438				sig[32..64].copy_from_slice(&t.signature.s()[..]);
439				sig[64] = t.signature.odd_y_parity() as u8;
440				msg.copy_from_slice(
441					&ethereum::EIP7702TransactionMessage::from(t.clone()).hash()[..],
442				);
443			}
444		}
445		let pubkey = sp_io::crypto::secp256k1_ecdsa_recover(&sig, &msg).ok()?;
446		Some(H160::from(H256::from(sp_io::hashing::keccak_256(&pubkey))))
447	}
448
449	fn store_block(post_log: Option<PostLogContent>, block_number: U256) {
450		let transactions_count = Pending::<T>::count();
451		let mut transactions = Vec::with_capacity(transactions_count as usize);
452		let mut statuses = Vec::with_capacity(transactions_count as usize);
453		let mut receipts = Vec::with_capacity(transactions_count as usize);
454		let mut logs_bloom = Bloom::default();
455		let mut cumulative_gas_used = U256::zero();
456		for transaction_index in 0..transactions_count {
457			if let Some((transaction, status, receipt)) = Pending::<T>::take(transaction_index) {
458				transactions.push(transaction);
459				statuses.push(status);
460				receipts.push(receipt.clone());
461				let (logs, used_gas) = match receipt {
462					Receipt::Legacy(d) | Receipt::EIP2930(d) | Receipt::EIP1559(d) => {
463						(d.logs.clone(), d.used_gas)
464					}
465					Receipt::EIP7702(d) => (d.logs.clone(), d.used_gas),
466				};
467				cumulative_gas_used = used_gas;
468				Self::logs_bloom(logs, &mut logs_bloom);
469			}
470		}
471
472		let ommers = Vec::<ethereum::Header>::new();
473		let receipts_root = ethereum::util::ordered_trie_root(
474			receipts.iter().map(ethereum::EnvelopedEncodable::encode),
475		);
476		let partial_header = ethereum::PartialHeader {
477			parent_hash: if block_number > U256::zero() {
478				BlockHash::<T>::get(block_number - 1)
479			} else {
480				H256::default()
481			},
482			beneficiary: pallet_evm::Pallet::<T>::find_author(),
483			state_root: T::StateRoot::get(),
484			receipts_root,
485			logs_bloom,
486			difficulty: U256::zero(),
487			number: block_number,
488			gas_limit: T::BlockGasLimit::get(),
489			gas_used: cumulative_gas_used,
490			timestamp: T::Timestamp::now().unique_saturated_into(),
491			extra_data: Vec::new(),
492			mix_hash: H256::default(),
493			nonce: H64::default(),
494		};
495		let block = ethereum::Block::new(partial_header, transactions.clone(), ommers);
496
497		CurrentBlock::<T>::put(block.clone());
498		CurrentReceipts::<T>::put(receipts.clone());
499		CurrentTransactionStatuses::<T>::put(statuses.clone());
500		BlockHash::<T>::insert(block_number, block.header.hash());
501
502		match post_log {
503			Some(PostLogContent::BlockAndTxnHashes) => {
504				let digest = DigestItem::Consensus(
505					FRONTIER_ENGINE_ID,
506					PostLog::Hashes(fp_consensus::Hashes::from_block(block)).encode(),
507				);
508				frame_system::Pallet::<T>::deposit_log(digest);
509			}
510			Some(PostLogContent::OnlyBlockHash) => {
511				let digest = DigestItem::Consensus(
512					FRONTIER_ENGINE_ID,
513					PostLog::BlockHash(block.header.hash()).encode(),
514				);
515				frame_system::Pallet::<T>::deposit_log(digest);
516			}
517			None => { /* do nothing*/ }
518		}
519	}
520
521	fn logs_bloom(logs: Vec<Log>, bloom: &mut Bloom) {
522		for log in logs {
523			bloom.accrue(BloomInput::Raw(&log.address[..]));
524			for topic in log.topics {
525				bloom.accrue(BloomInput::Raw(&topic[..]));
526			}
527		}
528	}
529
530	// Controls that must be performed by the pool.
531	// The controls common with the State Transition Function (STF) are in
532	// the function `validate_transaction_common`.
533	fn validate_transaction_in_pool(
534		origin: H160,
535		transaction: &Transaction,
536	) -> TransactionValidity {
537		let transaction_data: TransactionData = transaction.into();
538		let transaction_nonce = transaction_data.nonce;
539		let (weight_limit, proof_size_base_cost) = Self::transaction_weight(&transaction_data);
540		let (base_fee, _) = T::FeeCalculator::min_gas_price();
541		let (who, _) = pallet_evm::Pallet::<T>::account_basic(&origin);
542
543		// Check if this is an EIP-7702 transaction
544		let is_eip7702 = matches!(transaction, Transaction::EIP7702(_));
545
546		let _ = CheckEvmTransaction::<InvalidTransactionWrapper>::new(
547			CheckEvmTransactionConfig {
548				evm_config: T::config(),
549				block_gas_limit: T::BlockGasLimit::get(),
550				base_fee,
551				chain_id: T::ChainId::get(),
552				is_transactional: true,
553			},
554			transaction_data.clone().into(),
555			weight_limit,
556			proof_size_base_cost,
557		)
558		.validate_in_pool_for(&who)
559		.and_then(|v| v.with_chain_id())
560		.and_then(|v| v.with_base_fee())
561		.and_then(|v| v.with_balance_for(&who))
562		.and_then(|v| v.with_eip7702_authorization_list(is_eip7702))
563		.map_err(|e| e.0)?;
564
565		// EIP-3607: https://eips.ethereum.org/EIPS/eip-3607
566		// Do not allow transactions for which `tx.sender` has any code deployed.
567		// Exception: Allow transactions from EOAs whose code is a valid delegation indicator (0xef0100 || address).
568		//
569		// This check should be done on the transaction validation (here) **and**
570		// on transaction execution, otherwise a contract tx will be included in
571		// the mempool and pollute the mempool forever.
572		if let Some(metadata) = pallet_evm::AccountCodesMetadata::<T>::get(origin) {
573			if metadata.size > 0 {
574				// Account has code, check if it's a valid delegation
575				let is_delegation = metadata.size
576					== evm::delegation::EIP_7702_DELEGATION_SIZE as u64
577					&& pallet_evm::AccountCodes::<T>::get(origin)
578						.starts_with(evm::delegation::EIP_7702_DELEGATION_PREFIX);
579
580				if !is_delegation {
581					return Err(InvalidTransaction::BadSigner.into());
582				}
583			}
584		}
585
586		let priority = match (
587			transaction_data.gas_price,
588			transaction_data.max_fee_per_gas,
589			transaction_data.max_priority_fee_per_gas,
590		) {
591			// Legacy or EIP-2930 transaction.
592			// Handle priority here. On legacy transaction everything in gas_price except
593			// the current base_fee is considered a tip to the miner and thus the priority.
594			(Some(gas_price), None, None) => {
595				gas_price.saturating_sub(base_fee).unique_saturated_into()
596			}
597			// EIP-1559 transaction without tip.
598			(None, Some(_), None) => 0,
599			// EIP-1559 transaction with tip.
600			(None, Some(max_fee_per_gas), Some(max_priority_fee_per_gas)) => max_fee_per_gas
601				.saturating_sub(base_fee)
602				.min(max_priority_fee_per_gas)
603				.unique_saturated_into(),
604			// Unreachable because already validated. Gracefully handle.
605			_ => return Err(InvalidTransaction::Payment.into()),
606		};
607
608		// The tag provides and requires must be filled correctly according to the nonce.
609		let mut builder = ValidTransactionBuilder::default()
610			.and_provides((origin, transaction_nonce))
611			.priority(priority);
612
613		// In the context of the pool, a transaction with
614		// too high a nonce is still considered valid
615		if transaction_nonce > who.nonce {
616			if let Some(prev_nonce) = transaction_nonce.checked_sub(1.into()) {
617				builder = builder.and_requires((origin, prev_nonce))
618			}
619		}
620
621		builder.build()
622	}
623
624	fn apply_validated_transaction(
625		source: H160,
626		transaction: Transaction,
627		maybe_force_create_address: Option<H160>,
628	) -> Result<(PostDispatchInfo, CallOrCreateInfo), DispatchErrorWithPostInfo> {
629		let (to, _, info) = Self::execute(source, &transaction, None, maybe_force_create_address)?;
630
631		let transaction_hash = transaction.hash();
632		let transaction_index = Pending::<T>::count();
633
634		let (reason, status, weight_info, used_gas, dest, extra_data) = match info.clone() {
635			CallOrCreateInfo::Call(info) => (
636				info.exit_reason.clone(),
637				TransactionStatus {
638					transaction_hash,
639					transaction_index,
640					from: source,
641					to,
642					contract_address: None,
643					logs: info.logs.clone(),
644					logs_bloom: {
645						let mut bloom: Bloom = Bloom::default();
646						Self::logs_bloom(info.logs, &mut bloom);
647						bloom
648					},
649				},
650				info.weight_info,
651				info.used_gas,
652				to,
653				match info.exit_reason {
654					ExitReason::Revert(_) => {
655						const LEN_START: usize = 36;
656						const MESSAGE_START: usize = 68;
657
658						let data = info.value;
659						let data_len = data.len();
660						if data_len > MESSAGE_START {
661							let message_len =
662								U256::from_big_endian(&data[LEN_START..MESSAGE_START])
663									.saturated_into::<usize>();
664							let message_end = MESSAGE_START.saturating_add(
665								message_len.min(T::ExtraDataLength::get() as usize),
666							);
667
668							if data_len >= message_end {
669								data[MESSAGE_START..message_end].to_vec()
670							} else {
671								data
672							}
673						} else {
674							data
675						}
676					}
677					_ => vec![],
678				},
679			),
680			CallOrCreateInfo::Create(info) => (
681				info.exit_reason,
682				TransactionStatus {
683					transaction_hash,
684					transaction_index,
685					from: source,
686					to,
687					contract_address: Some(info.value),
688					logs: info.logs.clone(),
689					logs_bloom: {
690						let mut bloom: Bloom = Bloom::default();
691						Self::logs_bloom(info.logs, &mut bloom);
692						bloom
693					},
694				},
695				info.weight_info,
696				info.used_gas,
697				Some(info.value),
698				Vec::new(),
699			),
700		};
701
702		let receipt = {
703			let status_code: u8 = match reason {
704				ExitReason::Succeed(_) => 1,
705				_ => 0,
706			};
707			let logs_bloom = status.logs_bloom;
708			let logs = status.clone().logs;
709			let cumulative_gas_used = if let Some((_, _, receipt)) =
710				Pending::<T>::get(transaction_index.saturating_sub(1))
711			{
712				match receipt {
713					Receipt::Legacy(d)
714					| Receipt::EIP2930(d)
715					| Receipt::EIP1559(d)
716					| Receipt::EIP7702(d) => d.used_gas.saturating_add(used_gas.effective),
717				}
718			} else {
719				used_gas.effective
720			};
721			match &transaction {
722				Transaction::Legacy(_) => Receipt::Legacy(ethereum::EIP658ReceiptData {
723					status_code,
724					used_gas: cumulative_gas_used,
725					logs_bloom,
726					logs,
727				}),
728				Transaction::EIP2930(_) => Receipt::EIP2930(ethereum::EIP2930ReceiptData {
729					status_code,
730					used_gas: cumulative_gas_used,
731					logs_bloom,
732					logs,
733				}),
734				Transaction::EIP1559(_) => Receipt::EIP1559(ethereum::EIP2930ReceiptData {
735					status_code,
736					used_gas: cumulative_gas_used,
737					logs_bloom,
738					logs,
739				}),
740				Transaction::EIP7702(_) => Receipt::EIP7702(ethereum::EIP7702ReceiptData {
741					status_code,
742					used_gas: cumulative_gas_used,
743					logs_bloom,
744					logs,
745				}),
746			}
747		};
748
749		Pending::<T>::insert(transaction_index, (transaction, status, receipt));
750
751		Self::deposit_event(Event::Executed {
752			from: source,
753			to: dest.unwrap_or_default(),
754			transaction_hash,
755			exit_reason: reason,
756			extra_data,
757		});
758
759		Ok((
760			PostDispatchInfo {
761				actual_weight: {
762					let mut gas_to_weight = T::GasWeightMapping::gas_to_weight(
763						core::cmp::max(
764							used_gas.standard.unique_saturated_into(),
765							used_gas.effective.unique_saturated_into(),
766						),
767						true,
768					);
769					if let Some(weight_info) = weight_info {
770						if let Some(proof_size_usage) = weight_info.proof_size_usage {
771							*gas_to_weight.proof_size_mut() = proof_size_usage;
772						}
773					}
774					Some(gas_to_weight)
775				},
776				pays_fee: Pays::No,
777			},
778			info,
779		))
780	}
781
782	/// Get current block hash
783	pub fn current_block_hash() -> Option<H256> {
784		<CurrentBlock<T>>::get().map(|block| block.header.hash())
785	}
786
787	/// Execute an Ethereum transaction.
788	pub fn execute(
789		from: H160,
790		transaction: &Transaction,
791		config: Option<evm::Config>,
792		maybe_force_create_address: Option<H160>,
793	) -> Result<(Option<H160>, Option<H160>, CallOrCreateInfo), DispatchErrorWithPostInfo> {
794		let transaction_data: TransactionData = transaction.into();
795		let (weight_limit, proof_size_base_cost) = Self::transaction_weight(&transaction_data);
796		let is_transactional = true;
797		let validate = false;
798
799		let (
800			input,
801			value,
802			gas_limit,
803			max_fee_per_gas,
804			max_priority_fee_per_gas,
805			nonce,
806			action,
807			access_list,
808			authorization_list,
809		) = {
810			match transaction {
811				// max_fee_per_gas and max_priority_fee_per_gas in legacy and 2930 transactions is
812				// the provided gas_price.
813				Transaction::Legacy(t) => (
814					t.input.clone(),
815					t.value,
816					t.gas_limit,
817					Some(t.gas_price),
818					Some(t.gas_price),
819					Some(t.nonce),
820					t.action,
821					Vec::new(),
822					Vec::new(),
823				),
824				Transaction::EIP2930(t) => {
825					let access_list: Vec<(H160, Vec<H256>)> = t
826						.access_list
827						.iter()
828						.map(|item| (item.address, item.storage_keys.clone()))
829						.collect();
830					(
831						t.input.clone(),
832						t.value,
833						t.gas_limit,
834						Some(t.gas_price),
835						Some(t.gas_price),
836						Some(t.nonce),
837						t.action,
838						access_list,
839						Vec::new(),
840					)
841				}
842				Transaction::EIP1559(t) => {
843					let access_list: Vec<(H160, Vec<H256>)> = t
844						.access_list
845						.iter()
846						.map(|item| (item.address, item.storage_keys.clone()))
847						.collect();
848					(
849						t.input.clone(),
850						t.value,
851						t.gas_limit,
852						Some(t.max_fee_per_gas),
853						Some(t.max_priority_fee_per_gas),
854						Some(t.nonce),
855						t.action,
856						access_list,
857						Vec::new(),
858					)
859				}
860				Transaction::EIP7702(t) => {
861					let access_list: Vec<(H160, Vec<H256>)> = t
862						.access_list
863						.iter()
864						.map(|item| (item.address, item.storage_keys.clone()))
865						.collect();
866					(
867						t.data.clone(),
868						t.value,
869						t.gas_limit,
870						Some(t.max_fee_per_gas),
871						Some(t.max_priority_fee_per_gas),
872						Some(t.nonce),
873						t.destination,
874						access_list,
875						t.authorization_list.clone(),
876					)
877				}
878			}
879		};
880
881		match action {
882			ethereum::TransactionAction::Call(target) => {
883				let res = match T::Runner::call(
884					from,
885					target,
886					input,
887					value,
888					gas_limit.unique_saturated_into(),
889					max_fee_per_gas,
890					max_priority_fee_per_gas,
891					nonce,
892					access_list,
893					authorization_list,
894					is_transactional,
895					validate,
896					weight_limit,
897					proof_size_base_cost,
898					config.as_ref().unwrap_or_else(|| T::config()),
899				) {
900					Ok(res) => res,
901					Err(e) => {
902						return Err(DispatchErrorWithPostInfo {
903							post_info: PostDispatchInfo {
904								actual_weight: Some(e.weight),
905								pays_fee: Pays::Yes,
906							},
907							error: e.error.into(),
908						})
909					}
910				};
911
912				Ok((Some(target), None, CallOrCreateInfo::Call(res)))
913			}
914			ethereum::TransactionAction::Create => {
915				let res = if let Some(force_address) = maybe_force_create_address {
916					match T::Runner::create_force_address(
917						from,
918						input,
919						value,
920						gas_limit.unique_saturated_into(),
921						max_fee_per_gas,
922						max_priority_fee_per_gas,
923						nonce,
924						access_list,
925						authorization_list,
926						is_transactional,
927						validate,
928						weight_limit,
929						proof_size_base_cost,
930						config.as_ref().unwrap_or_else(|| T::config()),
931						force_address,
932					) {
933						Ok(res) => res,
934						Err(e) => {
935							return Err(DispatchErrorWithPostInfo {
936								post_info: PostDispatchInfo {
937									actual_weight: Some(e.weight),
938									pays_fee: Pays::Yes,
939								},
940								error: e.error.into(),
941							})
942						}
943					}
944				} else {
945					match T::Runner::create(
946						from,
947						input,
948						value,
949						gas_limit.unique_saturated_into(),
950						max_fee_per_gas,
951						max_priority_fee_per_gas,
952						nonce,
953						access_list,
954						authorization_list,
955						is_transactional,
956						validate,
957						weight_limit,
958						proof_size_base_cost,
959						config.as_ref().unwrap_or_else(|| T::config()),
960					) {
961						Ok(res) => res,
962						Err(e) => {
963							return Err(DispatchErrorWithPostInfo {
964								post_info: PostDispatchInfo {
965									actual_weight: Some(e.weight),
966									pays_fee: Pays::Yes,
967								},
968								error: e.error.into(),
969							})
970						}
971					}
972				};
973
974				Ok((None, Some(res.value), CallOrCreateInfo::Create(res)))
975			}
976		}
977	}
978
979	/// Validate an Ethereum transaction already in block
980	///
981	/// This function must be called during the pre-dispatch phase
982	/// (just before applying the extrinsic).
983	pub fn validate_transaction_in_block(
984		origin: H160,
985		transaction: &Transaction,
986	) -> Result<(), TransactionValidityError> {
987		let transaction_data: TransactionData = transaction.into();
988		let (weight_limit, proof_size_base_cost) = Self::transaction_weight(&transaction_data);
989		let (base_fee, _) = T::FeeCalculator::min_gas_price();
990		let (who, _) = pallet_evm::Pallet::<T>::account_basic(&origin);
991
992		// Check if this is an EIP-7702 transaction
993		let is_eip7702 = matches!(transaction, Transaction::EIP7702(_));
994
995		let _ = CheckEvmTransaction::<InvalidTransactionWrapper>::new(
996			CheckEvmTransactionConfig {
997				evm_config: T::config(),
998				block_gas_limit: T::BlockGasLimit::get(),
999				base_fee,
1000				chain_id: T::ChainId::get(),
1001				is_transactional: true,
1002			},
1003			transaction_data.into(),
1004			weight_limit,
1005			proof_size_base_cost,
1006		)
1007		.validate_in_block_for(&who)
1008		.and_then(|v| v.with_chain_id())
1009		.and_then(|v| v.with_base_fee())
1010		.and_then(|v| v.with_balance_for(&who))
1011		.and_then(|v| v.with_eip7702_authorization_list(is_eip7702))
1012		.map_err(|e| TransactionValidityError::Invalid(e.0))?;
1013
1014		Ok(())
1015	}
1016
1017	pub fn migrate_block_v0_to_v2() -> Weight {
1018		let db_weights = T::DbWeight::get();
1019		let mut weight: Weight = db_weights.reads(1);
1020		let item = b"CurrentBlock";
1021		let block_v0 = frame_support::storage::migration::get_storage_value::<ethereum::BlockV0>(
1022			Self::name().as_bytes(),
1023			item,
1024			&[],
1025		);
1026		if let Some(block_v0) = block_v0 {
1027			weight = weight.saturating_add(db_weights.writes(1));
1028			let block_v2: ethereum::BlockV2 = block_v0.into();
1029			frame_support::storage::migration::put_storage_value::<ethereum::BlockV2>(
1030				Self::name().as_bytes(),
1031				item,
1032				&[],
1033				block_v2,
1034			);
1035		}
1036		weight
1037	}
1038
1039	#[cfg(feature = "try-runtime")]
1040	pub fn pre_migrate_block_v2() -> Result<Vec<u8>, &'static str> {
1041		let item = b"CurrentBlock";
1042		let block_v0 = frame_support::storage::migration::get_storage_value::<ethereum::BlockV0>(
1043			Self::name().as_bytes(),
1044			item,
1045			&[],
1046		);
1047		if let Some(block_v0) = block_v0 {
1048			Ok((
1049				block_v0.header.number,
1050				block_v0.header.parent_hash,
1051				block_v0.transactions.len() as u64,
1052			)
1053				.encode())
1054		} else {
1055			Ok(Vec::new())
1056		}
1057	}
1058
1059	#[cfg(feature = "try-runtime")]
1060	pub fn post_migrate_block_v2(v0_data: Vec<u8>) -> Result<(), &'static str> {
1061		let (v0_number, v0_parent_hash, v0_transaction_len): (U256, H256, u64) = Decode::decode(
1062			&mut v0_data.as_slice(),
1063		)
1064		.expect("the state parameter should be something that was generated by pre_upgrade");
1065		let item = b"CurrentBlock";
1066		let block_v2 = frame_support::storage::migration::get_storage_value::<ethereum::BlockV2>(
1067			Self::name().as_bytes(),
1068			item,
1069			&[],
1070		);
1071
1072		assert!(block_v2.is_some());
1073
1074		let block_v2 = block_v2.unwrap();
1075		assert_eq!(block_v2.header.number, v0_number);
1076		assert_eq!(block_v2.header.parent_hash, v0_parent_hash);
1077		assert_eq!(block_v2.transactions.len() as u64, v0_transaction_len);
1078		Ok(())
1079	}
1080}
1081
1082pub struct ValidatedTransaction<T>(PhantomData<T>);
1083impl<T: Config> ValidatedTransactionT for ValidatedTransaction<T> {
1084	fn apply(
1085		source: H160,
1086		transaction: Transaction,
1087		maybe_force_create_address: Option<H160>,
1088	) -> Result<(PostDispatchInfo, CallOrCreateInfo), DispatchErrorWithPostInfo> {
1089		Pallet::<T>::apply_validated_transaction(source, transaction, maybe_force_create_address)
1090	}
1091}
1092
1093#[derive(Eq, PartialEq, Clone, RuntimeDebug)]
1094pub enum ReturnValue {
1095	Bytes(Vec<u8>),
1096	Hash(H160),
1097}
1098
1099pub struct IntermediateStateRoot<T>(PhantomData<T>);
1100impl<T: Get<RuntimeVersion>> Get<H256> for IntermediateStateRoot<T> {
1101	fn get() -> H256 {
1102		let version = T::get().state_version();
1103		H256::decode(&mut &sp_io::storage::root(version)[..])
1104			.expect("Node is configured to use the same hash; qed")
1105	}
1106}
1107
1108/// Returns the Ethereum block hash by number.
1109pub struct EthereumBlockHashMapping<T>(PhantomData<T>);
1110impl<T: Config> BlockHashMapping for EthereumBlockHashMapping<T> {
1111	fn block_hash(number: u32) -> H256 {
1112		BlockHash::<T>::get(U256::from(number))
1113	}
1114}
1115
1116pub struct InvalidTransactionWrapper(InvalidTransaction);
1117
1118impl From<TransactionValidationError> for InvalidTransactionWrapper {
1119	fn from(validation_error: TransactionValidationError) -> Self {
1120		match validation_error {
1121			TransactionValidationError::GasLimitTooLow => InvalidTransactionWrapper(
1122				InvalidTransaction::Custom(TransactionValidationError::GasLimitTooLow as u8),
1123			),
1124			TransactionValidationError::GasLimitTooHigh => InvalidTransactionWrapper(
1125				InvalidTransaction::Custom(TransactionValidationError::GasLimitTooHigh as u8),
1126			),
1127			TransactionValidationError::PriorityFeeTooHigh => InvalidTransactionWrapper(
1128				InvalidTransaction::Custom(TransactionValidationError::PriorityFeeTooHigh as u8),
1129			),
1130			TransactionValidationError::BalanceTooLow => {
1131				InvalidTransactionWrapper(InvalidTransaction::Payment)
1132			}
1133			TransactionValidationError::TxNonceTooLow => {
1134				InvalidTransactionWrapper(InvalidTransaction::Stale)
1135			}
1136			TransactionValidationError::TxNonceTooHigh => {
1137				InvalidTransactionWrapper(InvalidTransaction::Future)
1138			}
1139			TransactionValidationError::InvalidFeeInput => InvalidTransactionWrapper(
1140				InvalidTransaction::Custom(TransactionValidationError::InvalidFeeInput as u8),
1141			),
1142			TransactionValidationError::InvalidChainId => InvalidTransactionWrapper(
1143				InvalidTransaction::Custom(TransactionValidationError::InvalidChainId as u8),
1144			),
1145			TransactionValidationError::InvalidSignature => InvalidTransactionWrapper(
1146				InvalidTransaction::Custom(TransactionValidationError::InvalidSignature as u8),
1147			),
1148			TransactionValidationError::GasPriceTooLow => InvalidTransactionWrapper(
1149				InvalidTransaction::Custom(TransactionValidationError::GasPriceTooLow as u8),
1150			),
1151			TransactionValidationError::EmptyAuthorizationList => {
1152				InvalidTransactionWrapper(InvalidTransaction::Custom(
1153					TransactionValidationError::EmptyAuthorizationList as u8,
1154				))
1155			}
1156			TransactionValidationError::AuthorizationListTooLarge => {
1157				InvalidTransactionWrapper(InvalidTransaction::Custom(
1158					TransactionValidationError::AuthorizationListTooLarge as u8,
1159				))
1160			}
1161			TransactionValidationError::UnknownError => InvalidTransactionWrapper(
1162				InvalidTransaction::Custom(TransactionValidationError::UnknownError as u8),
1163			),
1164		}
1165	}
1166}