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)
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).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 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 InvalidSignature,
349 PreLogExists,
351 }
352
353 #[pallet::storage]
355 pub type Pending<T: Config> =
356 CountedStorageMap<_, Identity, u32, (Transaction, TransactionStatus, Receipt), OptionQuery>;
357
358 #[pallet::storage]
360 pub type CurrentBlock<T: Config> = StorageValue<_, ethereum::BlockV3>;
361
362 #[pallet::storage]
364 pub type CurrentReceipts<T: Config> = StorageValue<_, Vec<Receipt>>;
365
366 #[pallet::storage]
368 pub type CurrentTransactionStatuses<T: Config> = StorageValue<_, Vec<TransactionStatus>>;
369
370 #[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 ðereum::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 ðereum::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 ðereum::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 ðereum::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 => { }
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 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 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 if let Some(metadata) = pallet_evm::AccountCodesMetadata::<T>::get(origin) {
572 if metadata.size > 0 {
573 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 (Some(gas_price), None, None) => {
594 gas_price.saturating_sub(base_fee).unique_saturated_into()
595 }
596 (None, Some(_), None) => 0,
598 (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 _ => return Err(InvalidTransaction::Payment.into()),
605 };
606
607 let mut builder = ValidTransactionBuilder::default()
609 .and_provides((origin, transaction_nonce))
610 .priority(priority);
611
612 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 pub fn current_block_hash() -> Option<H256> {
782 <CurrentBlock<T>>::get().map(|block| block.header.hash())
783 }
784
785 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 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 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 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
1073pub 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}