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)
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).map(|(post_info, _)| post_info)
329		}
330	}
331
332	#[pallet::event]
333	#[pallet::generate_deposit(pub(super) fn deposit_event)]
334	pub enum Event {
335		/// An ethereum transaction was successfully executed.
336		Executed {
337			from: H160,
338			to: H160,
339			transaction_hash: H256,
340			exit_reason: ExitReason,
341			extra_data: Vec<u8>,
342		},
343	}
344
345	#[pallet::error]
346	pub enum Error<T> {
347		/// Signature is invalid.
348		InvalidSignature,
349		/// Pre-log is present, therefore transact is not allowed.
350		PreLogExists,
351	}
352
353	/// Mapping from transaction index to transaction in the current building block.
354	#[pallet::storage]
355	pub type Pending<T: Config> =
356		CountedStorageMap<_, Identity, u32, (Transaction, TransactionStatus, Receipt), OptionQuery>;
357
358	/// The current Ethereum block.
359	#[pallet::storage]
360	pub type CurrentBlock<T: Config> = StorageValue<_, ethereum::BlockV3>;
361
362	/// The current Ethereum receipts.
363	#[pallet::storage]
364	pub type CurrentReceipts<T: Config> = StorageValue<_, Vec<Receipt>>;
365
366	/// The current transaction statuses.
367	#[pallet::storage]
368	pub type CurrentTransactionStatuses<T: Config> = StorageValue<_, Vec<TransactionStatus>>;
369
370	// Mapping for block number and hashes.
371	#[pallet::storage]
372	pub type BlockHash<T: Config> = StorageMap<_, Twox64Concat, U256, H256, ValueQuery>;
373
374	#[pallet::genesis_config]
375	#[derive(frame_support::DefaultNoBound)]
376	pub struct GenesisConfig<T> {
377		#[serde(skip)]
378		pub _marker: PhantomData<T>,
379	}
380
381	#[pallet::genesis_build]
382	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
383		fn build(&self) {
384			<Pallet<T>>::store_block(None, U256::zero());
385			frame_support::storage::unhashed::put::<EthereumStorageSchema>(
386				PALLET_ETHEREUM_SCHEMA,
387				&EthereumStorageSchema::V3,
388			);
389		}
390	}
391}
392
393impl<T: Config> Pallet<T> {
394	pub fn transaction_weight(transaction_data: &TransactionData) -> (Option<Weight>, Option<u64>) {
395		match <T as pallet_evm::Config>::GasWeightMapping::gas_to_weight(
396			transaction_data.gas_limit.unique_saturated_into(),
397			true,
398		) {
399			weight_limit if weight_limit.proof_size() > 0 => (
400				Some(weight_limit),
401				Some(transaction_data.proof_size_base_cost()),
402			),
403			_ => (None, None),
404		}
405	}
406
407	fn recover_signer(transaction: &Transaction) -> Option<H160> {
408		let mut sig = [0u8; 65];
409		let mut msg = [0u8; 32];
410		match transaction {
411			Transaction::Legacy(t) => {
412				sig[0..32].copy_from_slice(&t.signature.r()[..]);
413				sig[32..64].copy_from_slice(&t.signature.s()[..]);
414				sig[64] = t.signature.standard_v();
415				msg.copy_from_slice(
416					&ethereum::LegacyTransactionMessage::from(t.clone()).hash()[..],
417				);
418			}
419			Transaction::EIP2930(t) => {
420				sig[0..32].copy_from_slice(&t.signature.r()[..]);
421				sig[32..64].copy_from_slice(&t.signature.s()[..]);
422				sig[64] = t.signature.odd_y_parity() as u8;
423				msg.copy_from_slice(
424					&ethereum::EIP2930TransactionMessage::from(t.clone()).hash()[..],
425				);
426			}
427			Transaction::EIP1559(t) => {
428				sig[0..32].copy_from_slice(&t.signature.r()[..]);
429				sig[32..64].copy_from_slice(&t.signature.s()[..]);
430				sig[64] = t.signature.odd_y_parity() as u8;
431				msg.copy_from_slice(
432					&ethereum::EIP1559TransactionMessage::from(t.clone()).hash()[..],
433				);
434			}
435			Transaction::EIP7702(t) => {
436				sig[0..32].copy_from_slice(&t.signature.r()[..]);
437				sig[32..64].copy_from_slice(&t.signature.s()[..]);
438				sig[64] = t.signature.odd_y_parity() as u8;
439				msg.copy_from_slice(
440					&ethereum::EIP7702TransactionMessage::from(t.clone()).hash()[..],
441				);
442			}
443		}
444		let pubkey = sp_io::crypto::secp256k1_ecdsa_recover(&sig, &msg).ok()?;
445		Some(H160::from(H256::from(sp_io::hashing::keccak_256(&pubkey))))
446	}
447
448	fn store_block(post_log: Option<PostLogContent>, block_number: U256) {
449		let transactions_count = Pending::<T>::count();
450		let mut transactions = Vec::with_capacity(transactions_count as usize);
451		let mut statuses = Vec::with_capacity(transactions_count as usize);
452		let mut receipts = Vec::with_capacity(transactions_count as usize);
453		let mut logs_bloom = Bloom::default();
454		let mut cumulative_gas_used = U256::zero();
455		for transaction_index in 0..transactions_count {
456			if let Some((transaction, status, receipt)) = Pending::<T>::take(transaction_index) {
457				transactions.push(transaction);
458				statuses.push(status);
459				receipts.push(receipt.clone());
460				let (logs, used_gas) = match receipt {
461					Receipt::Legacy(d) | Receipt::EIP2930(d) | Receipt::EIP1559(d) => {
462						(d.logs.clone(), d.used_gas)
463					}
464					Receipt::EIP7702(d) => (d.logs.clone(), d.used_gas),
465				};
466				cumulative_gas_used = used_gas;
467				Self::logs_bloom(logs, &mut logs_bloom);
468			}
469		}
470
471		let ommers = Vec::<ethereum::Header>::new();
472		let receipts_root = ethereum::util::ordered_trie_root(
473			receipts.iter().map(ethereum::EnvelopedEncodable::encode),
474		);
475		let partial_header = ethereum::PartialHeader {
476			parent_hash: if block_number > U256::zero() {
477				BlockHash::<T>::get(block_number - 1)
478			} else {
479				H256::default()
480			},
481			beneficiary: pallet_evm::Pallet::<T>::find_author(),
482			state_root: T::StateRoot::get(),
483			receipts_root,
484			logs_bloom,
485			difficulty: U256::zero(),
486			number: block_number,
487			gas_limit: T::BlockGasLimit::get(),
488			gas_used: cumulative_gas_used,
489			timestamp: T::Timestamp::now().unique_saturated_into(),
490			extra_data: Vec::new(),
491			mix_hash: H256::default(),
492			nonce: H64::default(),
493		};
494		let block = ethereum::Block::new(partial_header, transactions.clone(), ommers);
495
496		CurrentBlock::<T>::put(block.clone());
497		CurrentReceipts::<T>::put(receipts.clone());
498		CurrentTransactionStatuses::<T>::put(statuses.clone());
499		BlockHash::<T>::insert(block_number, block.header.hash());
500
501		match post_log {
502			Some(PostLogContent::BlockAndTxnHashes) => {
503				let digest = DigestItem::Consensus(
504					FRONTIER_ENGINE_ID,
505					PostLog::Hashes(fp_consensus::Hashes::from_block(block)).encode(),
506				);
507				frame_system::Pallet::<T>::deposit_log(digest);
508			}
509			Some(PostLogContent::OnlyBlockHash) => {
510				let digest = DigestItem::Consensus(
511					FRONTIER_ENGINE_ID,
512					PostLog::BlockHash(block.header.hash()).encode(),
513				);
514				frame_system::Pallet::<T>::deposit_log(digest);
515			}
516			None => { /* do nothing*/ }
517		}
518	}
519
520	fn logs_bloom(logs: Vec<Log>, bloom: &mut Bloom) {
521		for log in logs {
522			bloom.accrue(BloomInput::Raw(&log.address[..]));
523			for topic in log.topics {
524				bloom.accrue(BloomInput::Raw(&topic[..]));
525			}
526		}
527	}
528
529	// Controls that must be performed by the pool.
530	// The controls common with the State Transition Function (STF) are in
531	// the function `validate_transaction_common`.
532	fn validate_transaction_in_pool(
533		origin: H160,
534		transaction: &Transaction,
535	) -> TransactionValidity {
536		let transaction_data: TransactionData = transaction.into();
537		let transaction_nonce = transaction_data.nonce;
538		let (weight_limit, proof_size_base_cost) = Self::transaction_weight(&transaction_data);
539		let (base_fee, _) = T::FeeCalculator::min_gas_price();
540		let (who, _) = pallet_evm::Pallet::<T>::account_basic(&origin);
541
542		// Check if this is an EIP-7702 transaction
543		let is_eip7702 = matches!(transaction, Transaction::EIP7702(_));
544
545		let _ = CheckEvmTransaction::<InvalidTransactionWrapper>::new(
546			CheckEvmTransactionConfig {
547				evm_config: T::config(),
548				block_gas_limit: T::BlockGasLimit::get(),
549				base_fee,
550				chain_id: T::ChainId::get(),
551				is_transactional: true,
552			},
553			transaction_data.clone().into(),
554			weight_limit,
555			proof_size_base_cost,
556		)
557		.validate_in_pool_for(&who)
558		.and_then(|v| v.with_chain_id())
559		.and_then(|v| v.with_base_fee())
560		.and_then(|v| v.with_balance_for(&who))
561		.and_then(|v| v.with_eip7702_authorization_list(is_eip7702))
562		.map_err(|e| e.0)?;
563
564		// EIP-3607: https://eips.ethereum.org/EIPS/eip-3607
565		// Do not allow transactions for which `tx.sender` has any code deployed.
566		// Exception: Allow transactions from EOAs whose code is a valid delegation indicator (0xef0100 || address).
567		//
568		// This check should be done on the transaction validation (here) **and**
569		// on transaction execution, otherwise a contract tx will be included in
570		// the mempool and pollute the mempool forever.
571		if let Some(metadata) = pallet_evm::AccountCodesMetadata::<T>::get(origin) {
572			if metadata.size > 0 {
573				// Account has code, check if it's a valid delegation
574				let is_delegation = metadata.size
575					== evm::delegation::EIP_7702_DELEGATION_SIZE as u64
576					&& pallet_evm::AccountCodes::<T>::get(origin)
577						.starts_with(evm::delegation::EIP_7702_DELEGATION_PREFIX);
578
579				if !is_delegation {
580					return Err(InvalidTransaction::BadSigner.into());
581				}
582			}
583		}
584
585		let priority = match (
586			transaction_data.gas_price,
587			transaction_data.max_fee_per_gas,
588			transaction_data.max_priority_fee_per_gas,
589		) {
590			// Legacy or EIP-2930 transaction.
591			// Handle priority here. On legacy transaction everything in gas_price except
592			// the current base_fee is considered a tip to the miner and thus the priority.
593			(Some(gas_price), None, None) => {
594				gas_price.saturating_sub(base_fee).unique_saturated_into()
595			}
596			// EIP-1559 transaction without tip.
597			(None, Some(_), None) => 0,
598			// EIP-1559 transaction with tip.
599			(None, Some(max_fee_per_gas), Some(max_priority_fee_per_gas)) => max_fee_per_gas
600				.saturating_sub(base_fee)
601				.min(max_priority_fee_per_gas)
602				.unique_saturated_into(),
603			// Unreachable because already validated. Gracefully handle.
604			_ => return Err(InvalidTransaction::Payment.into()),
605		};
606
607		// The tag provides and requires must be filled correctly according to the nonce.
608		let mut builder = ValidTransactionBuilder::default()
609			.and_provides((origin, transaction_nonce))
610			.priority(priority);
611
612		// In the context of the pool, a transaction with
613		// too high a nonce is still considered valid
614		if transaction_nonce > who.nonce {
615			if let Some(prev_nonce) = transaction_nonce.checked_sub(1.into()) {
616				builder = builder.and_requires((origin, prev_nonce))
617			}
618		}
619
620		builder.build()
621	}
622
623	fn apply_validated_transaction(
624		source: H160,
625		transaction: Transaction,
626	) -> Result<(PostDispatchInfo, CallOrCreateInfo), DispatchErrorWithPostInfo> {
627		let (to, _, info) = Self::execute(source, &transaction, None)?;
628
629		let transaction_hash = transaction.hash();
630		let transaction_index = Pending::<T>::count();
631
632		let (reason, status, weight_info, used_gas, dest, extra_data) = match info.clone() {
633			CallOrCreateInfo::Call(info) => (
634				info.exit_reason.clone(),
635				TransactionStatus {
636					transaction_hash,
637					transaction_index,
638					from: source,
639					to,
640					contract_address: None,
641					logs: info.logs.clone(),
642					logs_bloom: {
643						let mut bloom: Bloom = Bloom::default();
644						Self::logs_bloom(info.logs, &mut bloom);
645						bloom
646					},
647				},
648				info.weight_info,
649				info.used_gas,
650				to,
651				match info.exit_reason {
652					ExitReason::Revert(_) => {
653						const LEN_START: usize = 36;
654						const MESSAGE_START: usize = 68;
655
656						let data = info.value;
657						let data_len = data.len();
658						if data_len > MESSAGE_START {
659							let message_len =
660								U256::from_big_endian(&data[LEN_START..MESSAGE_START])
661									.saturated_into::<usize>();
662							let message_end = MESSAGE_START.saturating_add(
663								message_len.min(T::ExtraDataLength::get() as usize),
664							);
665
666							if data_len >= message_end {
667								data[MESSAGE_START..message_end].to_vec()
668							} else {
669								data
670							}
671						} else {
672							data
673						}
674					}
675					_ => vec![],
676				},
677			),
678			CallOrCreateInfo::Create(info) => (
679				info.exit_reason,
680				TransactionStatus {
681					transaction_hash,
682					transaction_index,
683					from: source,
684					to,
685					contract_address: Some(info.value),
686					logs: info.logs.clone(),
687					logs_bloom: {
688						let mut bloom: Bloom = Bloom::default();
689						Self::logs_bloom(info.logs, &mut bloom);
690						bloom
691					},
692				},
693				info.weight_info,
694				info.used_gas,
695				Some(info.value),
696				Vec::new(),
697			),
698		};
699
700		let receipt = {
701			let status_code: u8 = match reason {
702				ExitReason::Succeed(_) => 1,
703				_ => 0,
704			};
705			let logs_bloom = status.logs_bloom;
706			let logs = status.clone().logs;
707			let cumulative_gas_used = if let Some((_, _, receipt)) =
708				Pending::<T>::get(transaction_index.saturating_sub(1))
709			{
710				match receipt {
711					Receipt::Legacy(d)
712					| Receipt::EIP2930(d)
713					| Receipt::EIP1559(d)
714					| Receipt::EIP7702(d) => d.used_gas.saturating_add(used_gas.effective),
715				}
716			} else {
717				used_gas.effective
718			};
719			match &transaction {
720				Transaction::Legacy(_) => Receipt::Legacy(ethereum::EIP658ReceiptData {
721					status_code,
722					used_gas: cumulative_gas_used,
723					logs_bloom,
724					logs,
725				}),
726				Transaction::EIP2930(_) => Receipt::EIP2930(ethereum::EIP2930ReceiptData {
727					status_code,
728					used_gas: cumulative_gas_used,
729					logs_bloom,
730					logs,
731				}),
732				Transaction::EIP1559(_) => Receipt::EIP1559(ethereum::EIP2930ReceiptData {
733					status_code,
734					used_gas: cumulative_gas_used,
735					logs_bloom,
736					logs,
737				}),
738				Transaction::EIP7702(_) => Receipt::EIP7702(ethereum::EIP7702ReceiptData {
739					status_code,
740					used_gas: cumulative_gas_used,
741					logs_bloom,
742					logs,
743				}),
744			}
745		};
746
747		Pending::<T>::insert(transaction_index, (transaction, status, receipt));
748
749		Self::deposit_event(Event::Executed {
750			from: source,
751			to: dest.unwrap_or_default(),
752			transaction_hash,
753			exit_reason: reason,
754			extra_data,
755		});
756
757		Ok((
758			PostDispatchInfo {
759				actual_weight: {
760					let mut gas_to_weight = T::GasWeightMapping::gas_to_weight(
761						core::cmp::max(
762							used_gas.standard.unique_saturated_into(),
763							used_gas.effective.unique_saturated_into(),
764						),
765						true,
766					);
767					if let Some(weight_info) = weight_info {
768						if let Some(proof_size_usage) = weight_info.proof_size_usage {
769							*gas_to_weight.proof_size_mut() = proof_size_usage;
770						}
771					}
772					Some(gas_to_weight)
773				},
774				pays_fee: Pays::No,
775			},
776			info,
777		))
778	}
779
780	/// Get current block hash
781	pub fn current_block_hash() -> Option<H256> {
782		<CurrentBlock<T>>::get().map(|block| block.header.hash())
783	}
784
785	/// Execute an Ethereum transaction.
786	pub fn execute(
787		from: H160,
788		transaction: &Transaction,
789		config: Option<evm::Config>,
790	) -> Result<(Option<H160>, Option<H160>, CallOrCreateInfo), DispatchErrorWithPostInfo> {
791		let transaction_data: TransactionData = transaction.into();
792		let (weight_limit, proof_size_base_cost) = Self::transaction_weight(&transaction_data);
793		let is_transactional = true;
794		let validate = false;
795
796		let (
797			input,
798			value,
799			gas_limit,
800			max_fee_per_gas,
801			max_priority_fee_per_gas,
802			nonce,
803			action,
804			access_list,
805			authorization_list,
806		) = {
807			match transaction {
808				// max_fee_per_gas and max_priority_fee_per_gas in legacy and 2930 transactions is
809				// the provided gas_price.
810				Transaction::Legacy(t) => (
811					t.input.clone(),
812					t.value,
813					t.gas_limit,
814					Some(t.gas_price),
815					Some(t.gas_price),
816					Some(t.nonce),
817					t.action,
818					Vec::new(),
819					Vec::new(),
820				),
821				Transaction::EIP2930(t) => {
822					let access_list: Vec<(H160, Vec<H256>)> = t
823						.access_list
824						.iter()
825						.map(|item| (item.address, item.storage_keys.clone()))
826						.collect();
827					(
828						t.input.clone(),
829						t.value,
830						t.gas_limit,
831						Some(t.gas_price),
832						Some(t.gas_price),
833						Some(t.nonce),
834						t.action,
835						access_list,
836						Vec::new(),
837					)
838				}
839				Transaction::EIP1559(t) => {
840					let access_list: Vec<(H160, Vec<H256>)> = t
841						.access_list
842						.iter()
843						.map(|item| (item.address, item.storage_keys.clone()))
844						.collect();
845					(
846						t.input.clone(),
847						t.value,
848						t.gas_limit,
849						Some(t.max_fee_per_gas),
850						Some(t.max_priority_fee_per_gas),
851						Some(t.nonce),
852						t.action,
853						access_list,
854						Vec::new(),
855					)
856				}
857				Transaction::EIP7702(t) => {
858					let access_list: Vec<(H160, Vec<H256>)> = t
859						.access_list
860						.iter()
861						.map(|item| (item.address, item.storage_keys.clone()))
862						.collect();
863					(
864						t.data.clone(),
865						t.value,
866						t.gas_limit,
867						Some(t.max_fee_per_gas),
868						Some(t.max_priority_fee_per_gas),
869						Some(t.nonce),
870						t.destination,
871						access_list,
872						t.authorization_list.clone(),
873					)
874				}
875			}
876		};
877
878		match action {
879			ethereum::TransactionAction::Call(target) => {
880				let res = match T::Runner::call(
881					from,
882					target,
883					input,
884					value,
885					gas_limit.unique_saturated_into(),
886					max_fee_per_gas,
887					max_priority_fee_per_gas,
888					nonce,
889					access_list,
890					authorization_list,
891					is_transactional,
892					validate,
893					weight_limit,
894					proof_size_base_cost,
895					config.as_ref().unwrap_or_else(|| T::config()),
896				) {
897					Ok(res) => res,
898					Err(e) => {
899						return Err(DispatchErrorWithPostInfo {
900							post_info: PostDispatchInfo {
901								actual_weight: Some(e.weight),
902								pays_fee: Pays::Yes,
903							},
904							error: e.error.into(),
905						})
906					}
907				};
908
909				Ok((Some(target), None, CallOrCreateInfo::Call(res)))
910			}
911			ethereum::TransactionAction::Create => {
912				let res = match T::Runner::create(
913					from,
914					input,
915					value,
916					gas_limit.unique_saturated_into(),
917					max_fee_per_gas,
918					max_priority_fee_per_gas,
919					nonce,
920					access_list,
921					authorization_list,
922					is_transactional,
923					validate,
924					weight_limit,
925					proof_size_base_cost,
926					config.as_ref().unwrap_or_else(|| T::config()),
927				) {
928					Ok(res) => res,
929					Err(e) => {
930						return Err(DispatchErrorWithPostInfo {
931							post_info: PostDispatchInfo {
932								actual_weight: Some(e.weight),
933								pays_fee: Pays::Yes,
934							},
935							error: e.error.into(),
936						})
937					}
938				};
939
940				Ok((None, Some(res.value), CallOrCreateInfo::Create(res)))
941			}
942		}
943	}
944
945	/// Validate an Ethereum transaction already in block
946	///
947	/// This function must be called during the pre-dispatch phase
948	/// (just before applying the extrinsic).
949	pub fn validate_transaction_in_block(
950		origin: H160,
951		transaction: &Transaction,
952	) -> Result<(), TransactionValidityError> {
953		let transaction_data: TransactionData = transaction.into();
954		let (weight_limit, proof_size_base_cost) = Self::transaction_weight(&transaction_data);
955		let (base_fee, _) = T::FeeCalculator::min_gas_price();
956		let (who, _) = pallet_evm::Pallet::<T>::account_basic(&origin);
957
958		// Check if this is an EIP-7702 transaction
959		let is_eip7702 = matches!(transaction, Transaction::EIP7702(_));
960
961		let _ = CheckEvmTransaction::<InvalidTransactionWrapper>::new(
962			CheckEvmTransactionConfig {
963				evm_config: T::config(),
964				block_gas_limit: T::BlockGasLimit::get(),
965				base_fee,
966				chain_id: T::ChainId::get(),
967				is_transactional: true,
968			},
969			transaction_data.into(),
970			weight_limit,
971			proof_size_base_cost,
972		)
973		.validate_in_block_for(&who)
974		.and_then(|v| v.with_chain_id())
975		.and_then(|v| v.with_base_fee())
976		.and_then(|v| v.with_balance_for(&who))
977		.and_then(|v| v.with_eip7702_authorization_list(is_eip7702))
978		.map_err(|e| TransactionValidityError::Invalid(e.0))?;
979
980		Ok(())
981	}
982
983	pub fn migrate_block_v0_to_v2() -> Weight {
984		let db_weights = T::DbWeight::get();
985		let mut weight: Weight = db_weights.reads(1);
986		let item = b"CurrentBlock";
987		let block_v0 = frame_support::storage::migration::get_storage_value::<ethereum::BlockV0>(
988			Self::name().as_bytes(),
989			item,
990			&[],
991		);
992		if let Some(block_v0) = block_v0 {
993			weight = weight.saturating_add(db_weights.writes(1));
994			let block_v2: ethereum::BlockV2 = block_v0.into();
995			frame_support::storage::migration::put_storage_value::<ethereum::BlockV2>(
996				Self::name().as_bytes(),
997				item,
998				&[],
999				block_v2,
1000			);
1001		}
1002		weight
1003	}
1004
1005	#[cfg(feature = "try-runtime")]
1006	pub fn pre_migrate_block_v2() -> Result<Vec<u8>, &'static str> {
1007		let item = b"CurrentBlock";
1008		let block_v0 = frame_support::storage::migration::get_storage_value::<ethereum::BlockV0>(
1009			Self::name().as_bytes(),
1010			item,
1011			&[],
1012		);
1013		if let Some(block_v0) = block_v0 {
1014			Ok((
1015				block_v0.header.number,
1016				block_v0.header.parent_hash,
1017				block_v0.transactions.len() as u64,
1018			)
1019				.encode())
1020		} else {
1021			Ok(Vec::new())
1022		}
1023	}
1024
1025	#[cfg(feature = "try-runtime")]
1026	pub fn post_migrate_block_v2(v0_data: Vec<u8>) -> Result<(), &'static str> {
1027		let (v0_number, v0_parent_hash, v0_transaction_len): (U256, H256, u64) = Decode::decode(
1028			&mut v0_data.as_slice(),
1029		)
1030		.expect("the state parameter should be something that was generated by pre_upgrade");
1031		let item = b"CurrentBlock";
1032		let block_v2 = frame_support::storage::migration::get_storage_value::<ethereum::BlockV2>(
1033			Self::name().as_bytes(),
1034			item,
1035			&[],
1036		);
1037
1038		assert!(block_v2.is_some());
1039
1040		let block_v2 = block_v2.unwrap();
1041		assert_eq!(block_v2.header.number, v0_number);
1042		assert_eq!(block_v2.header.parent_hash, v0_parent_hash);
1043		assert_eq!(block_v2.transactions.len() as u64, v0_transaction_len);
1044		Ok(())
1045	}
1046}
1047
1048pub struct ValidatedTransaction<T>(PhantomData<T>);
1049impl<T: Config> ValidatedTransactionT for ValidatedTransaction<T> {
1050	fn apply(
1051		source: H160,
1052		transaction: Transaction,
1053	) -> Result<(PostDispatchInfo, CallOrCreateInfo), DispatchErrorWithPostInfo> {
1054		Pallet::<T>::apply_validated_transaction(source, transaction)
1055	}
1056}
1057
1058#[derive(Eq, PartialEq, Clone, RuntimeDebug)]
1059pub enum ReturnValue {
1060	Bytes(Vec<u8>),
1061	Hash(H160),
1062}
1063
1064pub struct IntermediateStateRoot<T>(PhantomData<T>);
1065impl<T: Get<RuntimeVersion>> Get<H256> for IntermediateStateRoot<T> {
1066	fn get() -> H256 {
1067		let version = T::get().state_version();
1068		H256::decode(&mut &sp_io::storage::root(version)[..])
1069			.expect("Node is configured to use the same hash; qed")
1070	}
1071}
1072
1073/// Returns the Ethereum block hash by number.
1074pub struct EthereumBlockHashMapping<T>(PhantomData<T>);
1075impl<T: Config> BlockHashMapping for EthereumBlockHashMapping<T> {
1076	fn block_hash(number: u32) -> H256 {
1077		BlockHash::<T>::get(U256::from(number))
1078	}
1079}
1080
1081pub struct InvalidTransactionWrapper(InvalidTransaction);
1082
1083impl From<TransactionValidationError> for InvalidTransactionWrapper {
1084	fn from(validation_error: TransactionValidationError) -> Self {
1085		match validation_error {
1086			TransactionValidationError::GasLimitTooLow => InvalidTransactionWrapper(
1087				InvalidTransaction::Custom(TransactionValidationError::GasLimitTooLow as u8),
1088			),
1089			TransactionValidationError::GasLimitTooHigh => InvalidTransactionWrapper(
1090				InvalidTransaction::Custom(TransactionValidationError::GasLimitTooHigh as u8),
1091			),
1092			TransactionValidationError::PriorityFeeTooHigh => InvalidTransactionWrapper(
1093				InvalidTransaction::Custom(TransactionValidationError::PriorityFeeTooHigh as u8),
1094			),
1095			TransactionValidationError::BalanceTooLow => {
1096				InvalidTransactionWrapper(InvalidTransaction::Payment)
1097			}
1098			TransactionValidationError::TxNonceTooLow => {
1099				InvalidTransactionWrapper(InvalidTransaction::Stale)
1100			}
1101			TransactionValidationError::TxNonceTooHigh => {
1102				InvalidTransactionWrapper(InvalidTransaction::Future)
1103			}
1104			TransactionValidationError::InvalidFeeInput => InvalidTransactionWrapper(
1105				InvalidTransaction::Custom(TransactionValidationError::InvalidFeeInput as u8),
1106			),
1107			TransactionValidationError::InvalidChainId => InvalidTransactionWrapper(
1108				InvalidTransaction::Custom(TransactionValidationError::InvalidChainId as u8),
1109			),
1110			TransactionValidationError::InvalidSignature => InvalidTransactionWrapper(
1111				InvalidTransaction::Custom(TransactionValidationError::InvalidSignature as u8),
1112			),
1113			TransactionValidationError::GasPriceTooLow => InvalidTransactionWrapper(
1114				InvalidTransaction::Custom(TransactionValidationError::GasPriceTooLow as u8),
1115			),
1116			TransactionValidationError::EmptyAuthorizationList => {
1117				InvalidTransactionWrapper(InvalidTransaction::Custom(
1118					TransactionValidationError::EmptyAuthorizationList as u8,
1119				))
1120			}
1121			TransactionValidationError::AuthorizationListTooLarge => {
1122				InvalidTransactionWrapper(InvalidTransaction::Custom(
1123					TransactionValidationError::AuthorizationListTooLarge as u8,
1124				))
1125			}
1126			TransactionValidationError::UnknownError => InvalidTransactionWrapper(
1127				InvalidTransaction::Custom(TransactionValidationError::UnknownError as u8),
1128			),
1129		}
1130	}
1131}