referrerpolicy=no-referrer-when-downgrade

pallet_revive/
gas.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
18use crate::{exec::ExecError, weights::WeightInfo, Config, Error};
19use core::marker::PhantomData;
20use frame_support::{
21	dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, PostDispatchInfo},
22	weights::Weight,
23	DefaultNoBound,
24};
25use sp_runtime::DispatchError;
26
27#[cfg(test)]
28use std::{any::Any, fmt::Debug};
29
30#[derive(Debug, PartialEq, Eq)]
31pub struct ChargedAmount(Weight);
32
33impl ChargedAmount {
34	pub fn amount(&self) -> Weight {
35		self.0
36	}
37}
38
39/// Meter for syncing the gas between the executor and the gas meter.
40#[derive(DefaultNoBound)]
41struct EngineMeter<T: Config> {
42	fuel: u64,
43	_phantom: PhantomData<T>,
44}
45
46impl<T: Config> EngineMeter<T> {
47	/// Create a meter with the given fuel limit.
48	fn new(limit: Weight) -> Self {
49		Self {
50			fuel: limit.ref_time().saturating_div(Self::ref_time_per_fuel()),
51			_phantom: PhantomData,
52		}
53	}
54
55	/// Set the fuel left to the given value.
56	/// Returns the amount of Weight consumed since the last update.
57	fn set_fuel(&mut self, fuel: u64) -> Weight {
58		let consumed = self.fuel.saturating_sub(fuel).saturating_mul(Self::ref_time_per_fuel());
59		self.fuel = fuel;
60		Weight::from_parts(consumed, 0)
61	}
62
63	/// Charge the given amount of gas.
64	/// Returns the amount of fuel left.
65	fn charge_ref_time(&mut self, ref_time: u64) -> Result<Syncable, DispatchError> {
66		let amount = ref_time
67			.checked_div(Self::ref_time_per_fuel())
68			.ok_or(Error::<T>::InvalidSchedule)?;
69
70		self.fuel.checked_sub(amount).ok_or_else(|| Error::<T>::OutOfGas)?;
71		Ok(Syncable(self.fuel.try_into().map_err(|_| Error::<T>::OutOfGas)?))
72	}
73
74	/// How much ref time does each PolkaVM gas correspond to.
75	fn ref_time_per_fuel() -> u64 {
76		let loop_iteration =
77			T::WeightInfo::instr(1).saturating_sub(T::WeightInfo::instr(0)).ref_time();
78		let empty_loop_iteration = T::WeightInfo::instr_empty_loop(1)
79			.saturating_sub(T::WeightInfo::instr_empty_loop(0))
80			.ref_time();
81		loop_iteration.saturating_sub(empty_loop_iteration)
82	}
83}
84
85/// Used to capture the gas left before entering a host function.
86///
87/// Has to be consumed in order to sync back the gas after leaving the host function.
88#[must_use]
89pub struct RefTimeLeft(u64);
90
91/// Resource that needs to be synced to the executor.
92///
93/// Wrapped to make sure that the resource will be synced back to the executor.
94#[must_use]
95pub struct Syncable(polkavm::Gas);
96
97impl From<Syncable> for polkavm::Gas {
98	fn from(from: Syncable) -> Self {
99		from.0
100	}
101}
102
103#[cfg(not(test))]
104pub trait TestAuxiliaries {}
105#[cfg(not(test))]
106impl<T> TestAuxiliaries for T {}
107
108#[cfg(test)]
109pub trait TestAuxiliaries: Any + Debug + PartialEq + Eq {}
110#[cfg(test)]
111impl<T: Any + Debug + PartialEq + Eq> TestAuxiliaries for T {}
112
113/// This trait represents a token that can be used for charging `GasMeter`.
114/// There is no other way of charging it.
115///
116/// Implementing type is expected to be super lightweight hence `Copy` (`Clone` is added
117/// for consistency). If inlined there should be no observable difference compared
118/// to a hand-written code.
119pub trait Token<T: Config>: Copy + Clone + TestAuxiliaries {
120	/// Return the amount of gas that should be taken by this token.
121	///
122	/// This function should be really lightweight and must not fail. It is not
123	/// expected that implementors will query the storage or do any kinds of heavy operations.
124	///
125	/// That said, implementors of this function still can run into overflows
126	/// while calculating the amount. In this case it is ok to use saturating operations
127	/// since on overflow they will return `max_value` which should consume all gas.
128	fn weight(&self) -> Weight;
129
130	/// Returns true if this token is expected to influence the lowest gas limit.
131	fn influence_lowest_gas_limit(&self) -> bool {
132		true
133	}
134}
135
136/// A wrapper around a type-erased trait object of what used to be a `Token`.
137#[cfg(test)]
138pub struct ErasedToken {
139	pub description: String,
140	pub token: Box<dyn Any>,
141}
142
143#[derive(DefaultNoBound)]
144pub struct GasMeter<T: Config> {
145	gas_limit: Weight,
146	/// Amount of gas left from initial gas limit. Can reach zero.
147	gas_left: Weight,
148	/// Due to `adjust_gas` and `nested` the `gas_left` can temporarily dip below its final value.
149	gas_left_lowest: Weight,
150	/// The amount of resources that was consumed by the execution engine.
151	/// We have to track it separately in order to avoid the loss of precision that happens when
152	/// converting from ref_time to the execution engine unit.
153	engine_meter: EngineMeter<T>,
154	_phantom: PhantomData<T>,
155	#[cfg(test)]
156	tokens: Vec<ErasedToken>,
157}
158
159impl<T: Config> GasMeter<T> {
160	pub fn new(gas_limit: Weight) -> Self {
161		GasMeter {
162			gas_limit,
163			gas_left: gas_limit,
164			gas_left_lowest: gas_limit,
165			engine_meter: EngineMeter::new(gas_limit),
166			_phantom: PhantomData,
167			#[cfg(test)]
168			tokens: Vec::new(),
169		}
170	}
171
172	/// Create a new gas meter by removing *all* the gas from the current meter.
173	///
174	/// This should only be used by the primordial frame in a sequence of calls - every subsequent
175	/// frame should use [`nested`](Self::nested).
176	pub fn nested_take_all(&mut self) -> Self {
177		let gas_left = self.gas_left;
178		self.gas_left -= gas_left;
179		GasMeter::new(gas_left)
180	}
181
182	/// Create a new gas meter for a nested call by removing gas from the current meter.
183	pub fn nested(&mut self, amount: Weight) -> Self {
184		let amount = amount.min(self.gas_left);
185		self.gas_left -= amount;
186		GasMeter::new(amount)
187	}
188
189	/// Absorb the remaining gas of a nested meter after we are done using it.
190	pub fn absorb_nested(&mut self, nested: Self) {
191		self.gas_left_lowest = (self.gas_left + nested.gas_limit)
192			.saturating_sub(nested.gas_required())
193			.min(self.gas_left_lowest);
194		self.gas_left += nested.gas_left;
195	}
196
197	/// Account for used gas.
198	///
199	/// Amount is calculated by the given `token`.
200	///
201	/// Returns `OutOfGas` if there is not enough gas or addition of the specified
202	/// amount of gas has lead to overflow.
203	///
204	/// NOTE that amount isn't consumed if there is not enough gas. This is considered
205	/// safe because we always charge gas before performing any resource-spending action.
206	#[inline]
207	pub fn charge<Tok: Token<T>>(&mut self, token: Tok) -> Result<ChargedAmount, DispatchError> {
208		#[cfg(test)]
209		{
210			// Unconditionally add the token to the storage.
211			let erased_tok =
212				ErasedToken { description: format!("{:?}", token), token: Box::new(token) };
213			self.tokens.push(erased_tok);
214		}
215		let amount = token.weight();
216		// It is OK to not charge anything on failure because we always charge _before_ we perform
217		// any action
218		self.gas_left = self.gas_left.checked_sub(&amount).ok_or_else(|| Error::<T>::OutOfGas)?;
219		Ok(ChargedAmount(amount))
220	}
221
222	/// Charge the specified amount of EVM gas.
223	/// This is used for basic opcodes (e.g arithmetic, bitwise, ...) that don't have a dedicated
224	/// benchmark
225	pub fn charge_evm_gas(&mut self, gas: u64) -> Result<(), DispatchError> {
226		let base_cost = T::WeightInfo::evm_opcode(1).saturating_sub(T::WeightInfo::evm_opcode(0));
227		self.gas_left = self
228			.gas_left
229			.checked_sub(&base_cost.saturating_mul(gas))
230			.ok_or_else(|| Error::<T>::OutOfGas)?;
231		Ok(())
232	}
233
234	/// Adjust a previously charged amount down to its actual amount.
235	///
236	/// This is when a maximum a priori amount was charged and then should be partially
237	/// refunded to match the actual amount.
238	pub fn adjust_gas<Tok: Token<T>>(&mut self, charged_amount: ChargedAmount, token: Tok) {
239		if token.influence_lowest_gas_limit() {
240			self.gas_left_lowest = self.gas_left_lowest();
241		}
242		let adjustment = charged_amount.0.saturating_sub(token.weight());
243		self.gas_left = self.gas_left.saturating_add(adjustment).min(self.gas_limit);
244	}
245
246	/// Hand over the gas metering responsibility from the executor to this meter.
247	///
248	/// Needs to be called when entering a host function to update this meter with the
249	/// gas that was tracked by the executor. It tracks the latest seen total value
250	/// in order to compute the delta that needs to be charged.
251	pub fn sync_from_executor(
252		&mut self,
253		engine_fuel: polkavm::Gas,
254	) -> Result<RefTimeLeft, DispatchError> {
255		let weight_consumed = self
256			.engine_meter
257			.set_fuel(engine_fuel.try_into().map_err(|_| Error::<T>::OutOfGas)?);
258		self.gas_left
259			.checked_reduce(weight_consumed)
260			.ok_or_else(|| Error::<T>::OutOfGas)?;
261		Ok(RefTimeLeft(self.gas_left.ref_time()))
262	}
263
264	/// Hand over the gas metering responsibility from this meter to the executor.
265	///
266	/// Needs to be called when leaving a host function in order to calculate how much
267	/// gas needs to be charged from the **executor**. It updates the last seen executor
268	/// total value so that it is correct when `sync_from_executor` is called the next time.
269	///
270	/// It is important that this does **not** actually sync with the executor. That has
271	/// to be done by the caller.
272	pub fn sync_to_executor(&mut self, before: RefTimeLeft) -> Result<Syncable, DispatchError> {
273		let ref_time_consumed = before.0.saturating_sub(self.gas_left().ref_time());
274		self.engine_meter.charge_ref_time(ref_time_consumed)
275	}
276
277	/// Returns the amount of gas that is required to run the same call.
278	///
279	/// This can be different from `gas_spent` because due to `adjust_gas` the amount of
280	/// spent gas can temporarily drop and be refunded later.
281	pub fn gas_required(&self) -> Weight {
282		self.gas_limit.saturating_sub(self.gas_left_lowest())
283	}
284
285	/// Returns how much gas was spent
286	pub fn gas_consumed(&self) -> Weight {
287		self.gas_limit.saturating_sub(self.gas_left)
288	}
289
290	/// Returns how much gas left from the initial budget.
291	pub fn gas_left(&self) -> Weight {
292		self.gas_left
293	}
294
295	/// The amount of gas in terms of engine gas.
296	pub fn engine_fuel_left(&self) -> Result<polkavm::Gas, DispatchError> {
297		self.engine_meter.fuel.try_into().map_err(|_| <Error<T>>::OutOfGas.into())
298	}
299
300	/// Turn this GasMeter into a DispatchResult that contains the actually used gas.
301	pub fn into_dispatch_result<R, E>(
302		self,
303		result: Result<R, E>,
304		base_weight: Weight,
305	) -> DispatchResultWithPostInfo
306	where
307		E: Into<ExecError>,
308	{
309		let post_info = PostDispatchInfo {
310			actual_weight: Some(self.gas_consumed().saturating_add(base_weight)),
311			pays_fee: Default::default(),
312		};
313
314		result
315			.map(|_| post_info)
316			.map_err(|e| DispatchErrorWithPostInfo { post_info, error: e.into().error })
317	}
318
319	fn gas_left_lowest(&self) -> Weight {
320		self.gas_left_lowest.min(self.gas_left)
321	}
322
323	#[cfg(test)]
324	pub fn tokens(&self) -> &[ErasedToken] {
325		&self.tokens
326	}
327}
328
329#[cfg(test)]
330mod tests {
331	use super::{GasMeter, Token, Weight};
332	use crate::tests::Test;
333
334	/// A simple utility macro that helps to match against a
335	/// list of tokens.
336	macro_rules! match_tokens {
337		($tokens_iter:ident,) => {
338		};
339		($tokens_iter:ident, $x:expr, $($rest:tt)*) => {
340			{
341				let next = ($tokens_iter).next().unwrap();
342				let pattern = $x;
343
344				// Note that we don't specify the type name directly in this macro,
345				// we only have some expression $x of some type. At the same time, we
346				// have an iterator of Box<dyn Any> and to downcast we need to specify
347				// the type which we want downcast to.
348				//
349				// So what we do is we assign `_pattern_typed_next_ref` to a variable which has
350				// the required type.
351				//
352				// Then we make `_pattern_typed_next_ref = token.downcast_ref()`. This makes
353				// rustc infer the type `T` (in `downcast_ref<T: Any>`) to be the same as in $x.
354
355				let mut _pattern_typed_next_ref = &pattern;
356				_pattern_typed_next_ref = match next.token.downcast_ref() {
357					Some(p) => {
358						assert_eq!(p, &pattern);
359						p
360					}
361					None => {
362						panic!("expected type {} got {}", stringify!($x), next.description);
363					}
364				};
365			}
366
367			match_tokens!($tokens_iter, $($rest)*);
368		};
369	}
370
371	/// A trivial token that charges the specified number of gas units.
372	#[derive(Copy, Clone, PartialEq, Eq, Debug)]
373	struct SimpleToken(u64);
374	impl Token<Test> for SimpleToken {
375		fn weight(&self) -> Weight {
376			Weight::from_parts(self.0, 0)
377		}
378	}
379
380	#[test]
381	fn it_works() {
382		let gas_meter = GasMeter::<Test>::new(Weight::from_parts(50000, 0));
383		assert_eq!(gas_meter.gas_left(), Weight::from_parts(50000, 0));
384	}
385
386	#[test]
387	fn tracing() {
388		let mut gas_meter = GasMeter::<Test>::new(Weight::from_parts(50000, 0));
389		assert!(!gas_meter.charge(SimpleToken(1)).is_err());
390
391		let mut tokens = gas_meter.tokens().iter();
392		match_tokens!(tokens, SimpleToken(1),);
393	}
394
395	// This test makes sure that nothing can be executed if there is no gas.
396	#[test]
397	fn refuse_to_execute_anything_if_zero() {
398		let mut gas_meter = GasMeter::<Test>::new(Weight::zero());
399		assert!(gas_meter.charge(SimpleToken(1)).is_err());
400	}
401
402	/// Previously, passing a `Weight` of 0 to `nested` would consume all of the meter's current
403	/// gas.
404	///
405	/// Now, a `Weight` of 0 means no gas for the nested call.
406	#[test]
407	fn nested_zero_gas_requested() {
408		let test_weight = 50000.into();
409		let mut gas_meter = GasMeter::<Test>::new(test_weight);
410		let gas_for_nested_call = gas_meter.nested(0.into());
411
412		assert_eq!(gas_meter.gas_left(), 50000.into());
413		assert_eq!(gas_for_nested_call.gas_left(), 0.into())
414	}
415
416	#[test]
417	fn nested_some_gas_requested() {
418		let test_weight = 50000.into();
419		let mut gas_meter = GasMeter::<Test>::new(test_weight);
420		let gas_for_nested_call = gas_meter.nested(10000.into());
421
422		assert_eq!(gas_meter.gas_left(), 40000.into());
423		assert_eq!(gas_for_nested_call.gas_left(), 10000.into())
424	}
425
426	#[test]
427	fn nested_all_gas_requested() {
428		let test_weight = Weight::from_parts(50000, 50000);
429		let mut gas_meter = GasMeter::<Test>::new(test_weight);
430		let gas_for_nested_call = gas_meter.nested(test_weight);
431
432		assert_eq!(gas_meter.gas_left(), Weight::from_parts(0, 0));
433		assert_eq!(gas_for_nested_call.gas_left(), 50_000.into())
434	}
435
436	#[test]
437	fn nested_excess_gas_requested() {
438		let test_weight = Weight::from_parts(50000, 50000);
439		let mut gas_meter = GasMeter::<Test>::new(test_weight);
440		let gas_for_nested_call = gas_meter.nested(test_weight + 10000.into());
441
442		assert_eq!(gas_meter.gas_left(), Weight::from_parts(0, 0));
443		assert_eq!(gas_for_nested_call.gas_left(), 50_000.into())
444	}
445
446	// Make sure that the gas meter does not charge in case of overcharge
447	#[test]
448	fn overcharge_does_not_charge() {
449		let mut gas_meter = GasMeter::<Test>::new(Weight::from_parts(200, 0));
450
451		// The first charge is should lead to OOG.
452		assert!(gas_meter.charge(SimpleToken(300)).is_err());
453
454		// The gas meter should still contain the full 200.
455		assert!(gas_meter.charge(SimpleToken(200)).is_ok());
456	}
457
458	// Charging the exact amount that the user paid for should be
459	// possible.
460	#[test]
461	fn charge_exact_amount() {
462		let mut gas_meter = GasMeter::<Test>::new(Weight::from_parts(25, 0));
463		assert!(!gas_meter.charge(SimpleToken(25)).is_err());
464	}
465}