Skip to main content

pallet_revive/evm/
runtime.rs

1// This file is part of Substrate.
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//! Runtime types for integrating `pallet-revive` with the EVM.
18use 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
48/// Used to set the weight limit argument of a `eth_call` or `eth_instantiate_with_code` call.
49pub trait SetWeightLimit {
50	/// Set the weight limit of this call.
51	///
52	/// Returns the replaced weight.
53	fn set_weight_limit(&mut self, weight_limit: Weight) -> Weight;
54}
55
56/// Wraps [`generic::UncheckedExtrinsic`] to support checking unsigned
57/// [`crate::Call::eth_transact`] extrinsic.
58#[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	// required by Checkable for `generic::UncheckedExtrinsic`
129	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
246/// EthExtra convert an unsigned [`crate::Call::eth_transact`] into a [`CheckedExtrinsic`].
247pub trait EthExtra {
248	/// The Runtime configuration.
249	type Config: Config + TxConfig;
250
251	/// The Runtime's transaction extension.
252	/// It should include at least:
253	/// - [`frame_system::CheckNonce`] to ensure that the nonce from the Ethereum transaction is
254	///   correct.
255	type Extension: TransactionExtension<CallOf<Self::Config>>;
256
257	/// Get the transaction extension to apply to an unsigned [`crate::Call::eth_transact`]
258	/// extrinsic.
259	///
260	/// # Parameters
261	/// - `nonce`: The nonce extracted from the Ethereum transaction.
262	/// - `tip`: The transaction tip calculated from the Ethereum transaction.
263	fn get_eth_extension(
264		nonce: <Self::Config as frame_system::Config>::Nonce,
265		tip: BalanceOf<Self::Config>,
266	) -> Self::Extension;
267
268	/// Convert the unsigned [`crate::Call::eth_transact`] into a [`CheckedExtrinsic`].
269	/// and ensure that the fees from the Ethereum transaction correspond to the fees computed from
270	/// the encoded_len and the injected weight_limit.
271	///
272	/// # Parameters
273	/// - `payload`: The RLP-encoded Ethereum transaction.
274	/// - `encoded_len`: The encoded length of the extrinsic.
275	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		// Check transaction type and reject unsupported transaction types
292		match &tx {
293			crate::evm::api::TransactionSigned::Transaction1559Signed(_) |
294			crate::evm::api::TransactionSigned::Transaction2930Signed(_) |
295			crate::evm::api::TransactionSigned::TransactionLegacySigned(_) => {
296				// Supported transaction types, continue processing
297			},
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		// We can't calculate a tip because it needs to be based on the actual gas used which we
360		// cannot know pre-dispatch. Hence we never supply a tip here or it would be way too high.
361		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	/// A builder for creating an unchecked extrinsic, and test that the check function works.
402	#[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		/// Create a new builder with default values.
411		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		/// Create a new builder with a call to the given address.
455		fn call_with(dest: H160) -> Self {
456			let mut builder = Self::new();
457			builder.tx.to = Some(dest);
458			builder
459		}
460
461		/// Create a new builder with an instantiate call.
462		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		/// Set before_validate function.
469		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		/// Call `check` on the unchecked extrinsic, and `pre_dispatch` on the signed extension.
484		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				// Fund the account.
497				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		// Fail because the tx input fail to get the blob length
650		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		// create some dummy data to increase the gas fee
687		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	/// The raw bytes seen in this test is of a deployment transaction from [eip-2470] which publish
719	/// a contract at a predicable address on any chain that it's run on. We use these bytes to test
720	/// that if we were to run this transaction on pallet-revive that it would run and also produce
721	/// a contract at the address described in the EIP.
722	///
723	/// Note: the linked EIP is not an EIP for Nick's method, it's just an EIP that makes use of
724	/// Nick's method.
725	///
726	/// [eip-2470]: https://eips.ethereum.org/EIPS/eip-2470
727	#[test]
728	fn contract_deployment_with_nick_method_works() {
729		// Arrange
730		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		// Act
755		let eth_transact_result = unchecked_extrinsic_builder.check();
756
757		// Assert
758		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}