referrerpolicy=no-referrer-when-downgrade

pallet_revive/evm/
call.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
18//! Functionality to decode an eth transaction into an dispatchable call.
19
20use crate::{
21	BalanceOf, CallOf, Config, GenericTransaction, LOG_TARGET, Pallet, RUNTIME_PALLETS_ADDR,
22	Weight, Zero,
23	evm::{
24		TYPE_LEGACY,
25		fees::{InfoT, compute_max_integer_quotient},
26		runtime::SetWeightLimit,
27	},
28	extract_code_and_data,
29};
30use alloc::{boxed::Box, vec::Vec};
31use codec::DecodeLimit;
32use frame_support::MAX_EXTRINSIC_DEPTH;
33use sp_core::{Get, U256};
34use sp_runtime::{SaturatedConversion, transaction_validity::InvalidTransaction};
35
36/// Result of decoding an eth transaction into a dispatchable call.
37pub struct CallInfo<T: Config> {
38	/// The dispatchable call with the correct weights assigned.
39	///
40	/// This will be either `eth_call` or `eth_instantiate_with_code`.
41	pub call: CallOf<T>,
42	/// The weight that was set inside [`Self::call`].
43	pub weight_limit: Weight,
44	/// The encoded length of the bare transaction carrying the ethereum payload.
45	pub encoded_len: u32,
46	/// The adjusted transaction fee of [`Self::call`].
47	pub tx_fee: BalanceOf<T>,
48	/// The additional storage deposit to be deposited into the txhold.
49	pub storage_deposit: BalanceOf<T>,
50	/// The ethereum gas limit of the transaction.
51	pub eth_gas_limit: U256,
52}
53
54/// Mode for creating a call from an ethereum transaction.
55#[derive(Debug, PartialEq, Eq, Clone)]
56pub enum CreateCallMode {
57	/// Mode for extrinsic execution. Carries the encoding length of the extrinsic and the
58	/// RLP-encoded Ethereum transaction
59	ExtrinsicExecution(u32, Vec<u8>),
60	/// Mode for dry running
61	DryRun,
62}
63
64impl GenericTransaction {
65	/// Decode `tx` into a dispatchable call.
66	pub fn into_call<T>(self, mode: CreateCallMode) -> Result<CallInfo<T>, InvalidTransaction>
67	where
68		T: Config,
69		CallOf<T>: SetWeightLimit,
70	{
71		let is_dry_run = matches!(mode, CreateCallMode::DryRun);
72		let base_fee = <Pallet<T>>::evm_base_fee();
73
74		// We would like to allow for transactions without a chain id to be executed through pallet
75		// revive. These are called unprotected transactions and they are transactions that predate
76		// EIP-155 which do not include a Chain ID. These transactions are still useful today in
77		// certain patterns in Ethereum such as "Nick's Method" for contract deployment which
78		// allows a contract to be deployed on all chains with the same address. This is only
79		// allowed for legacy transactions and isn't allowed for any other transaction type.
80		// * Here's a relevant EIP: https://eips.ethereum.org/EIPS/eip-2470
81		// * Here's Nick's article: https://weka.medium.com/how-to-send-ether-to-11-440-people-187e332566b7
82		match (self.chain_id, self.r#type.as_ref()) {
83			(None, Some(super::Byte(TYPE_LEGACY))) => {},
84			(Some(chain_id), ..) => {
85				if chain_id != <T as Config>::ChainId::get().into() {
86					log::debug!(target: LOG_TARGET, "Invalid chain_id {chain_id:?}");
87					return Err(InvalidTransaction::Call);
88				}
89			},
90			(None, ..) => {
91				log::debug!(target: LOG_TARGET, "Invalid chain_id None");
92				return Err(InvalidTransaction::Call);
93			},
94		}
95
96		let Some(gas) = self.gas else {
97			log::debug!(target: LOG_TARGET, "No gas provided");
98			return Err(InvalidTransaction::Call);
99		};
100
101		// Currently, effective_gas_price will always be the same as base_fee
102		// Because all callers of `into_call` will prepare `tx` that way. Some of the subsequent
103		// logic will not work correctly anymore if we change that assumption.
104		let Some(effective_gas_price) = self.gas_price else {
105			log::debug!(target: LOG_TARGET, "No gas_price provided.");
106			return Err(InvalidTransaction::Payment);
107		};
108
109		if effective_gas_price < base_fee {
110			log::debug!(
111				target: LOG_TARGET,
112				"Specified gas_price is too low. effective_gas_price={effective_gas_price} base_fee={base_fee}"
113			);
114			return Err(InvalidTransaction::Payment);
115		}
116
117		let (encoded_len, transaction_encoded) =
118			if let CreateCallMode::ExtrinsicExecution(encoded_len, transaction_encoded) = mode {
119				(encoded_len, transaction_encoded)
120			} else {
121				// For dry runs, we need to ensure that the RLP encoding length is at least the
122				// length of the encoding of the actual transaction submitted later
123				let mut maximized_tx = self.clone();
124				let maximized_base_fee = base_fee.saturating_mul(256.into());
125				maximized_tx.gas = Some(u64::MAX.into());
126				maximized_tx.gas_price = Some(maximized_base_fee);
127				maximized_tx.max_priority_fee_per_gas = Some(maximized_base_fee);
128
129				let unsigned_tx = maximized_tx.try_into_unsigned().map_err(|_| {
130					log::debug!(target: LOG_TARGET, "Invalid transaction type.");
131					InvalidTransaction::Call
132				})?;
133				let transaction_encoded = unsigned_tx.dummy_signed_payload();
134
135				let eth_transact_call =
136					crate::Call::<T>::eth_transact { payload: transaction_encoded.clone() };
137				(<T as Config>::FeeInfo::encoded_len(eth_transact_call.into()), transaction_encoded)
138			};
139
140		let value = self.value.unwrap_or_default();
141		let data = self.input.to_vec();
142
143		let mut call = if let Some(dest) = self.to {
144			if dest == RUNTIME_PALLETS_ADDR {
145				let call =
146					CallOf::<T>::decode_all_with_depth_limit(MAX_EXTRINSIC_DEPTH, &mut &data[..])
147						.map_err(|_| {
148						log::debug!(target: LOG_TARGET, "Failed to decode data as Call");
149						InvalidTransaction::Call
150					})?;
151
152				if !value.is_zero() {
153					log::debug!(target: LOG_TARGET, "Runtime pallets address cannot be called with value");
154					return Err(InvalidTransaction::Call);
155				}
156
157				crate::Call::eth_substrate_call::<T> { call: Box::new(call), transaction_encoded }
158					.into()
159			} else {
160				let call = crate::Call::eth_call::<T> {
161					dest,
162					value,
163					weight_limit: Zero::zero(),
164					eth_gas_limit: gas,
165					data,
166					transaction_encoded,
167					effective_gas_price,
168					encoded_len,
169				}
170				.into();
171				call
172			}
173		} else {
174			let (code, data) = if data.starts_with(&polkavm_common::program::BLOB_MAGIC) {
175				let Some((code, data)) = extract_code_and_data(&data) else {
176					log::debug!(target: LOG_TARGET, "Failed to extract polkavm code & data");
177					return Err(InvalidTransaction::Call);
178				};
179				(code, data)
180			} else {
181				(data, Default::default())
182			};
183
184			let call = crate::Call::eth_instantiate_with_code::<T> {
185				value,
186				weight_limit: Zero::zero(),
187				eth_gas_limit: gas,
188				code,
189				data,
190				transaction_encoded,
191				effective_gas_price,
192				encoded_len,
193			}
194			.into();
195
196			call
197		};
198
199		// the fee as signed off by the eth wallet. we cannot consume more.
200		let eth_fee =
201			effective_gas_price.saturating_mul(gas) / <T as Config>::NativeToEthRatio::get();
202
203		let weight_limit = {
204			let fixed_fee = <T as Config>::FeeInfo::fixed_fee(encoded_len as u32);
205			let info = <T as Config>::FeeInfo::dispatch_info(&call);
206
207			let remaining_fee = {
208				let adjusted = eth_fee.checked_sub(fixed_fee.into()).ok_or_else(|| {
209				log::debug!(target: LOG_TARGET, "Not enough gas supplied to cover base and len fee. eth_fee={eth_fee:?} fixed_fee={fixed_fee:?}");
210				InvalidTransaction::Payment
211			})?;
212
213				let unadjusted = compute_max_integer_quotient(
214					<T as Config>::FeeInfo::next_fee_multiplier(),
215					<BalanceOf<T>>::saturated_from(adjusted),
216				);
217
218				unadjusted
219			};
220			let remaining_fee_weight = <T as Config>::FeeInfo::fee_to_weight(remaining_fee);
221			let weight_limit = remaining_fee_weight
222			.checked_sub(&info.total_weight()).ok_or_else(|| {
223			log::debug!(target: LOG_TARGET, "Not enough gas supplied to cover the weight ({:?}) of the extrinsic. remaining_fee_weight: {remaining_fee_weight:?}", info.total_weight(),);
224			InvalidTransaction::Payment
225		})?;
226
227			call.set_weight_limit(weight_limit);
228
229			if !is_dry_run {
230				let max_weight = <Pallet<T>>::evm_max_extrinsic_weight();
231				let info = <T as Config>::FeeInfo::dispatch_info(&call);
232				let overweight_by = info.total_weight().saturating_sub(max_weight);
233				let capped_weight = weight_limit.saturating_sub(overweight_by);
234				call.set_weight_limit(capped_weight);
235				capped_weight
236			} else {
237				weight_limit
238			}
239		};
240
241		// the overall fee of the extrinsic including the gas limit
242		let tx_fee = <T as Config>::FeeInfo::tx_fee(encoded_len, &call);
243
244		// the leftover we make available to the deposit collection system
245		let storage_deposit = eth_fee.checked_sub(tx_fee.into()).ok_or_else(|| {
246			log::error!(target: LOG_TARGET, "The eth_fee={eth_fee:?} is smaller than the tx_fee={tx_fee:?}. This is a bug.");
247			InvalidTransaction::Payment
248		})?.saturated_into();
249
250		Ok(CallInfo {
251			call,
252			weight_limit,
253			encoded_len,
254			tx_fee,
255			storage_deposit,
256			eth_gas_limit: gas,
257		})
258	}
259}