referrerpolicy=no-referrer-when-downgrade

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