pallet_revive/metering/
weight.rs1#[cfg(test)]
19mod tests;
20
21use crate::{vm::evm::Halt, weights::WeightInfo, Config, Error};
22use core::{marker::PhantomData, ops::ControlFlow};
23use frame_support::{weights::Weight, DefaultNoBound};
24use sp_runtime::DispatchError;
25
26#[cfg(test)]
27use std::{any::Any, fmt::Debug};
28
29#[derive(Debug, PartialEq, Eq)]
30pub struct ChargedAmount(Weight);
31
32impl ChargedAmount {
33 pub fn amount(&self) -> Weight {
34 self.0
35 }
36}
37
38#[derive(DefaultNoBound)]
40struct EngineMeter<T: Config> {
41 fuel: u64,
42 _phantom: PhantomData<T>,
43}
44
45impl<T: Config> EngineMeter<T> {
46 fn new() -> Self {
48 Self { fuel: 0, _phantom: PhantomData }
49 }
50
51 fn set_fuel(&mut self, fuel: u64) -> Weight {
54 let consumed = self.fuel.saturating_sub(fuel).saturating_mul(Self::ref_time_per_fuel());
55 self.fuel = fuel;
56 Weight::from_parts(consumed, 0)
57 }
58
59 fn sync_remaining_ref_time(&mut self, remaining_ref_time: u64) -> polkavm::Gas {
62 self.fuel = remaining_ref_time.saturating_div(Self::ref_time_per_fuel());
63 self.fuel.try_into().unwrap_or(polkavm::Gas::MAX)
64 }
65
66 fn ref_time_per_fuel() -> u64 {
68 let loop_iteration =
69 T::WeightInfo::instr(1).saturating_sub(T::WeightInfo::instr(0)).ref_time();
70 let empty_loop_iteration = T::WeightInfo::instr_empty_loop(1)
71 .saturating_sub(T::WeightInfo::instr_empty_loop(0))
72 .ref_time();
73 loop_iteration.saturating_sub(empty_loop_iteration)
74 }
75}
76
77#[must_use]
81pub struct Syncable(polkavm::Gas);
82
83impl From<Syncable> for polkavm::Gas {
84 fn from(from: Syncable) -> Self {
85 from.0
86 }
87}
88
89#[cfg(not(test))]
90pub trait TestAuxiliaries {}
91#[cfg(not(test))]
92impl<T> TestAuxiliaries for T {}
93
94#[cfg(test)]
95pub trait TestAuxiliaries: Any + Debug + PartialEq + Eq {}
96#[cfg(test)]
97impl<T: Any + Debug + PartialEq + Eq> TestAuxiliaries for T {}
98
99pub trait Token<T: Config>: Copy + Clone + TestAuxiliaries {
106 fn weight(&self) -> Weight;
115
116 fn influence_lowest_weight_limit(&self) -> bool {
118 true
119 }
120}
121
122#[cfg(test)]
124pub struct ErasedToken {
125 pub description: String,
126 pub token: Box<dyn Any>,
127}
128
129#[derive(DefaultNoBound)]
130pub struct WeightMeter<T: Config> {
131 pub weight_limit: Option<Weight>,
133 effective_weight_limit: Weight,
137 weight_consumed: Weight,
139 weight_consumed_highest: Weight,
142 engine_meter: EngineMeter<T>,
146 _phantom: PhantomData<T>,
147 #[cfg(test)]
148 tokens: Vec<ErasedToken>,
149}
150
151impl<T: Config> WeightMeter<T> {
152 pub fn new(weight_limit: Option<Weight>, stipend: Option<Weight>) -> Self {
153 WeightMeter {
154 weight_limit,
155 effective_weight_limit: weight_limit.unwrap_or_default(),
156 weight_consumed: Default::default(),
157 weight_consumed_highest: stipend.unwrap_or_default(),
158 engine_meter: EngineMeter::new(),
159 _phantom: PhantomData,
160 #[cfg(test)]
161 tokens: Vec::new(),
162 }
163 }
164
165 pub fn set_effective_weight_limit(&mut self, limit: Weight) {
166 self.effective_weight_limit = limit;
167 }
168
169 pub fn absorb_nested(&mut self, nested: Self) {
171 self.weight_consumed_highest = self
172 .weight_consumed
173 .saturating_add(nested.weight_required())
174 .max(self.weight_consumed_highest);
175 self.weight_consumed += nested.weight_consumed;
176 }
177
178 #[inline]
188 pub fn charge<Tok: Token<T>>(&mut self, token: Tok) -> Result<ChargedAmount, DispatchError> {
189 #[cfg(test)]
190 {
191 let erased_tok =
193 ErasedToken { description: format!("{:?}", token), token: Box::new(token) };
194 self.tokens.push(erased_tok);
195 }
196
197 let amount = token.weight();
198 let new_consumed = self.weight_consumed.saturating_add(amount);
201 if new_consumed.any_gt(self.effective_weight_limit) {
202 return Err(<Error<T>>::OutOfGas.into())
203 }
204
205 self.weight_consumed = new_consumed;
206 Ok(ChargedAmount(amount))
207 }
208
209 #[inline]
211 pub fn charge_or_halt<Tok: Token<T>>(
212 &mut self,
213 token: Tok,
214 ) -> ControlFlow<Halt, ChargedAmount> {
215 self.charge(token)
216 .map_or_else(|_| ControlFlow::Break(Error::<T>::OutOfGas.into()), ControlFlow::Continue)
217 }
218
219 pub fn adjust_weight<Tok: Token<T>>(&mut self, charged_amount: ChargedAmount, token: Tok) {
224 if token.influence_lowest_weight_limit() {
225 self.weight_consumed_highest = self.weight_required();
226 }
227 let adjustment = charged_amount.0.saturating_sub(token.weight());
228 self.weight_consumed = self.weight_consumed.saturating_sub(adjustment);
229 }
230
231 pub fn sync_from_executor(&mut self, engine_fuel: polkavm::Gas) -> Result<(), DispatchError> {
237 let weight_consumed = self
238 .engine_meter
239 .set_fuel(engine_fuel.try_into().map_err(|_| Error::<T>::OutOfGas)?);
240
241 self.weight_consumed.saturating_accrue(weight_consumed);
242 if self.weight_consumed.any_gt(self.effective_weight_limit) {
243 self.weight_consumed = self.effective_weight_limit;
244 return Err(<Error<T>>::OutOfGas.into())
245 }
246
247 Ok(())
248 }
249
250 pub fn sync_to_executor(&mut self) -> polkavm::Gas {
259 self.engine_meter.sync_remaining_ref_time(self.weight_left().ref_time())
260 }
261
262 pub fn weight_required(&self) -> Weight {
267 self.weight_consumed_highest.max(self.weight_consumed)
268 }
269
270 pub fn weight_consumed(&self) -> Weight {
272 self.weight_consumed
273 }
274
275 pub fn consume_all(&mut self) {
276 self.weight_consumed = self.effective_weight_limit;
277 }
278
279 pub fn weight_left(&self) -> Weight {
281 self.effective_weight_limit.saturating_sub(self.weight_consumed)
282 }
283
284 #[cfg(test)]
285 pub fn tokens(&self) -> &[ErasedToken] {
286 &self.tokens
287 }
288
289 #[cfg(test)]
290 pub fn nested(&mut self, amount: Weight) -> Self {
291 Self::new(Some(self.weight_left().min(amount)), None)
292 }
293}