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