referrerpolicy=no-referrer-when-downgrade

pallet_revive/metering/
weight.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18#[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/// Meter for syncing the gas between the executor and the weight meter.
39#[derive(DefaultNoBound)]
40struct EngineMeter<T: Config> {
41	fuel: u64,
42	_phantom: PhantomData<T>,
43}
44
45impl<T: Config> EngineMeter<T> {
46	/// Create a meter with the given fuel limit.
47	fn new() -> Self {
48		Self { fuel: 0, _phantom: PhantomData }
49	}
50
51	/// Set the fuel left to the given value.
52	/// Returns the amount of Weight consumed since the last update.
53	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	/// Charge the given amount of ref time.
60	/// Returns the amount of fuel left.
61	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	/// How much ref time does each PolkaVM gas correspond to.
67	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/// Resource that needs to be synced to the executor.
78///
79/// Wrapped to make sure that the resource will be synced back to the executor.
80#[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
99/// This trait represents a token that can be used for charging `WeightMeter`.
100/// There is no other way of charging it.
101///
102/// Implementing type is expected to be super lightweight hence `Copy` (`Clone` is added
103/// for consistency). If inlined there should be no observable difference compared
104/// to a hand-written code.
105pub trait Token<T: Config>: Copy + Clone + TestAuxiliaries {
106	/// Return the amount of weight that should be taken by this token.
107	///
108	/// This function should be really lightweight and must not fail. It is not
109	/// expected that implementors will query the storage or do any kinds of heavy operations.
110	///
111	/// That said, implementors of this function still can run into overflows
112	/// while calculating the amount. In this case it is ok to use saturating operations
113	/// since on overflow they will return `max_value` which should consume all weight.
114	fn weight(&self) -> Weight;
115
116	/// Returns true if this token is expected to influence the lowest weight limit.
117	fn influence_lowest_weight_limit(&self) -> bool {
118		true
119	}
120}
121
122/// A wrapper around a type-erased trait object of what used to be a `Token`.
123#[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	/// The overall weight limit of this weight meter. If it is None, then there is no restriction
132	pub weight_limit: Option<Weight>,
133	/// The current actual effective weight limit. Used to check whether the weight meter ran out
134	/// of resources. This weight limit needs to be adapted whenever the metering runs in ethereum
135	/// mode and there is a charge on the deposit meter.
136	effective_weight_limit: Weight,
137	/// Amount of weight already consumed. Must be < `weight_limit`.
138	weight_consumed: Weight,
139	/// Due to `adjust_weight` and `nested` the `weight_consumed` can temporarily peak above its
140	/// final value.
141	weight_consumed_highest: Weight,
142	/// The amount of resources that was consumed by the execution engine.
143	/// We have to track it separately in order to avoid the loss of precision that happens when
144	/// converting from ref_time to the execution engine unit.
145	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	/// Absorb the remaining weight of a nested meter after we are done using it.
170	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	/// Account for used weight.
179	///
180	/// Amount is calculated by the given `token`.
181	///
182	/// Returns `OutOfGas` if there is not enough weight or addition of the specified
183	/// amount of weight has lead to overflow.
184	///
185	/// NOTE that amount isn't consumed if there is not enough weight. This is considered
186	/// safe because we always charge weight before performing any resource-spending action.
187	#[inline]
188	pub fn charge<Tok: Token<T>>(&mut self, token: Tok) -> Result<ChargedAmount, DispatchError> {
189		#[cfg(test)]
190		{
191			// Unconditionally add the token to the storage.
192			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		// It is OK to not charge anything on failure because we always charge _before_ we perform
199		// any action
200		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	/// Charge the specified token amount of weight or halt if not enough weight is left.
210	#[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	/// Adjust a previously charged amount down to its actual amount.
220	///
221	/// This is when a maximum a priori amount was charged and then should be partially
222	/// refunded to match the actual amount.
223	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	/// Hand over the gas metering responsibility from the executor to this meter.
232	///
233	/// Needs to be called when entering a host function to update this meter with the
234	/// gas that was tracked by the executor. It tracks the latest seen total value
235	/// in order to compute the delta that needs to be charged.
236	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	/// Hand over the gas metering responsibility from this meter to the executor.
251	///
252	/// Needs to be called when leaving a host function in order to calculate how much
253	/// gas needs to be charged from the **executor**. It updates the last seen executor
254	/// total value so that it is correct when `sync_from_executor` is called the next time.
255	///
256	/// It is important that this does **not** actually sync with the executor. That has
257	/// to be done by the caller.
258	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	/// Returns the amount of weight that is required to run the same call.
263	///
264	/// This can be different from `weight_spent` because due to `adjust_weight` the amount of
265	/// spent weight can temporarily drop and be refunded later.
266	pub fn weight_required(&self) -> Weight {
267		self.weight_consumed_highest.max(self.weight_consumed)
268	}
269
270	/// Returns how much weight was spent
271	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	/// Returns how much weight left from the initial budget.
280	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}