1#![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;
49use 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;
67use 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 type StateRoot: Get<H256>;
208 type PostLogContent: Get<PostLogContent>;
210 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 let block_hash_count = T::BlockHashCount::get();
252 let to_remove = n
253 .saturating_sub(block_hash_count)
254 .saturating_sub(One::one());
255 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 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 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 #[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 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 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 InvalidSignature,
350 PreLogExists,
352 }
353
354 #[pallet::storage]
356 pub type Pending<T: Config> =
357 CountedStorageMap<_, Identity, u32, (Transaction, TransactionStatus, Receipt), OptionQuery>;
358
359 #[pallet::storage]
361 pub type CurrentBlock<T: Config> = StorageValue<_, ethereum::BlockV3>;
362
363 #[pallet::storage]
365 pub type CurrentReceipts<T: Config> = StorageValue<_, Vec<Receipt>>;
366
367 #[pallet::storage]
369 pub type CurrentTransactionStatuses<T: Config> = StorageValue<_, Vec<TransactionStatus>>;
370
371 #[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 ðereum::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 ðereum::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 ðereum::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 ðereum::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 => { }
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 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 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 if let Some(metadata) = pallet_evm::AccountCodesMetadata::<T>::get(origin) {
573 if metadata.size > 0 {
574 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 (Some(gas_price), None, None) => {
595 gas_price.saturating_sub(base_fee).unique_saturated_into()
596 }
597 (None, Some(_), None) => 0,
599 (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 _ => return Err(InvalidTransaction::Payment.into()),
606 };
607
608 let mut builder = ValidTransactionBuilder::default()
610 .and_provides((origin, transaction_nonce))
611 .priority(priority);
612
613 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 pub fn current_block_hash() -> Option<H256> {
784 <CurrentBlock<T>>::get().map(|block| block.header.hash())
785 }
786
787 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 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 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 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
1108pub 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}