1use crate::{
19 AccountIdOf, AddressMapper, BalanceOf, CallOf, Config, LOG_TARGET, Pallet, Zero,
20 evm::{
21 CreateCallMode,
22 api::{GenericTransaction, TransactionSigned},
23 fees::InfoT,
24 },
25};
26use codec::{Decode, DecodeWithMemTracking, Encode};
27use frame_support::{
28 dispatch::{DispatchInfo, GetDispatchInfo},
29 traits::{
30 InherentBuilder, IsSubType, SignedTransactionBuilder,
31 fungible::Balanced,
32 tokens::{Fortitude, Precision, Preservation},
33 },
34};
35use pallet_transaction_payment::Config as TxConfig;
36use scale_info::{StaticTypeInfo, TypeInfo};
37use sp_core::U256;
38use sp_runtime::{
39 Debug, OpaqueExtrinsic, Weight,
40 generic::{self, CheckedExtrinsic, ExtrinsicFormat},
41 traits::{
42 Checkable, ExtrinsicCall, ExtrinsicLike, ExtrinsicMetadata, LazyExtrinsic,
43 TransactionExtension,
44 },
45 transaction_validity::{InvalidTransaction, TransactionValidityError},
46};
47
48pub trait SetWeightLimit {
50 fn set_weight_limit(&mut self, weight_limit: Weight) -> Weight;
54}
55
56#[derive(Encode, Decode, DecodeWithMemTracking, Clone, PartialEq, Eq, Debug)]
59pub struct UncheckedExtrinsic<Address, Signature, E: EthExtra>(
60 pub generic::UncheckedExtrinsic<Address, CallOf<E::Config>, Signature, E::Extension>,
61);
62
63impl<Address, Signature, E: EthExtra> TypeInfo for UncheckedExtrinsic<Address, Signature, E>
64where
65 Address: StaticTypeInfo,
66 Signature: StaticTypeInfo,
67 E::Extension: StaticTypeInfo,
68{
69 type Identity =
70 generic::UncheckedExtrinsic<Address, CallOf<E::Config>, Signature, E::Extension>;
71 fn type_info() -> scale_info::Type {
72 generic::UncheckedExtrinsic::<Address, CallOf<E::Config>, Signature, E::Extension>::type_info()
73 }
74}
75
76impl<Address, Signature, E: EthExtra>
77 From<generic::UncheckedExtrinsic<Address, CallOf<E::Config>, Signature, E::Extension>>
78 for UncheckedExtrinsic<Address, Signature, E>
79{
80 fn from(
81 utx: generic::UncheckedExtrinsic<Address, CallOf<E::Config>, Signature, E::Extension>,
82 ) -> Self {
83 Self(utx)
84 }
85}
86
87impl<Address: TypeInfo, Signature: TypeInfo, E: EthExtra> ExtrinsicLike
88 for UncheckedExtrinsic<Address, Signature, E>
89{
90 fn is_bare(&self) -> bool {
91 ExtrinsicLike::is_bare(&self.0)
92 }
93}
94
95impl<Address, Signature, E: EthExtra> ExtrinsicMetadata
96 for UncheckedExtrinsic<Address, Signature, E>
97{
98 const VERSIONS: &'static [u8] = generic::UncheckedExtrinsic::<
99 Address,
100 CallOf<E::Config>,
101 Signature,
102 E::Extension,
103 >::VERSIONS;
104 type TransactionExtensions = E::Extension;
105}
106
107impl<Address: TypeInfo, Signature: TypeInfo, E: EthExtra> ExtrinsicCall
108 for UncheckedExtrinsic<Address, Signature, E>
109{
110 type Call = CallOf<E::Config>;
111
112 fn call(&self) -> &Self::Call {
113 self.0.call()
114 }
115
116 fn into_call(self) -> Self::Call {
117 self.0.into_call()
118 }
119}
120
121impl<LookupSource, Signature, E, Lookup> Checkable<Lookup>
122 for UncheckedExtrinsic<LookupSource, Signature, E>
123where
124 E: EthExtra,
125 Self: Encode,
126 <E::Config as frame_system::Config>::Nonce: TryFrom<U256>,
127 CallOf<E::Config>: SetWeightLimit,
128 generic::UncheckedExtrinsic<LookupSource, CallOf<E::Config>, Signature, E::Extension>:
130 Checkable<
131 Lookup,
132 Checked = CheckedExtrinsic<AccountIdOf<E::Config>, CallOf<E::Config>, E::Extension>,
133 >,
134{
135 type Checked = CheckedExtrinsic<AccountIdOf<E::Config>, CallOf<E::Config>, E::Extension>;
136
137 fn check(self, lookup: &Lookup) -> Result<Self::Checked, TransactionValidityError> {
138 if !self.0.is_signed() {
139 if let Some(crate::Call::eth_transact { payload }) = self.0.function.is_sub_type() {
140 log::trace!(
141 target: LOG_TARGET,
142 "eth_transact substrate tx hash: 0x{}",
143 sp_core::hexdisplay::HexDisplay::from(&sp_core::hashing::blake2_256(&self.encode())),
144 );
145 let checked = E::try_into_checked_extrinsic(payload, self.encoded_size())?;
146 return Ok(checked);
147 };
148 }
149 self.0.check(lookup)
150 }
151
152 #[cfg(feature = "try-runtime")]
153 fn unchecked_into_checked_i_know_what_i_am_doing(
154 self,
155 lookup: &Lookup,
156 ) -> Result<Self::Checked, TransactionValidityError> {
157 self.0.unchecked_into_checked_i_know_what_i_am_doing(lookup)
158 }
159}
160
161impl<Address, Signature, E: EthExtra> GetDispatchInfo
162 for UncheckedExtrinsic<Address, Signature, E>
163{
164 fn get_dispatch_info(&self) -> DispatchInfo {
165 self.0.get_dispatch_info()
166 }
167}
168
169impl<Address: Encode, Signature: Encode, E: EthExtra> serde::Serialize
170 for UncheckedExtrinsic<Address, Signature, E>
171{
172 fn serialize<S>(&self, seq: S) -> Result<S::Ok, S::Error>
173 where
174 S: ::serde::Serializer,
175 {
176 self.0.serialize(seq)
177 }
178}
179
180impl<'a, Address: Decode, Signature: Decode, E: EthExtra> serde::Deserialize<'a>
181 for UncheckedExtrinsic<Address, Signature, E>
182{
183 fn deserialize<D>(de: D) -> Result<Self, D::Error>
184 where
185 D: serde::Deserializer<'a>,
186 {
187 let r = sp_core::bytes::deserialize(de)?;
188 Decode::decode(&mut &r[..])
189 .map_err(|e| serde::de::Error::custom(alloc::format!("Decode error: {}", e)))
190 }
191}
192
193impl<Address, Signature, E: EthExtra> SignedTransactionBuilder
194 for UncheckedExtrinsic<Address, Signature, E>
195where
196 Address: TypeInfo,
197 Signature: TypeInfo,
198 E::Extension: TypeInfo,
199{
200 type Address = Address;
201 type Signature = Signature;
202 type Extension = E::Extension;
203
204 fn new_signed_transaction(
205 call: Self::Call,
206 signed: Address,
207 signature: Signature,
208 tx_ext: E::Extension,
209 ) -> Self {
210 generic::UncheckedExtrinsic::new_signed(call, signed, signature, tx_ext).into()
211 }
212}
213
214impl<Address, Signature, E: EthExtra> InherentBuilder for UncheckedExtrinsic<Address, Signature, E>
215where
216 Address: TypeInfo,
217 Signature: TypeInfo,
218 E::Extension: TypeInfo,
219{
220 fn new_inherent(call: Self::Call) -> Self {
221 generic::UncheckedExtrinsic::new_bare(call).into()
222 }
223}
224
225impl<Address, Signature, E: EthExtra> From<UncheckedExtrinsic<Address, Signature, E>>
226 for OpaqueExtrinsic
227where
228 Address: Encode,
229 Signature: Encode,
230 E::Extension: Encode,
231{
232 fn from(extrinsic: UncheckedExtrinsic<Address, Signature, E>) -> Self {
233 extrinsic.0.into()
234 }
235}
236
237impl<Address, Signature, E: EthExtra> LazyExtrinsic for UncheckedExtrinsic<Address, Signature, E>
238where
239 generic::UncheckedExtrinsic<Address, CallOf<E::Config>, Signature, E::Extension>: LazyExtrinsic,
240{
241 fn decode_unprefixed(data: &[u8]) -> Result<Self, codec::Error> {
242 Ok(Self(LazyExtrinsic::decode_unprefixed(data)?))
243 }
244}
245
246pub trait EthExtra {
248 type Config: Config + TxConfig;
250
251 type Extension: TransactionExtension<CallOf<Self::Config>>;
256
257 fn get_eth_extension(
264 nonce: <Self::Config as frame_system::Config>::Nonce,
265 tip: BalanceOf<Self::Config>,
266 ) -> Self::Extension;
267
268 fn try_into_checked_extrinsic(
276 payload: &[u8],
277 encoded_len: usize,
278 ) -> Result<
279 CheckedExtrinsic<AccountIdOf<Self::Config>, CallOf<Self::Config>, Self::Extension>,
280 InvalidTransaction,
281 >
282 where
283 <Self::Config as frame_system::Config>::Nonce: TryFrom<U256>,
284 CallOf<Self::Config>: SetWeightLimit,
285 {
286 let tx = TransactionSigned::decode(&payload).map_err(|err| {
287 log::debug!(target: LOG_TARGET, "Failed to decode transaction: {err:?}");
288 InvalidTransaction::Call
289 })?;
290
291 match &tx {
293 crate::evm::api::TransactionSigned::Transaction1559Signed(_) |
294 crate::evm::api::TransactionSigned::Transaction2930Signed(_) |
295 crate::evm::api::TransactionSigned::TransactionLegacySigned(_) => {
296 },
298 crate::evm::api::TransactionSigned::Transaction7702Signed(_) => {
299 log::debug!(target: LOG_TARGET, "EIP-7702 transactions are not supported");
300 return Err(InvalidTransaction::Call);
301 },
302 crate::evm::api::TransactionSigned::Transaction4844Signed(_) => {
303 log::debug!(target: LOG_TARGET, "EIP-4844 transactions are not supported");
304 return Err(InvalidTransaction::Call);
305 },
306 }
307
308 let signer_addr = tx.recover_eth_address().map_err(|err| {
309 log::debug!(target: LOG_TARGET, "Failed to recover signer: {err:?}");
310 InvalidTransaction::BadProof
311 })?;
312
313 let signer = <Self::Config as Config>::AddressMapper::to_fallback_account_id(&signer_addr);
314 let base_fee = <Pallet<Self::Config>>::evm_base_fee();
315 let tx = GenericTransaction::from_signed(tx, base_fee, None);
316 let nonce = tx.nonce.unwrap_or_default().try_into().map_err(|_| {
317 log::debug!(target: LOG_TARGET, "Failed to convert nonce");
318 InvalidTransaction::Call
319 })?;
320
321 log::debug!(target: LOG_TARGET, "Decoded Ethereum transaction with signer: {signer_addr:?} nonce: {nonce:?}");
322 let call_info = tx.into_call::<Self::Config>(CreateCallMode::ExtrinsicExecution(
323 encoded_len as u32,
324 payload.to_vec(),
325 ))?;
326 let storage_credit = <Self::Config as Config>::Currency::withdraw(
327 &signer,
328 call_info.storage_deposit,
329 Precision::Exact,
330 Preservation::Preserve,
331 Fortitude::Polite,
332 ).map_err(|_| {
333 log::debug!(target: LOG_TARGET, "Not enough balance to hold additional storage deposit of {:?}", call_info.storage_deposit);
334 InvalidTransaction::Payment
335 })?;
336 <Self::Config as Config>::FeeInfo::deposit_txfee(storage_credit);
337
338 crate::tracing::if_tracing(|tracer| {
339 tracer.watch_address(&Pallet::<Self::Config>::block_author());
340 tracer.watch_address(&signer_addr);
341 });
342
343 log::debug!(target: LOG_TARGET, "\
344 Created checked Ethereum transaction with: \
345 from={signer_addr:?} \
346 eth_gas={} \
347 encoded_len={encoded_len} \
348 tx_fee={:?} \
349 storage_deposit={:?} \
350 weight_limit={} \
351 nonce={nonce:?}\
352 ",
353 call_info.eth_gas_limit,
354 call_info.tx_fee,
355 call_info.storage_deposit,
356 call_info.weight_limit,
357 );
358
359 Ok(CheckedExtrinsic {
362 format: ExtrinsicFormat::Signed(
363 signer.into(),
364 Self::get_eth_extension(nonce, Zero::zero()),
365 ),
366 function: call_info.call,
367 })
368 }
369}
370
371#[cfg(test)]
372mod test {
373 use super::*;
374 use crate::{
375 EthTransactInfo, RUNTIME_PALLETS_ADDR, Weight,
376 evm::*,
377 test_utils::*,
378 tests::{
379 Address, ExtBuilder, RuntimeCall, RuntimeOrigin, SignedExtra, Test, UncheckedExtrinsic,
380 },
381 };
382 use frame_support::{error::LookupError, traits::fungible::Mutate};
383 use pallet_revive_fixtures::compile_module;
384 use sp_runtime::traits::{self, Checkable, DispatchTransaction};
385
386 type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
387
388 struct TestContext;
389
390 impl traits::Lookup for TestContext {
391 type Source = Address;
392 type Target = AccountIdOf<Test>;
393 fn lookup(&self, s: Self::Source) -> Result<Self::Target, LookupError> {
394 match s {
395 Self::Source::Id(id) => Ok(id),
396 _ => Err(LookupError),
397 }
398 }
399 }
400
401 #[derive(Clone)]
403 struct UncheckedExtrinsicBuilder {
404 tx: GenericTransaction,
405 before_validate: Option<std::sync::Arc<dyn Fn() + Send + Sync>>,
406 dry_run: Option<EthTransactInfo<BalanceOf<Test>>>,
407 }
408
409 impl UncheckedExtrinsicBuilder {
410 fn new() -> Self {
412 Self {
413 tx: GenericTransaction {
414 from: Some(Account::default().address()),
415 chain_id: Some(<Test as Config>::ChainId::get().into()),
416 ..Default::default()
417 },
418 before_validate: None,
419 dry_run: None,
420 }
421 }
422
423 fn data(mut self, data: Vec<u8>) -> Self {
424 self.tx.input = Bytes(data).into();
425 self
426 }
427
428 fn fund_account(account: &Account) {
429 let _ = <Test as Config>::Currency::set_balance(
430 &account.substrate_account(),
431 100_000_000_000_000,
432 );
433 }
434
435 fn estimate_gas(&mut self) {
436 let account = Account::default();
437 Self::fund_account(&account);
438
439 let dry_run =
440 crate::Pallet::<Test>::dry_run_eth_transact(self.tx.clone(), Default::default());
441 self.tx.gas_price = Some(<Pallet<Test>>::evm_base_fee());
442
443 match dry_run {
444 Ok(dry_run) => {
445 self.tx.gas = Some(dry_run.eth_gas);
446 self.dry_run = Some(dry_run);
447 },
448 Err(err) => {
449 log::debug!(target: LOG_TARGET, "Failed to estimate gas: {:?}", err);
450 },
451 }
452 }
453
454 fn call_with(dest: H160) -> Self {
456 let mut builder = Self::new();
457 builder.tx.to = Some(dest);
458 builder
459 }
460
461 fn instantiate_with(code: Vec<u8>, data: Vec<u8>) -> Self {
463 let mut builder = Self::new();
464 builder.tx.input = Bytes(code.into_iter().chain(data.into_iter()).collect()).into();
465 builder
466 }
467
468 fn before_validate(mut self, f: impl Fn() + Send + Sync + 'static) -> Self {
470 self.before_validate = Some(std::sync::Arc::new(f));
471 self
472 }
473
474 fn check(
475 self,
476 ) -> Result<
477 (u32, RuntimeCall, SignedExtra, GenericTransaction, Weight, TransactionSigned),
478 TransactionValidityError,
479 > {
480 self.mutate_estimate_and_check(Box::new(|_| ()))
481 }
482
483 fn mutate_estimate_and_check(
485 mut self,
486 f: Box<dyn FnOnce(&mut GenericTransaction) -> ()>,
487 ) -> Result<
488 (u32, RuntimeCall, SignedExtra, GenericTransaction, Weight, TransactionSigned),
489 TransactionValidityError,
490 > {
491 ExtBuilder::default().build().execute_with(|| self.estimate_gas());
492 ExtBuilder::default().build().execute_with(|| {
493 f(&mut self.tx);
494 let UncheckedExtrinsicBuilder { tx, before_validate, .. } = self.clone();
495
496 let account = Account::default();
498 Self::fund_account(&account);
499
500 let signed_transaction =
501 account.sign_transaction(tx.clone().try_into_unsigned().unwrap());
502 let call = RuntimeCall::Contracts(crate::Call::eth_transact {
503 payload: signed_transaction.signed_payload().clone(),
504 });
505
506 let uxt: UncheckedExtrinsic = generic::UncheckedExtrinsic::new_bare(call).into();
507 let encoded_len = uxt.encoded_size();
508 let result: CheckedExtrinsic<_, _, _> = uxt.check(&TestContext {})?;
509 let (account_id, extra): (AccountId32, SignedExtra) = match result.format {
510 ExtrinsicFormat::Signed(signer, extra) => (signer, extra),
511 _ => unreachable!(),
512 };
513
514 before_validate.map(|f| f());
515 extra.clone().validate_and_prepare(
516 RuntimeOrigin::signed(account_id),
517 &result.function,
518 &result.function.get_dispatch_info(),
519 encoded_len,
520 0,
521 )?;
522
523 Ok((
524 encoded_len as u32,
525 result.function,
526 extra,
527 tx,
528 self.dry_run.unwrap().weight_required,
529 signed_transaction,
530 ))
531 })
532 }
533 }
534
535 #[test]
536 fn check_eth_transact_call_works() {
537 let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20]));
538 let (expected_encoded_len, call, _, tx, weight_required, signed_transaction) =
539 builder.check().unwrap();
540 let expected_effective_gas_price =
541 ExtBuilder::default().build().execute_with(|| Pallet::<Test>::evm_base_fee());
542
543 match call {
544 RuntimeCall::Contracts(crate::Call::eth_call::<Test> {
545 dest,
546 value,
547 weight_limit,
548 data,
549 transaction_encoded,
550 effective_gas_price,
551 encoded_len,
552 ..
553 }) if dest == tx.to.unwrap() &&
554 value == tx.value.unwrap_or_default().as_u64().into() &&
555 data == tx.input.to_vec() &&
556 transaction_encoded == signed_transaction.signed_payload() &&
557 effective_gas_price == expected_effective_gas_price =>
558 {
559 assert_eq!(encoded_len, expected_encoded_len);
560 assert!(
561 weight_limit.all_gte(weight_required),
562 "Assert failed: weight_limit={weight_limit:?} >= weight_required={weight_required:?}"
563 );
564 },
565 _ => panic!("Call does not match."),
566 }
567 }
568
569 #[test]
570 fn check_eth_transact_instantiate_works() {
571 let (expected_code, _) = compile_module("dummy").unwrap();
572 let expected_data = vec![];
573 let builder = UncheckedExtrinsicBuilder::instantiate_with(
574 expected_code.clone(),
575 expected_data.clone(),
576 );
577 let (expected_encoded_len, call, _, tx, weight_required, signed_transaction) =
578 builder.check().unwrap();
579 let expected_effective_gas_price =
580 ExtBuilder::default().build().execute_with(|| Pallet::<Test>::evm_base_fee());
581 let expected_value = tx.value.unwrap_or_default().as_u64().into();
582
583 match call {
584 RuntimeCall::Contracts(crate::Call::eth_instantiate_with_code::<Test> {
585 value,
586 weight_limit,
587 code,
588 data,
589 transaction_encoded,
590 effective_gas_price,
591 encoded_len,
592 ..
593 }) if value == expected_value &&
594 code == expected_code &&
595 data == expected_data &&
596 transaction_encoded == signed_transaction.signed_payload() &&
597 effective_gas_price == expected_effective_gas_price =>
598 {
599 assert_eq!(encoded_len, expected_encoded_len);
600 assert!(
601 weight_limit.all_gte(weight_required),
602 "Assert failed: weight_limit={weight_limit:?} >= weight_required={weight_required:?}"
603 );
604 },
605 _ => panic!("Call does not match."),
606 }
607 }
608
609 #[test]
610 fn check_eth_transact_nonce_works() {
611 let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20]));
612
613 assert_eq!(
614 builder.mutate_estimate_and_check(Box::new(|tx| tx.nonce = Some(1u32.into()))),
615 Err(TransactionValidityError::Invalid(InvalidTransaction::Future))
616 );
617
618 let builder =
619 UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20])).before_validate(|| {
620 <crate::System<Test>>::inc_account_nonce(Account::default().substrate_account());
621 });
622
623 assert_eq!(
624 builder.check(),
625 Err(TransactionValidityError::Invalid(InvalidTransaction::Stale))
626 );
627 }
628
629 #[test]
630 fn check_eth_transact_chain_id_works() {
631 let builder = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20]));
632
633 assert_eq!(
634 builder.mutate_estimate_and_check(Box::new(|tx| tx.chain_id = Some(42.into()))),
635 Err(TransactionValidityError::Invalid(InvalidTransaction::Call))
636 );
637 }
638
639 #[test]
640 fn check_instantiate_data() {
641 let code: Vec<u8> = polkavm_common::program::BLOB_MAGIC
642 .into_iter()
643 .chain(b"invalid code".iter().cloned())
644 .collect();
645 let data = vec![1];
646
647 let builder = UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone());
648
649 assert_eq!(
651 builder.check(),
652 Err(TransactionValidityError::Invalid(InvalidTransaction::Call))
653 );
654 }
655
656 #[test]
657 fn check_transaction_fees() {
658 let scenarios: Vec<(_, Box<dyn FnOnce(&mut GenericTransaction)>, _)> = vec![
659 (
660 "Eth fees too low",
661 Box::new(|tx| {
662 tx.gas_price = Some(100u64.into());
663 }),
664 InvalidTransaction::Payment,
665 ),
666 (
667 "Gas fees too low",
668 Box::new(|tx| {
669 tx.gas = Some(tx.gas.unwrap() / 2);
670 }),
671 InvalidTransaction::Payment,
672 ),
673 ];
674
675 for (msg, update_tx, err) in scenarios {
676 let res = UncheckedExtrinsicBuilder::call_with(H160::from([1u8; 20]))
677 .mutate_estimate_and_check(update_tx);
678
679 assert_eq!(res, Err(TransactionValidityError::Invalid(err)), "{}", msg);
680 }
681 }
682
683 #[test]
684 fn check_transaction_tip() {
685 let (code, _) = compile_module("dummy").unwrap();
686 let data = vec![42u8; crate::limits::CALLDATA_BYTES as usize];
688 let (_, _, extra, _tx, _gas_required, _) =
689 UncheckedExtrinsicBuilder::instantiate_with(code.clone(), data.clone())
690 .mutate_estimate_and_check(Box::new(|tx| {
691 tx.gas_price = Some(tx.gas_price.unwrap() * 103 / 100);
692 log::debug!(target: LOG_TARGET, "Gas price: {:?}", tx.gas_price);
693 }))
694 .unwrap();
695
696 assert_eq!(U256::from(extra.1.tip()), 0u32.into());
697 }
698
699 #[test]
700 fn check_runtime_pallets_addr_works() {
701 let remark: CallOf<Test> =
702 frame_system::Call::remark { remark: b"Hello, world!".to_vec() }.into();
703
704 let builder =
705 UncheckedExtrinsicBuilder::call_with(RUNTIME_PALLETS_ADDR).data(remark.encode());
706 let (_, call, _, _, _, _) = builder.check().unwrap();
707
708 match call {
709 RuntimeCall::Contracts(crate::Call::eth_substrate_call {
710 call: inner_call, ..
711 }) => {
712 assert_eq!(*inner_call, remark);
713 },
714 _ => panic!("Expected the RuntimeCall::Contracts variant, got: {:?}", call),
715 }
716 }
717
718 #[test]
728 fn contract_deployment_with_nick_method_works() {
729 let raw_transaction_bytes = alloy_core::hex!(
731 "0xf9016c8085174876e8008303c4d88080b90154608060405234801561001057600080fd5b50610134806100206000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c80634af63f0214602d575b600080fd5b60cf60048036036040811015604157600080fd5b810190602081018135640100000000811115605b57600080fd5b820183602082011115606c57600080fd5b80359060200191846001830284011164010000000083111715608d57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550509135925060eb915050565b604080516001600160a01b039092168252519081900360200190f35b6000818351602085016000f5939250505056fea26469706673582212206b44f8a82cb6b156bfcc3dc6aadd6df4eefd204bc928a4397fd15dacf6d5320564736f6c634300060200331b83247000822470"
732 );
733
734 let mut signed_transaction = TransactionSigned::decode(raw_transaction_bytes.as_slice())
735 .expect("Invalid raw transaction bytes");
736 if let TransactionSigned::TransactionLegacySigned(ref mut legacy_transaction) =
737 signed_transaction
738 {
739 legacy_transaction.transaction_legacy_unsigned.gas =
740 U256::from_dec_str("3750815700000").unwrap();
741 }
742 let generic_transaction = GenericTransaction::from_signed(
743 signed_transaction.clone(),
744 ExtBuilder::default().build().execute_with(|| Pallet::<Test>::evm_base_fee()),
745 None,
746 );
747
748 let unchecked_extrinsic_builder = UncheckedExtrinsicBuilder {
749 tx: generic_transaction,
750 before_validate: None,
751 dry_run: None,
752 };
753
754 let eth_transact_result = unchecked_extrinsic_builder.check();
756
757 let (
759 _encoded_len,
760 _function,
761 _extra,
762 generic_transaction,
763 _gas_required,
764 _signed_transaction,
765 ) = eth_transact_result.expect("eth_transact failed");
766 assert!(
767 generic_transaction.chain_id.is_none(),
768 "Chain Id in the generic transaction is not None"
769 );
770 }
771}