use crate::{
BalanceOf, CallOf, Config, GenericTransaction, LOG_TARGET, Pallet, RUNTIME_PALLETS_ADDR,
Weight, Zero,
evm::{
TYPE_LEGACY,
fees::{InfoT, compute_max_integer_quotient},
runtime::SetWeightLimit,
},
extract_code_and_data,
};
use alloc::{boxed::Box, vec::Vec};
use codec::DecodeLimit;
use frame_support::MAX_EXTRINSIC_DEPTH;
use sp_core::{Get, U256};
use sp_runtime::{SaturatedConversion, transaction_validity::InvalidTransaction};
pub struct CallInfo<T: Config> {
pub call: CallOf<T>,
pub weight_limit: Weight,
pub encoded_len: u32,
pub tx_fee: BalanceOf<T>,
pub storage_deposit: BalanceOf<T>,
pub eth_gas_limit: U256,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum CreateCallMode {
ExtrinsicExecution(u32, Vec<u8>),
DryRun,
}
impl GenericTransaction {
pub fn into_call<T>(self, mode: CreateCallMode) -> Result<CallInfo<T>, InvalidTransaction>
where
T: Config,
CallOf<T>: SetWeightLimit,
{
let is_dry_run = matches!(mode, CreateCallMode::DryRun);
let base_fee = <Pallet<T>>::evm_base_fee();
match (self.chain_id, self.r#type.as_ref()) {
(None, Some(super::Byte(TYPE_LEGACY))) => {},
(Some(chain_id), ..) => {
if chain_id != <T as Config>::ChainId::get().into() {
log::debug!(target: LOG_TARGET, "Invalid chain_id {chain_id:?}");
return Err(InvalidTransaction::Call);
}
},
(None, ..) => {
log::debug!(target: LOG_TARGET, "Invalid chain_id None");
return Err(InvalidTransaction::Call);
},
}
let Some(gas) = self.gas else {
log::debug!(target: LOG_TARGET, "No gas provided");
return Err(InvalidTransaction::Call);
};
let Some(effective_gas_price) = self.gas_price else {
log::debug!(target: LOG_TARGET, "No gas_price provided.");
return Err(InvalidTransaction::Payment);
};
if effective_gas_price < base_fee {
log::debug!(
target: LOG_TARGET,
"Specified gas_price is too low. effective_gas_price={effective_gas_price} base_fee={base_fee}"
);
return Err(InvalidTransaction::Payment);
}
let (encoded_len, transaction_encoded) =
if let CreateCallMode::ExtrinsicExecution(encoded_len, transaction_encoded) = mode {
(encoded_len, transaction_encoded)
} else {
let mut maximized_tx = self.clone();
let maximized_base_fee = base_fee.saturating_mul(256.into());
maximized_tx.gas = Some(u64::MAX.into());
maximized_tx.gas_price = Some(maximized_base_fee);
maximized_tx.max_priority_fee_per_gas = Some(maximized_base_fee);
let unsigned_tx = maximized_tx.try_into_unsigned().map_err(|_| {
log::debug!(target: LOG_TARGET, "Invalid transaction type.");
InvalidTransaction::Call
})?;
let transaction_encoded = unsigned_tx.dummy_signed_payload();
let eth_transact_call =
crate::Call::<T>::eth_transact { payload: transaction_encoded.clone() };
(<T as Config>::FeeInfo::encoded_len(eth_transact_call.into()), transaction_encoded)
};
let value = self.value.unwrap_or_default();
let data = self.input.to_vec();
let mut call = if let Some(dest) = self.to {
if dest == RUNTIME_PALLETS_ADDR {
let call =
CallOf::<T>::decode_all_with_depth_limit(MAX_EXTRINSIC_DEPTH, &mut &data[..])
.map_err(|_| {
log::debug!(target: LOG_TARGET, "Failed to decode data as Call");
InvalidTransaction::Call
})?;
if !value.is_zero() {
log::debug!(target: LOG_TARGET, "Runtime pallets address cannot be called with value");
return Err(InvalidTransaction::Call);
}
crate::Call::eth_substrate_call::<T> { call: Box::new(call), transaction_encoded }
.into()
} else {
let call = crate::Call::eth_call::<T> {
dest,
value,
weight_limit: Zero::zero(),
eth_gas_limit: gas,
data,
transaction_encoded,
effective_gas_price,
encoded_len,
}
.into();
call
}
} else {
let (code, data) = if data.starts_with(&polkavm_common::program::BLOB_MAGIC) {
let Some((code, data)) = extract_code_and_data(&data) else {
log::debug!(target: LOG_TARGET, "Failed to extract polkavm code & data");
return Err(InvalidTransaction::Call);
};
(code, data)
} else {
(data, Default::default())
};
let call = crate::Call::eth_instantiate_with_code::<T> {
value,
weight_limit: Zero::zero(),
eth_gas_limit: gas,
code,
data,
transaction_encoded,
effective_gas_price,
encoded_len,
}
.into();
call
};
let eth_fee =
effective_gas_price.saturating_mul(gas) / <T as Config>::NativeToEthRatio::get();
let weight_limit = {
let fixed_fee = <T as Config>::FeeInfo::fixed_fee(encoded_len as u32);
let info = <T as Config>::FeeInfo::dispatch_info(&call);
let remaining_fee = {
let adjusted = eth_fee.checked_sub(fixed_fee.into()).ok_or_else(|| {
log::debug!(target: LOG_TARGET, "Not enough gas supplied to cover base and len fee. eth_fee={eth_fee:?} fixed_fee={fixed_fee:?}");
InvalidTransaction::Payment
})?;
let unadjusted = compute_max_integer_quotient(
<T as Config>::FeeInfo::next_fee_multiplier(),
<BalanceOf<T>>::saturated_from(adjusted),
);
unadjusted
};
let remaining_fee_weight = <T as Config>::FeeInfo::fee_to_weight(remaining_fee);
let weight_limit = remaining_fee_weight
.checked_sub(&info.total_weight()).ok_or_else(|| {
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(),);
InvalidTransaction::Payment
})?;
call.set_weight_limit(weight_limit);
if !is_dry_run {
let max_weight = <Pallet<T>>::evm_max_extrinsic_weight();
let info = <T as Config>::FeeInfo::dispatch_info(&call);
let overweight_by = info.total_weight().saturating_sub(max_weight);
let capped_weight = weight_limit.saturating_sub(overweight_by);
call.set_weight_limit(capped_weight);
capped_weight
} else {
weight_limit
}
};
let tx_fee = <T as Config>::FeeInfo::tx_fee(encoded_len, &call);
let storage_deposit = eth_fee.checked_sub(tx_fee.into()).ok_or_else(|| {
log::error!(target: LOG_TARGET, "The eth_fee={eth_fee:?} is smaller than the tx_fee={tx_fee:?}. This is a bug.");
InvalidTransaction::Payment
})?.saturated_into();
Ok(CallInfo {
call,
weight_limit,
encoded_len,
tx_fee,
storage_deposit,
eth_gas_limit: gas,
})
}
}