mod gas;
mod math;
mod storage;
mod weight;
#[cfg(test)]
mod tests;
use crate::{
BalanceOf, Config, Error, ExecConfig, ExecOrigin as Origin, LOG_TARGET, StorageDeposit,
evm::fees::InfoT, exec::CallResources, storage::ContractInfo, vm::evm::Halt,
};
pub use gas::SignedGas;
pub use storage::Diff;
pub use weight::{ChargedAmount, Token};
use frame_support::{DebugNoBound, DefaultNoBound};
use num_traits::Zero;
use core::{fmt::Debug, marker::PhantomData, ops::ControlFlow};
use sp_runtime::{FixedPointNumber, Weight};
use storage::{DepositOf, GenericMeter as GenericStorageMeter, Meter as RootStorageMeter};
use weight::WeightMeter;
use sp_runtime::{DispatchError, DispatchResult, FixedU128, SaturatedConversion};
pub trait State: private::Sealed + Default + Debug {}
#[derive(Default, Debug)]
pub struct Root;
#[derive(Default, Debug)]
pub struct Nested;
impl State for Root {}
impl State for Nested {}
mod private {
pub trait Sealed {}
impl Sealed for super::Root {}
impl Sealed for super::Nested {}
}
pub type TransactionMeter<T> = ResourceMeter<T, Root>;
pub type FrameMeter<T> = ResourceMeter<T, Nested>;
#[derive(DefaultNoBound)]
pub struct ResourceMeter<T: Config, S: State> {
weight: WeightMeter<T>,
deposit: GenericStorageMeter<T, S>,
max_total_gas: SignedGas<T>,
total_consumed_weight_before: Weight,
total_consumed_deposit_before: DepositOf<T>,
transaction_limits: TransactionLimits<T>,
_phantom: PhantomData<S>,
}
#[derive(DebugNoBound, Clone)]
pub enum TransactionLimits<T: Config> {
EthereumGas {
eth_gas_limit: BalanceOf<T>,
weight_limit: Weight,
eth_tx_info: EthTxInfo<T>,
},
WeightAndDeposit { weight_limit: Weight, deposit_limit: BalanceOf<T> },
}
impl<T: Config> Default for TransactionLimits<T> {
fn default() -> Self {
Self::WeightAndDeposit {
weight_limit: Default::default(),
deposit_limit: Default::default(),
}
}
}
impl<T: Config, S: State> ResourceMeter<T, S> {
pub fn new_nested(&self, limit: &CallResources<T>) -> Result<FrameMeter<T>, DispatchError> {
log::trace!(
target: LOG_TARGET,
"Creating nested meter from parent: \
limit={limit:?}, \
weight_left={:?}, \
deposit_left={:?}, \
weight_consumed={:?}, \
deposit_consumed={:?}",
self.weight_left(),
self.deposit_left(),
self.weight_consumed(),
self.deposit_consumed(),
);
let mut new_meter = match &self.transaction_limits {
TransactionLimits::EthereumGas { eth_tx_info, .. } => {
math::ethereum_execution::new_nested_meter(self, limit, eth_tx_info)
},
TransactionLimits::WeightAndDeposit { .. } => {
math::substrate_execution::new_nested_meter(self, limit)
},
}?;
new_meter.adjust_effective_weight_limit()?;
log::trace!(
target: LOG_TARGET,
"Creating nested meter done: \
weight_left={:?}, \
deposit_left={:?}, \
weight_consumed={:?}, \
deposit_consumed={:?}",
new_meter.weight_left(),
new_meter.deposit_left(),
new_meter.weight_consumed(),
new_meter.deposit_consumed(),
);
Ok(new_meter)
}
pub fn absorb_weight_meter_only(&mut self, other: FrameMeter<T>) {
log::trace!(
target: LOG_TARGET,
"Absorb weight meter only: \
parent_weight_left={:?}, \
parent_deposit_left={:?}, \
parent_weight_consumed={:?}, \
parent_deposit_consumed={:?}, \
child_weight_left={:?}, \
child_deposit_left={:?}, \
child_weight_consumed={:?}, \
child_deposit_consumed={:?}",
self.weight_left(),
self.deposit_left(),
self.weight_consumed(),
self.deposit_consumed(),
other.weight_left(),
other.deposit_left(),
other.weight_consumed(),
other.deposit_consumed(),
);
self.weight.absorb_nested(other.weight);
self.deposit.absorb_only_max_charged(other.deposit);
log::trace!(
target: LOG_TARGET,
"Absorb weight meter done: \
parent_weight_left={:?}, \
parent_deposit_left={:?}, \
parent_weight_consumed={:?}, \
parent_deposit_consumed={:?}",
self.weight_left(),
self.deposit_left(),
self.weight_consumed(),
self.deposit_consumed(),
);
}
pub fn absorb_all_meters(
&mut self,
other: FrameMeter<T>,
contract: &T::AccountId,
info: Option<&mut ContractInfo<T>>,
) {
log::trace!(
target: LOG_TARGET,
"Absorb all meters: \
parent_weight_left={:?}, \
parent_deposit_left={:?}, \
parent_weight_consumed={:?}, \
parent_deposit_consumed={:?}, \
child_weight_left={:?}, \
child_deposit_left={:?}, \
child_weight_consumed={:?}, \
child_deposit_consumed={:?}",
self.weight_left(),
self.deposit_left(),
self.weight_consumed(),
self.deposit_consumed(),
other.weight_left(),
other.deposit_left(),
other.weight_consumed(),
other.deposit_consumed(),
);
self.weight.absorb_nested(other.weight);
self.deposit.absorb(other.deposit, contract, info);
let result = self.adjust_effective_weight_limit();
debug_assert!(result.is_ok(), "Absorbing nested meters should not exceed limits");
log::trace!(
target: LOG_TARGET,
"Absorb all meters done: \
parent_weight_left={:?}, \
parent_deposit_left={:?}, \
parent_weight_consumed={:?}, \
parent_deposit_consumed={:?}",
self.weight_left(),
self.deposit_left(),
self.weight_consumed(),
self.deposit_consumed(),
);
}
#[inline]
pub fn charge_weight_token<Tok: Token<T>>(
&mut self,
token: Tok,
) -> Result<ChargedAmount, DispatchError> {
self.weight.charge(token)
}
#[inline]
pub fn charge_or_halt<Tok: Token<T>>(
&mut self,
token: Tok,
) -> ControlFlow<Halt, ChargedAmount> {
self.weight.charge_or_halt(token)
}
pub fn adjust_weight<Tok: Token<T>>(&mut self, charged_amount: ChargedAmount, token: Tok) {
self.weight.adjust_weight(charged_amount, token);
}
pub fn sync_from_executor(&mut self, engine_fuel: polkavm::Gas) -> Result<(), DispatchError> {
self.weight.sync_from_executor(engine_fuel)
}
pub fn sync_to_executor(&mut self) -> polkavm::Gas {
self.weight.sync_to_executor()
}
pub fn consume_all_weight(&mut self) {
self.weight.consume_all();
}
pub fn charge_deposit(&mut self, deposit: &DepositOf<T>) -> DispatchResult {
log::trace!(
target: LOG_TARGET,
"Charge deposit: \
deposit={:?}, \
deposit_left={:?}, \
deposit_consumed={:?}, \
max_charged={:?}",
deposit,
self.deposit_left(),
self.deposit_consumed(),
self.deposit.max_charged(),
);
self.deposit.record_charge(deposit);
self.adjust_effective_weight_limit()?;
if self.deposit.is_root {
if self.deposit_left().is_none() {
self.deposit.reset();
self.adjust_effective_weight_limit()?;
return Err(<Error<T>>::StorageDepositLimitExhausted.into());
}
}
Ok(())
}
pub fn eth_gas_left(&self) -> Option<BalanceOf<T>> {
let gas_left = match &self.transaction_limits {
TransactionLimits::EthereumGas { eth_tx_info, .. } => {
math::ethereum_execution::gas_left(self, eth_tx_info)
},
TransactionLimits::WeightAndDeposit { .. } => math::substrate_execution::gas_left(self),
}?;
gas_left.to_ethereum_gas()
}
pub fn weight_left(&self) -> Option<Weight> {
match &self.transaction_limits {
TransactionLimits::EthereumGas { eth_tx_info, .. } => {
math::ethereum_execution::weight_left(self, eth_tx_info)
},
TransactionLimits::WeightAndDeposit { .. } => {
math::substrate_execution::weight_left(self)
},
}
}
pub fn deposit_left(&self) -> Option<BalanceOf<T>> {
match &self.transaction_limits {
TransactionLimits::EthereumGas { eth_tx_info, .. } => {
math::ethereum_execution::deposit_left(self, eth_tx_info)
},
TransactionLimits::WeightAndDeposit { .. } => {
math::substrate_execution::deposit_left(self)
},
}
}
pub fn total_consumed_gas(&self) -> BalanceOf<T> {
let signed_gas = match &self.transaction_limits {
TransactionLimits::EthereumGas { eth_tx_info, .. } => {
math::ethereum_execution::total_consumed_gas(self, eth_tx_info)
},
TransactionLimits::WeightAndDeposit { .. } => {
math::substrate_execution::total_consumed_gas(self)
},
};
signed_gas.to_ethereum_gas().unwrap_or_default()
}
pub fn weight_consumed(&self) -> Weight {
self.weight.weight_consumed()
}
pub fn weight_required(&self) -> Weight {
self.weight.weight_required()
}
pub fn deposit_consumed(&self) -> DepositOf<T> {
self.deposit.consumed()
}
pub fn deposit_required(&self) -> DepositOf<T> {
self.deposit.max_charged()
}
pub fn eth_gas_consumed(&self) -> BalanceOf<T> {
let signed_gas = match &self.transaction_limits {
TransactionLimits::EthereumGas { eth_tx_info, .. } => {
math::ethereum_execution::eth_gas_consumed(self, eth_tx_info)
},
TransactionLimits::WeightAndDeposit { .. } => {
math::substrate_execution::eth_gas_consumed(self)
},
};
signed_gas.to_ethereum_gas().unwrap_or_default()
}
fn adjust_effective_weight_limit(&mut self) -> DispatchResult {
if matches!(self.transaction_limits, TransactionLimits::WeightAndDeposit { .. }) {
return Ok(());
}
if let Some(weight_left) = self.weight_left() {
let new_effective_limit = self.weight.weight_consumed().saturating_add(weight_left);
self.weight.set_effective_weight_limit(new_effective_limit);
Ok(())
} else {
Err(<Error<T>>::OutOfGas.into())
}
}
}
impl<T: Config> TransactionMeter<T> {
pub fn new(transaction_limits: TransactionLimits<T>) -> Result<Self, DispatchError> {
log::debug!(
target: LOG_TARGET,
"Start new meter: transaction_limits={transaction_limits:?}",
);
let mut transaction_meter = match transaction_limits {
TransactionLimits::EthereumGas { eth_gas_limit, weight_limit, eth_tx_info } => {
math::ethereum_execution::new_root(eth_gas_limit, weight_limit, eth_tx_info)
},
TransactionLimits::WeightAndDeposit { weight_limit, deposit_limit } => {
math::substrate_execution::new_root(weight_limit, deposit_limit)
},
}?;
transaction_meter.adjust_effective_weight_limit()?;
log::trace!(
target: LOG_TARGET,
"New meter done: \
weight_left={:?}, \
deposit_left={:?}, \
weight_consumed={:?}, \
deposit_consumed={:?}",
transaction_meter.weight_left(),
transaction_meter.deposit_left(),
transaction_meter.weight_consumed(),
transaction_meter.deposit_consumed(),
);
Ok(transaction_meter)
}
pub fn new_from_limits(
weight_limit: Weight,
deposit_limit: BalanceOf<T>,
) -> Result<Self, DispatchError> {
Self::new(TransactionLimits::WeightAndDeposit { weight_limit, deposit_limit })
}
pub fn execute_postponed_deposits(
&mut self,
origin: &Origin<T>,
exec_config: &ExecConfig<T>,
) -> Result<DepositOf<T>, DispatchError> {
log::debug!(
target: LOG_TARGET,
"Transaction meter finishes: \
weight_left={:?}, \
deposit_left={:?}, \
weight_consumed={:?}, \
deposit_consumed={:?}, \
eth_gas_consumed={:?}",
self.weight_left(),
self.deposit_left(),
self.weight_consumed(),
self.deposit_consumed(),
self.eth_gas_consumed(),
);
if self.deposit_left().is_none() {
return Err(<Error<T>>::StorageDepositNotEnoughFunds.into());
}
self.deposit.execute_postponed_deposits(origin, exec_config)
}
pub fn terminate(&mut self, contract_account: T::AccountId, refunded: BalanceOf<T>) {
self.deposit.terminate(contract_account, refunded);
}
}
impl<T: Config> FrameMeter<T> {
pub fn charge_contract_deposit_and_transfer(
&mut self,
contract: T::AccountId,
amount: DepositOf<T>,
) -> DispatchResult {
log::trace!(
target: LOG_TARGET,
"Charge deposit and transfer: \
amount={:?}, \
deposit_left={:?}, \
deposit_consumed={:?}, \
max_charged={:?}",
amount,
self.deposit_left(),
self.deposit_consumed(),
self.deposit.max_charged(),
);
self.deposit.charge_deposit(contract, amount);
self.adjust_effective_weight_limit()
}
pub fn record_contract_storage_changes(&mut self, diff: &Diff) -> DispatchResult {
log::trace!(
target: LOG_TARGET,
"Charge contract storage: \
diff={:?}, \
deposit_left={:?}, \
deposit_consumed={:?}, \
max_charged={:?}",
diff,
self.deposit_left(),
self.deposit_consumed(),
self.deposit.max_charged(),
);
self.deposit.charge(diff);
self.adjust_effective_weight_limit()
}
pub fn finalize(&mut self, info: Option<&mut ContractInfo<T>>) -> DispatchResult {
self.deposit.finalize_own_contributions(info);
if self.deposit_left().is_none() {
return Err(<Error<T>>::StorageDepositLimitExhausted.into());
}
Ok(())
}
pub fn apply_pending_storage_changes(&self, info: &mut ContractInfo<T>) {
self.deposit.apply_pending_changes_to_contract(info);
}
}
#[derive(DebugNoBound, Clone)]
pub struct EthTxInfo<T: Config> {
pub encoded_len: u32,
pub extra_weight: Weight,
_phantom: PhantomData<T>,
}
impl<T: Config> EthTxInfo<T> {
pub fn new(encoded_len: u32, extra_weight: Weight) -> Self {
Self { encoded_len, extra_weight, _phantom: PhantomData }
}
pub fn gas_consumption(
&self,
consumed_weight: &Weight,
consumed_deposit: &DepositOf<T>,
) -> SignedGas<T> {
let fixed_fee = T::FeeInfo::fixed_fee(self.encoded_len);
let deposit_and_fixed_fee =
consumed_deposit.saturating_add(&DepositOf::<T>::Charge(fixed_fee));
let deposit_gas = SignedGas::from_adjusted_deposit_charge(&deposit_and_fixed_fee);
let weight_gas = SignedGas::from_weight_fee(T::FeeInfo::weight_to_fee(
&consumed_weight.saturating_add(self.extra_weight),
));
deposit_gas.saturating_add(&weight_gas)
}
pub fn weight_remaining(
&self,
max_total_gas: &SignedGas<T>,
total_weight_consumption: &Weight,
total_deposit_consumption: &DepositOf<T>,
) -> Option<Weight> {
let fixed_fee = T::FeeInfo::fixed_fee(self.encoded_len);
let deposit_and_fixed_fee =
total_deposit_consumption.saturating_add(&DepositOf::<T>::Charge(fixed_fee));
let deposit_gas = SignedGas::from_adjusted_deposit_charge(&deposit_and_fixed_fee);
let consumable_fee = max_total_gas.saturating_sub(&deposit_gas).to_weight_fee()?;
T::FeeInfo::fee_to_weight(consumable_fee)
.checked_sub(&total_weight_consumption.saturating_add(self.extra_weight))
}
}