1use 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
36pub struct CallInfo<T: Config> {
38 pub call: CallOf<T>,
42 pub weight_limit: Weight,
44 pub encoded_len: u32,
46 pub tx_fee: BalanceOf<T>,
48 pub storage_deposit: BalanceOf<T>,
50 pub eth_gas_limit: U256,
52}
53
54#[derive(Debug, PartialEq, Eq, Clone)]
56pub enum CreateCallMode {
57 ExtrinsicExecution(u32, Vec<u8>),
60 DryRun,
62}
63
64impl GenericTransaction {
65 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 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 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 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 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 let tx_fee = <T as Config>::FeeInfo::tx_fee(encoded_len, &call);
243
244 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}