use super::{
BalanceOf, CallResources, Config, DispatchError, Error, EthTxInfo, FixedPointNumber, FixedU128,
FrameMeter, InfoT, ResourceMeter, RootStorageMeter, SaturatedConversion, SignedGas, State,
StorageDeposit, Token, TransactionLimits, TransactionMeter, Weight, WeightMeter, Zero,
};
use crate::vm::{RuntimeCosts, evm::EVMGas};
use core::marker::PhantomData;
use revm::interpreter::gas::CALL_STIPEND;
const STIPEND_LOG_TOPICS: u32 = 4;
const STIPEND_LOG_DATA_LEN: u32 = 64;
fn determine_call_stipend<T: Config>() -> Weight {
let gas_weight = <EVMGas as Token<T>>::weight(&EVMGas(CALL_STIPEND));
let event_weight = <RuntimeCosts as Token<T>>::weight(&RuntimeCosts::DepositEvent {
num_topic: STIPEND_LOG_TOPICS,
len: STIPEND_LOG_DATA_LEN,
});
gas_weight.saturating_add(event_weight)
}
pub mod substrate_execution {
use num_traits::One;
use super::*;
pub fn new_root<T: Config>(
weight_limit: Weight,
deposit_limit: BalanceOf<T>,
) -> Result<TransactionMeter<T>, DispatchError> {
Ok(TransactionMeter {
weight: WeightMeter::new(weight_limit, None),
deposit: RootStorageMeter::new(Some(deposit_limit)),
max_total_gas: Default::default(),
total_consumed_weight_before: Default::default(),
total_consumed_deposit_before: Default::default(),
transaction_limits: TransactionLimits::WeightAndDeposit { weight_limit, deposit_limit },
_phantom: PhantomData,
})
}
pub fn new_nested_meter<T: Config, S: State>(
meter: &ResourceMeter<T, S>,
limit: &CallResources<T>,
) -> Result<FrameMeter<T>, DispatchError> {
let self_consumed_weight = meter.weight.weight_consumed();
let self_consumed_deposit = meter.deposit.consumed();
let total_consumed_weight =
meter.total_consumed_weight_before.saturating_add(self_consumed_weight);
let total_consumed_deposit =
meter.total_consumed_deposit_before.saturating_add(&self_consumed_deposit);
let weight_left = meter
.weight
.weight_limit
.checked_sub(&self_consumed_weight)
.ok_or(<Error<T>>::OutOfGas)?;
let deposit_limit = meter.deposit.limit.expect(
"Deposit limits are always defined for `ResourceMeter` in Substrate \
execution mode (i.e., when its `transaction_limits` are `WeightAndDeposit`); qed",
);
let deposit_left = self_consumed_deposit
.available(&deposit_limit)
.ok_or(<Error<T>>::StorageDepositLimitExhausted)?;
let (nested_weight_limit, nested_deposit_limit, stipend) = {
match limit {
CallResources::NoLimits => (weight_left, deposit_left, None),
CallResources::Ethereum { gas, add_stipend } => {
let weight_gas_left = SignedGas::<T>::from_weight_fee(
T::FeeInfo::weight_to_fee_average(&weight_left),
);
let deposit_gas_left = SignedGas::<T>::from_adjusted_deposit_charge(
&StorageDeposit::Charge(deposit_left),
);
let Some(remaining_gas) =
(weight_gas_left.saturating_add(&deposit_gas_left)).to_ethereum_gas()
else {
return Err(<Error<T>>::OutOfGas.into());
};
let remaining_gas = remaining_gas.min(u64::MAX.saturated_into());
let gas_limit = remaining_gas.min(*gas);
let ratio = if remaining_gas.is_zero() {
FixedU128::one()
} else {
FixedU128::from_rational(
gas_limit.saturated_into(),
remaining_gas.saturated_into(),
)
};
let mut weight_limit = Weight::from_parts(
ratio.saturating_mul_int(weight_left.ref_time()),
ratio.saturating_mul_int(weight_left.proof_size()),
);
let deposit_limit = ratio.saturating_mul_int(deposit_left);
let stipend = if *add_stipend {
let weight_stipend = determine_call_stipend::<T>();
if weight_left.any_lt(weight_stipend) {
return Err(<Error<T>>::OutOfGas.into());
}
weight_limit.saturating_accrue(weight_stipend);
Some(weight_stipend)
} else {
None
};
(weight_left.min(weight_limit), deposit_left.min(deposit_limit), stipend)
},
CallResources::WeightDeposit { weight, deposit_limit } =>
{
(weight_left.min(*weight), deposit_left.min(*deposit_limit), None)
},
}
};
Ok(FrameMeter::<T> {
weight: WeightMeter::new(nested_weight_limit, stipend),
deposit: meter.deposit.nested(Some(nested_deposit_limit)),
max_total_gas: Default::default(),
total_consumed_weight_before: total_consumed_weight,
total_consumed_deposit_before: total_consumed_deposit,
transaction_limits: meter.transaction_limits.clone(),
_phantom: PhantomData,
})
}
pub fn gas_left<T: Config, S: State>(meter: &ResourceMeter<T, S>) -> Option<SignedGas<T>> {
match (weight_left(meter), deposit_left(meter)) {
(Some(weight_left), Some(deposit_left)) => {
let weight_gas_left = SignedGas::<T>::from_weight_fee(
T::FeeInfo::weight_to_fee_average(&weight_left),
);
let deposit_gas_left = SignedGas::<T>::from_adjusted_deposit_charge(
&StorageDeposit::Charge(deposit_left),
);
Some(weight_gas_left.saturating_add(&deposit_gas_left))
},
_ => None,
}
}
pub fn weight_left<T: Config, S: State>(meter: &ResourceMeter<T, S>) -> Option<Weight> {
meter.weight.weight_limit.checked_sub(&meter.weight.weight_consumed())
}
pub fn deposit_left<T: Config, S: State>(meter: &ResourceMeter<T, S>) -> Option<BalanceOf<T>> {
let deposit_limit = meter.deposit.limit.expect(
"Deposit limits are always defined for `ResourceMeter` in Substrate \
execution mode (i.e., when its `transaction_limits` are `WeightAndDeposit`); qed",
);
meter.deposit.consumed().available(&deposit_limit)
}
pub fn total_consumed_gas<T: Config, S: State>(meter: &ResourceMeter<T, S>) -> SignedGas<T> {
let self_consumed_weight = meter.weight.weight_consumed();
let self_consumed_deposit = meter.deposit.consumed();
let total_consumed_weight =
meter.total_consumed_weight_before.saturating_add(self_consumed_weight);
let total_consumed_deposit =
meter.total_consumed_deposit_before.saturating_add(&self_consumed_deposit);
let consumed_weight_gas =
SignedGas::from_weight_fee(T::FeeInfo::weight_to_fee_average(&total_consumed_weight));
let consumed_deposit_gas = SignedGas::from_adjusted_deposit_charge(&total_consumed_deposit);
consumed_deposit_gas.saturating_add(&consumed_weight_gas)
}
pub fn eth_gas_consumed<T: Config, S: State>(meter: &ResourceMeter<T, S>) -> SignedGas<T> {
let self_consumed_weight = meter.weight.weight_consumed();
let self_consumed_deposit = meter.deposit.consumed();
let total_consumed_weight =
meter.total_consumed_weight_before.saturating_add(self_consumed_weight);
let consumed_weight_gas_before = SignedGas::from_weight_fee(
T::FeeInfo::weight_to_fee_average(&meter.total_consumed_weight_before),
);
let consumed_weight_gas =
SignedGas::from_weight_fee(T::FeeInfo::weight_to_fee_average(&total_consumed_weight));
let self_consumed_weight_gas =
consumed_weight_gas.saturating_sub(&consumed_weight_gas_before);
let self_consumed_deposit_gas =
SignedGas::from_adjusted_deposit_charge(&self_consumed_deposit);
self_consumed_deposit_gas.saturating_add(&self_consumed_weight_gas)
}
}
pub mod ethereum_execution {
use super::*;
pub fn new_root<T: Config>(
eth_gas_limit: BalanceOf<T>,
weight_limit: Weight,
eth_tx_info: EthTxInfo<T>,
) -> Result<TransactionMeter<T>, DispatchError> {
let meter = TransactionMeter {
weight: WeightMeter::new(weight_limit, None),
deposit: RootStorageMeter::new(None),
max_total_gas: SignedGas::from_ethereum_gas(eth_gas_limit),
total_consumed_weight_before: Default::default(),
total_consumed_deposit_before: Default::default(),
transaction_limits: TransactionLimits::EthereumGas {
eth_gas_limit,
weight_limit,
eth_tx_info,
},
_phantom: PhantomData,
};
if meter.eth_gas_left().is_some() {
Ok(meter)
} else {
return Err(<Error<T>>::OutOfGas.into());
}
}
pub fn new_nested_meter<T: Config, S: State>(
meter: &ResourceMeter<T, S>,
limit: &CallResources<T>,
eth_tx_info: &EthTxInfo<T>,
) -> Result<FrameMeter<T>, DispatchError> {
let self_consumed_weight = meter.weight.weight_consumed();
let self_consumed_deposit = meter.deposit.consumed();
let total_consumed_weight =
meter.total_consumed_weight_before.saturating_add(self_consumed_weight);
let total_consumed_deposit =
meter.total_consumed_deposit_before.saturating_add(&self_consumed_deposit);
let total_gas_consumption =
eth_tx_info.gas_consumption(&total_consumed_weight, &total_consumed_deposit);
let remaining_gas = meter.max_total_gas.saturating_sub(&total_gas_consumption);
let weight_left = {
let unbounded_weight_left = eth_tx_info
.weight_remaining(
&meter.max_total_gas,
&total_consumed_weight,
&total_consumed_deposit,
)
.ok_or(<Error<T>>::OutOfGas)?;
unbounded_weight_left.min(
meter
.weight
.weight_limit
.checked_sub(&self_consumed_weight)
.ok_or(<Error<T>>::OutOfGas)?,
)
};
let deposit_left = {
let Some(unbounded_deposit_left) = remaining_gas.to_adjusted_deposit_charge() else {
return Err(<Error<T>>::OutOfGas.into());
};
match meter.deposit.limit {
Some(deposit_limit) => unbounded_deposit_left.min(
self_consumed_deposit
.available(&deposit_limit)
.ok_or(<Error<T>>::StorageDepositLimitExhausted)?,
),
None => unbounded_deposit_left,
}
};
let (nested_gas_limit, nested_weight_limit, nested_deposit_limit, stipend) = {
match limit {
CallResources::NoLimits => (
remaining_gas,
weight_left,
if meter.deposit.limit.is_none() { None } else { Some(deposit_left) },
None,
),
CallResources::Ethereum { gas, add_stipend } => {
let gas_limit = SignedGas::from_ethereum_gas(*gas);
let (gas_limit, stipend) = if *add_stipend {
let weight_stipend = determine_call_stipend::<T>();
if weight_left.any_lt(weight_stipend) {
return Err(<Error<T>>::OutOfGas.into());
}
(
gas_limit.saturating_add(&SignedGas::<T>::from_weight_fee(
T::FeeInfo::weight_to_fee(&weight_stipend),
)),
Some(weight_stipend),
)
} else {
(gas_limit, None)
};
(
remaining_gas.min(&gas_limit),
weight_left,
if meter.deposit.limit.is_none() { None } else { Some(deposit_left) },
stipend,
)
},
CallResources::WeightDeposit { weight, deposit_limit } => {
let nested_weight_limit = weight_left.min(*weight);
let nested_deposit_limit = deposit_left.min(*deposit_limit);
let new_max_total_gas = eth_tx_info.gas_consumption(
&total_consumed_weight.saturating_add(nested_weight_limit),
&total_consumed_deposit
.saturating_add(&StorageDeposit::Charge(nested_deposit_limit)),
);
let gas_limit = new_max_total_gas.saturating_sub(&total_gas_consumption);
(
remaining_gas.min(&gas_limit),
nested_weight_limit,
Some(nested_deposit_limit),
None,
)
},
}
};
let nested_max_total_gas = total_gas_consumption.saturating_add(&nested_gas_limit);
Ok(FrameMeter::<T> {
weight: WeightMeter::new(nested_weight_limit, stipend),
deposit: meter.deposit.nested(nested_deposit_limit),
max_total_gas: nested_max_total_gas,
total_consumed_weight_before: total_consumed_weight,
total_consumed_deposit_before: total_consumed_deposit,
transaction_limits: meter.transaction_limits.clone(),
_phantom: PhantomData,
})
}
pub fn gas_left<T: Config, S: State>(
meter: &ResourceMeter<T, S>,
eth_tx_info: &EthTxInfo<T>,
) -> Option<SignedGas<T>> {
let self_consumed_weight = meter.weight.weight_consumed();
let self_consumed_deposit = meter.deposit.consumed();
let total_consumed_weight =
meter.total_consumed_weight_before.saturating_add(self_consumed_weight);
let total_consumed_deposit =
meter.total_consumed_deposit_before.saturating_add(&self_consumed_deposit);
let total_gas_consumption =
eth_tx_info.gas_consumption(&total_consumed_weight, &total_consumed_deposit);
Some(meter.max_total_gas.saturating_sub(&total_gas_consumption))
}
pub fn weight_left<T: Config, S: State>(
meter: &ResourceMeter<T, S>,
eth_tx_info: &EthTxInfo<T>,
) -> Option<Weight> {
let self_consumed_weight = meter.weight.weight_consumed();
let self_consumed_deposit = meter.deposit.consumed();
let total_consumed_weight =
meter.total_consumed_weight_before.saturating_add(self_consumed_weight);
let total_consumed_deposit =
meter.total_consumed_deposit_before.saturating_add(&self_consumed_deposit);
let weight_left = eth_tx_info.weight_remaining(
&meter.max_total_gas,
&total_consumed_weight,
&total_consumed_deposit,
)?;
Some(weight_left.min(meter.weight.weight_limit.checked_sub(&self_consumed_weight)?))
}
pub fn deposit_left<T: Config, S: State>(
meter: &ResourceMeter<T, S>,
eth_tx_info: &EthTxInfo<T>,
) -> Option<BalanceOf<T>> {
let deposit_left = gas_left(meter, eth_tx_info)?.to_adjusted_deposit_charge()?;
Some(match meter.deposit.limit {
Some(deposit_limit) => {
let deposit_available = meter.deposit.consumed().available(&deposit_limit)?;
deposit_left.min(deposit_available)
},
None => deposit_left,
})
}
pub fn total_consumed_gas<T: Config, S: State>(
meter: &ResourceMeter<T, S>,
eth_tx_info: &EthTxInfo<T>,
) -> SignedGas<T> {
let self_consumed_weight = meter.weight.weight_consumed();
let self_consumed_deposit = meter.deposit.consumed();
let total_consumed_weight =
meter.total_consumed_weight_before.saturating_add(self_consumed_weight);
let total_consumed_deposit =
meter.total_consumed_deposit_before.saturating_add(&self_consumed_deposit);
eth_tx_info.gas_consumption(&total_consumed_weight, &total_consumed_deposit)
}
pub fn eth_gas_consumed<T: Config, S: State>(
meter: &ResourceMeter<T, S>,
eth_tx_info: &EthTxInfo<T>,
) -> SignedGas<T> {
let self_consumed_weight = meter.weight.weight_consumed();
let self_consumed_deposit = meter.deposit.consumed();
let total_consumed_weight =
meter.total_consumed_weight_before.saturating_add(self_consumed_weight);
let total_consumed_deposit =
meter.total_consumed_deposit_before.saturating_add(&self_consumed_deposit);
let total_gas_consumed =
eth_tx_info.gas_consumption(&total_consumed_weight, &total_consumed_deposit);
let total_gas_consumed_before = eth_tx_info.gas_consumption(
&meter.total_consumed_weight_before,
&meter.total_consumed_deposit_before,
);
total_gas_consumed.saturating_sub(&total_gas_consumed_before)
}
}