1use crate::{
21 BalanceOf, CallOf, Config, GenericTransaction, LOG_TARGET, Pallet, RUNTIME_PALLETS_ADDR,
22 Weight, Zero,
23 evm::{
24 TYPE_EIP7702, TYPE_LEGACY,
25 fees::{InfoT, compute_max_integer_quotient},
26 runtime::SetWeightLimit,
27 },
28 extract_code_and_data,
29 metering::EthTxInfo,
30};
31use alloc::{boxed::Box, vec::Vec};
32use codec::DecodeLimit;
33use frame_support::MAX_EXTRINSIC_DEPTH;
34use sp_core::{Get, U256};
35use sp_runtime::{SaturatedConversion, Saturating, transaction_validity::InvalidTransaction};
36
37pub struct CallInfo<T: Config> {
39 pub call: CallOf<T>,
43 pub weight_limit: Weight,
45 pub encoded_len: u32,
47 pub tx_fee: BalanceOf<T>,
49 pub storage_deposit: BalanceOf<T>,
51 pub eth_gas_limit: U256,
53 pub authorization_list: Vec<crate::evm::AuthorizationListEntry>,
55}
56
57#[derive(Debug, PartialEq, Eq, Clone)]
59pub enum CreateCallMode {
60 ExtrinsicExecution(u32, Vec<u8>),
63 DryRun,
65}
66
67impl GenericTransaction {
68 pub fn into_call<T>(self, mode: CreateCallMode) -> Result<CallInfo<T>, InvalidTransaction>
70 where
71 T: Config,
72 CallOf<T>: SetWeightLimit,
73 {
74 let is_dry_run = matches!(mode, CreateCallMode::DryRun);
75 let base_fee = <Pallet<T>>::evm_base_fee();
76
77 match (self.chain_id, self.r#type.as_ref()) {
86 (None, Some(super::Byte(TYPE_LEGACY))) => {},
87 (Some(chain_id), ..) => {
88 if chain_id != <T as Config>::ChainId::get().into() {
89 log::debug!(target: LOG_TARGET, "Invalid chain_id {chain_id:?}");
90 return Err(InvalidTransaction::Call);
91 }
92 },
93 (None, ..) => {
94 log::debug!(target: LOG_TARGET, "Invalid chain_id None");
95 return Err(InvalidTransaction::Call);
96 },
97 }
98
99 let Some(gas) = self.gas else {
100 log::debug!(target: LOG_TARGET, "No gas provided");
101 return Err(InvalidTransaction::Call);
102 };
103
104 if let Some(super::Byte(TYPE_EIP7702)) = self.r#type.as_ref() {
106 if self.to.is_none() {
107 log::debug!(target: LOG_TARGET, "EIP-7702 transactions require non-null destination");
108 return Err(InvalidTransaction::Call);
109 }
110
111 if self.authorization_list.is_empty() {
113 log::debug!(target: LOG_TARGET, "EIP-7702 transactions require non-empty authorization list");
114 return Err(InvalidTransaction::Call);
115 }
116
117 if self.to == Some(RUNTIME_PALLETS_ADDR) {
121 log::debug!(target: LOG_TARGET, "EIP-7702 transactions cannot target RUNTIME_PALLETS_ADDR");
122 return Err(InvalidTransaction::Call);
123 }
124 }
125
126 for auth in self.authorization_list.iter() {
132 if auth.nonce.bits() > 64 {
133 log::debug!(
134 target: LOG_TARGET,
135 "EIP-7702 authorization nonce exceeds 2^64: {:?}",
136 auth.nonce,
137 );
138 return Err(InvalidTransaction::Call);
139 }
140 if auth.y_parity.bits() > 8 {
141 log::debug!(
142 target: LOG_TARGET,
143 "EIP-7702 authorization y_parity exceeds 2^8: {:?}",
144 auth.y_parity,
145 );
146 return Err(InvalidTransaction::Call);
147 }
148 }
149
150 let Some(effective_gas_price) = self.gas_price else {
154 log::debug!(target: LOG_TARGET, "No gas_price provided.");
155 return Err(InvalidTransaction::Payment);
156 };
157
158 if effective_gas_price < base_fee {
159 log::debug!(
160 target: LOG_TARGET,
161 "Specified gas_price is too low. effective_gas_price={effective_gas_price} base_fee={base_fee}"
162 );
163 return Err(InvalidTransaction::Payment);
164 }
165
166 let (encoded_len, transaction_encoded) =
167 if let CreateCallMode::ExtrinsicExecution(encoded_len, transaction_encoded) = mode {
168 (encoded_len, transaction_encoded)
169 } else {
170 let mut maximized_tx = self.clone();
173 let maximized_base_fee = base_fee.saturating_mul(256.into());
174 maximized_tx.gas = Some(u64::MAX.into());
175 maximized_tx.gas_price = Some(maximized_base_fee);
176 maximized_tx.max_fee_per_gas = Some(maximized_base_fee);
177 maximized_tx.max_priority_fee_per_gas = Some(maximized_base_fee);
178
179 let unsigned_tx = maximized_tx.try_into_unsigned().map_err(|_| {
180 log::debug!(target: LOG_TARGET, "Invalid transaction type.");
181 InvalidTransaction::Call
182 })?;
183 let transaction_encoded = unsigned_tx.dummy_signed_payload();
184
185 let eth_transact_call =
186 crate::Call::<T>::eth_transact { payload: transaction_encoded.clone() };
187 (<T as Config>::FeeInfo::encoded_len(eth_transact_call.into()), transaction_encoded)
188 };
189
190 let value = self.value.unwrap_or_default();
191 let data = self.input.to_vec();
192
193 let mut call = if let Some(dest) = self.to {
194 if dest == RUNTIME_PALLETS_ADDR {
195 let call =
196 CallOf::<T>::decode_all_with_depth_limit(MAX_EXTRINSIC_DEPTH, &mut &data[..])
197 .map_err(|_| {
198 log::debug!(target: LOG_TARGET, "Failed to decode data as Call");
199 InvalidTransaction::Call
200 })?;
201
202 if !value.is_zero() {
203 log::debug!(target: LOG_TARGET, "Runtime pallets address cannot be called with value");
204 return Err(InvalidTransaction::Call);
205 }
206
207 crate::Call::eth_substrate_call::<T> { call: Box::new(call), transaction_encoded }
208 .into()
209 } else {
210 crate::Call::eth_call::<T> {
211 dest,
212 value,
213 weight_limit: Zero::zero(),
214 eth_gas_limit: gas,
215 data,
216 transaction_encoded,
217 effective_gas_price,
218 encoded_len,
219 authorization_list: self.authorization_list.clone(),
220 }
221 .into()
222 }
223 } else {
224 let (code, data) = if data.starts_with(&polkavm_common::program::BLOB_MAGIC) {
225 let Some((code, data)) = extract_code_and_data(&data) else {
226 log::debug!(target: LOG_TARGET, "Failed to extract polkavm code & data");
227 return Err(InvalidTransaction::Call);
228 };
229 (code, data)
230 } else {
231 (data, Default::default())
232 };
233
234 let call = crate::Call::eth_instantiate_with_code::<T> {
235 value,
236 weight_limit: Zero::zero(),
237 eth_gas_limit: gas,
238 code,
239 data,
240 transaction_encoded,
241 effective_gas_price,
242 encoded_len,
243 }
244 .into();
245
246 call
247 };
248
249 let eth_fee =
251 effective_gas_price.saturating_mul(gas) / <T as Config>::NativeToEthRatio::get();
252
253 let weight_limit = {
254 let fixed_fee = <T as Config>::FeeInfo::fixed_fee(encoded_len as u32);
255 let info = <T as Config>::FeeInfo::dispatch_info(&call);
256
257 let remaining_fee = {
258 let adjusted = eth_fee.checked_sub(fixed_fee.into()).ok_or_else(|| {
259 log::debug!(target: LOG_TARGET, "Not enough gas supplied to cover base and len fee. eth_fee={eth_fee:?} fixed_fee={fixed_fee:?}");
260 InvalidTransaction::Payment
261 })?;
262
263 let unadjusted = compute_max_integer_quotient(
264 <T as Config>::FeeInfo::next_fee_multiplier(),
265 <BalanceOf<T>>::saturated_from(adjusted),
266 );
267
268 unadjusted
269 };
270 let remaining_fee_weight = <T as Config>::FeeInfo::fee_to_weight(remaining_fee);
271 let weight_limit = remaining_fee_weight
272 .checked_sub(&info.total_weight()).ok_or_else(|| {
273 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(),);
274 InvalidTransaction::Payment
275 })?;
276
277 call.set_weight_limit(weight_limit);
278
279 if !is_dry_run {
280 let max_weight = <Pallet<T>>::evm_max_extrinsic_weight();
281 let info = <T as Config>::FeeInfo::dispatch_info(&call);
282 let overweight_by = info.total_weight().saturating_sub(max_weight);
283 let capped_weight = weight_limit.saturating_sub(overweight_by);
284 call.set_weight_limit(capped_weight);
285 capped_weight
286 } else {
287 weight_limit
288 }
289 };
290
291 let tx_fee = <T as Config>::FeeInfo::tx_fee(encoded_len, &call);
293
294 let storage_deposit = eth_fee.checked_sub(tx_fee.into()).ok_or_else(|| {
296 log::error!(target: LOG_TARGET, "The eth_fee={eth_fee:?} is smaller than the tx_fee={tx_fee:?}. This is a bug.");
297 InvalidTransaction::Payment
298 })?.saturated_into();
299
300 if !self.authorization_list.is_empty() {
303 let info = <T as Config>::FeeInfo::base_dispatch_info(&mut call);
304 let max_deposit = EthTxInfo::<T>::new(encoded_len, info.total_weight())
305 .max_deposit(gas.saturated_into());
306
307 let auth_cost = <Pallet<T>>::worst_case_delegation_deposit()
308 .saturating_mul(self.authorization_list.len().saturated_into());
309
310 if max_deposit < auth_cost {
311 log::debug!(
312 target: LOG_TARGET,
313 "Not enough gas to cover deposits for authorization accounts. \
314 max_deposit={max_deposit:?} required={auth_cost:?}"
315 );
316 return Err(InvalidTransaction::Payment);
317 }
318 }
319
320 Ok(CallInfo {
321 call,
322 weight_limit,
323 encoded_len,
324 tx_fee,
325 storage_deposit,
326 eth_gas_limit: gas,
327 authorization_list: self.authorization_list,
328 })
329 }
330}