#[cfg(test)]
mod tests;
use crate::{Config, Error, vm::evm::Halt, weights::WeightInfo};
use core::{marker::PhantomData, ops::ControlFlow};
use frame_support::{DefaultNoBound, weights::Weight};
use sp_runtime::DispatchError;
#[cfg(test)]
use std::{any::Any, fmt::Debug};
#[derive(Debug, PartialEq, Eq)]
pub struct ChargedAmount(Weight);
impl ChargedAmount {
pub fn amount(&self) -> Weight {
self.0
}
}
#[derive(DefaultNoBound)]
struct EngineMeter<T: Config> {
fuel: u64,
_phantom: PhantomData<T>,
}
impl<T: Config> EngineMeter<T> {
fn new() -> Self {
Self { fuel: 0, _phantom: PhantomData }
}
fn set_fuel(&mut self, fuel: u64) -> Weight {
let consumed = self.fuel.saturating_sub(fuel).saturating_mul(Self::ref_time_per_fuel());
self.fuel = fuel;
Weight::from_parts(consumed, 0)
}
fn sync_remaining_ref_time(&mut self, remaining_ref_time: u64) -> polkavm::Gas {
self.fuel = remaining_ref_time.saturating_div(Self::ref_time_per_fuel());
self.fuel.try_into().unwrap_or(polkavm::Gas::MAX)
}
fn ref_time_per_fuel() -> u64 {
let loop_iteration =
T::WeightInfo::instr(1).saturating_sub(T::WeightInfo::instr(0)).ref_time();
let empty_loop_iteration = T::WeightInfo::instr_empty_loop(1)
.saturating_sub(T::WeightInfo::instr_empty_loop(0))
.ref_time();
loop_iteration.saturating_sub(empty_loop_iteration)
}
}
#[must_use]
pub struct Syncable(polkavm::Gas);
impl From<Syncable> for polkavm::Gas {
fn from(from: Syncable) -> Self {
from.0
}
}
#[cfg(not(test))]
pub trait TestAuxiliaries {}
#[cfg(not(test))]
impl<T> TestAuxiliaries for T {}
#[cfg(test)]
pub trait TestAuxiliaries: Any + Debug + PartialEq + Eq {}
#[cfg(test)]
impl<T: Any + Debug + PartialEq + Eq> TestAuxiliaries for T {}
pub trait Token<T: Config>: Copy + Clone + TestAuxiliaries {
fn weight(&self) -> Weight;
fn influence_lowest_weight_limit(&self) -> bool {
true
}
}
#[cfg(test)]
pub struct ErasedToken {
pub description: String,
pub token: Box<dyn Any>,
}
#[derive(DefaultNoBound)]
pub struct WeightMeter<T: Config> {
pub weight_limit: Weight,
effective_weight_limit: Weight,
weight_consumed: Weight,
weight_consumed_highest: Weight,
engine_meter: EngineMeter<T>,
_phantom: PhantomData<T>,
#[cfg(test)]
tokens: Vec<ErasedToken>,
}
impl<T: Config> WeightMeter<T> {
pub fn new(weight_limit: Weight, stipend: Option<Weight>) -> Self {
WeightMeter {
weight_limit,
effective_weight_limit: weight_limit,
weight_consumed: Default::default(),
weight_consumed_highest: stipend.unwrap_or_default(),
engine_meter: EngineMeter::new(),
_phantom: PhantomData,
#[cfg(test)]
tokens: Vec::new(),
}
}
pub fn set_effective_weight_limit(&mut self, limit: Weight) {
self.effective_weight_limit = limit;
}
pub fn absorb_nested(&mut self, nested: Self) {
self.weight_consumed_highest = self
.weight_consumed
.saturating_add(nested.weight_required())
.max(self.weight_consumed_highest);
self.weight_consumed += nested.weight_consumed;
}
#[inline]
pub fn charge<Tok: Token<T>>(&mut self, token: Tok) -> Result<ChargedAmount, DispatchError> {
#[cfg(test)]
{
let erased_tok =
ErasedToken { description: format!("{:?}", token), token: Box::new(token) };
self.tokens.push(erased_tok);
}
let amount = token.weight();
let new_consumed = self.weight_consumed.saturating_add(amount);
if new_consumed.any_gt(self.effective_weight_limit) {
return Err(<Error<T>>::OutOfGas.into());
}
self.weight_consumed = new_consumed;
Ok(ChargedAmount(amount))
}
#[inline]
pub fn charge_or_halt<Tok: Token<T>>(
&mut self,
token: Tok,
) -> ControlFlow<Halt, ChargedAmount> {
self.charge(token)
.map_or_else(|_| ControlFlow::Break(Error::<T>::OutOfGas.into()), ControlFlow::Continue)
}
pub fn adjust_weight<Tok: Token<T>>(&mut self, charged_amount: ChargedAmount, token: Tok) {
if token.influence_lowest_weight_limit() {
self.weight_consumed_highest = self.weight_required();
}
let adjustment = charged_amount.0.saturating_sub(token.weight());
self.weight_consumed = self.weight_consumed.saturating_sub(adjustment);
}
pub fn sync_from_executor(&mut self, engine_fuel: polkavm::Gas) -> Result<(), DispatchError> {
let weight_consumed = self
.engine_meter
.set_fuel(engine_fuel.try_into().map_err(|_| Error::<T>::OutOfGas)?);
self.weight_consumed.saturating_accrue(weight_consumed);
if self.weight_consumed.any_gt(self.effective_weight_limit) {
self.weight_consumed = self.effective_weight_limit;
return Err(<Error<T>>::OutOfGas.into());
}
Ok(())
}
pub fn sync_to_executor(&mut self) -> polkavm::Gas {
self.engine_meter.sync_remaining_ref_time(self.weight_left().ref_time())
}
pub fn weight_required(&self) -> Weight {
self.weight_consumed_highest.max(self.weight_consumed)
}
pub fn weight_consumed(&self) -> Weight {
self.weight_consumed
}
pub fn consume_all(&mut self) {
self.weight_consumed = self.effective_weight_limit;
}
pub fn weight_left(&self) -> Weight {
self.effective_weight_limit.saturating_sub(self.weight_consumed)
}
#[cfg(test)]
pub fn tokens(&self) -> &[ErasedToken] {
&self.tokens
}
#[cfg(test)]
pub fn nested(&mut self, amount: Weight) -> Self {
Self::new(self.weight_left().min(amount), None)
}
}