git.delta.rocks / unique-network / refs/commits / e8fdadb89923

difftreelog

source

pallets/contracts/src/gas.rs10.4 KiBsourcehistory
1// This file is part of Substrate.23// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd.4// SPDX-License-Identifier: Apache-2.056// 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 at9//10// 	http://www.apache.org/licenses/LICENSE-2.011//12// Unless required by applicable law or agreed to in writing, software13// 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 and16// limitations under the License.1718use crate::{Config, Error, exec::ExecError};19use sp_std::marker::PhantomData;20use sp_runtime::traits::Zero;21use frame_support::{22	dispatch::{23		DispatchResultWithPostInfo, PostDispatchInfo, DispatchErrorWithPostInfo, DispatchError,24	},25	weights::Weight,26};27use sp_core::crypto::UncheckedFrom;2829#[cfg(test)]30use std::{any::Any, fmt::Debug};3132#[derive(Debug, PartialEq, Eq)]33pub struct ChargedAmount(Weight);3435impl ChargedAmount {36	pub fn amount(&self) -> Weight {37		self.038	}39}4041#[cfg(not(test))]42pub trait TestAuxiliaries {}43#[cfg(not(test))]44impl<T> TestAuxiliaries for T {}4546#[cfg(test)]47pub trait TestAuxiliaries: Any + Debug + PartialEq + Eq {}48#[cfg(test)]49impl<T: Any + Debug + PartialEq + Eq> TestAuxiliaries for T {}5051/// This trait represents a token that can be used for charging `GasMeter`.52/// There is no other way of charging it.53///54/// Implementing type is expected to be super lightweight hence `Copy` (`Clone` is added55/// for consistency). If inlined there should be no observable difference compared56/// to a hand-written code.57pub trait Token<T: Config>: Copy + Clone + TestAuxiliaries {58	/// Metadata type, which the token can require for calculating the amount59	/// of gas to charge. Can be a some configuration type or60	/// just the `()`.61	type Metadata;6263	/// Calculate amount of gas that should be taken by this token.64	///65	/// This function should be really lightweight and must not fail. It is not66	/// expected that implementors will query the storage or do any kinds of heavy operations.67	///68	/// That said, implementors of this function still can run into overflows69	/// while calculating the amount. In this case it is ok to use saturating operations70	/// since on overflow they will return `max_value` which should consume all gas.71	fn calculate_amount(&self, metadata: &Self::Metadata) -> Weight;72}7374/// A wrapper around a type-erased trait object of what used to be a `Token`.75#[cfg(test)]76pub struct ErasedToken {77	pub description: String,78	pub token: Box<dyn Any>,79}8081pub struct GasMeter<T: Config> {82	gas_limit: Weight,83	/// Amount of gas left from initial gas limit. Can reach zero.84	gas_left: Weight,85	_phantom: PhantomData<T>,86	#[cfg(test)]87	tokens: Vec<ErasedToken>,88}8990impl<T: Config> GasMeter<T>91where92	T::AccountId: UncheckedFrom<<T as frame_system::Config>::Hash> + AsRef<[u8]>,93{94	pub fn new(gas_limit: Weight) -> Self {95		GasMeter {96			gas_limit,97			gas_left: gas_limit,98			_phantom: PhantomData,99			#[cfg(test)]100			tokens: Vec::new(),101		}102	}103104	/// Account for used gas.105	///106	/// Amount is calculated by the given `token`.107	///108	/// Returns `OutOfGas` if there is not enough gas or addition of the specified109	/// amount of gas has lead to overflow. On success returns `Proceed`.110	///111	/// NOTE that amount is always consumed, i.e. if there is not enough gas112	/// then the counter will be set to zero.113	#[inline]114	pub fn charge<Tok: Token<T>>(115		&mut self,116		metadata: &Tok::Metadata,117		token: Tok,118	) -> Result<ChargedAmount, DispatchError> {119		#[cfg(test)]120		{121			// Unconditionally add the token to the storage.122			let erased_tok = ErasedToken {123				description: format!("{:?}", token),124				token: Box::new(token),125			};126			self.tokens.push(erased_tok);127		}128129		let amount = token.calculate_amount(metadata);130		let new_value = self.gas_left.checked_sub(amount);131132		// We always consume the gas even if there is not enough gas.133		self.gas_left = new_value.unwrap_or_else(Zero::zero);134135		match new_value {136			Some(_) => Ok(ChargedAmount(amount)),137			None => Err(Error::<T>::OutOfGas.into()),138		}139	}140141	/// Adjust a previously charged amount down to its actual amount.142	///143	/// This is when a maximum a priori amount was charged and then should be partially144	/// refunded to match the actual amount.145	pub fn adjust_gas<Tok: Token<T>>(146		&mut self,147		charged_amount: ChargedAmount,148		metadata: &Tok::Metadata,149		token: Tok,150	) {151		let adjustment = charged_amount152			.0153			.saturating_sub(token.calculate_amount(metadata));154		self.gas_left = self.gas_left.saturating_add(adjustment).min(self.gas_limit);155	}156157	/// Refund previously charged gas back to the gas meter.158	///159	/// This can be used if a gas worst case estimation must be charged before160	/// performing a certain action. This way the difference can be refundend when161	/// the worst case did not happen.162	pub fn refund(&mut self, amount: ChargedAmount) {163		self.gas_left = self.gas_left.saturating_add(amount.0).min(self.gas_limit)164	}165166	/// Allocate some amount of gas and perform some work with167	/// a newly created nested gas meter.168	///169	/// Invokes `f` with either the gas meter that has `amount` gas left or170	/// with `None`, if this gas meter has not enough gas to allocate given `amount`.171	///172	/// All unused gas in the nested gas meter is returned to this gas meter.173	pub fn with_nested<R, F: FnOnce(Option<&mut GasMeter<T>>) -> R>(174		&mut self,175		amount: Weight,176		f: F,177	) -> R {178		// NOTE that it is ok to allocate all available gas since it still ensured179		// by `charge` that it doesn't reach zero.180		if self.gas_left < amount {181			f(None)182		} else {183			self.gas_left = self.gas_left - amount;184			let mut nested = GasMeter::new(amount);185186			let r = f(Some(&mut nested));187188			self.gas_left = self.gas_left + nested.gas_left;189190			r191		}192	}193194	/// Returns how much gas was used.195	pub fn gas_spent(&self) -> Weight {196		self.gas_limit - self.gas_left197	}198199	/// Returns how much gas left from the initial budget.200	pub fn gas_left(&self) -> Weight {201		self.gas_left202	}203204	/// Turn this GasMeter into a DispatchResult that contains the actually used gas.205	pub fn into_dispatch_result<R, E>(206		self,207		result: Result<R, E>,208		base_weight: Weight,209	) -> DispatchResultWithPostInfo210	where211		E: Into<ExecError>,212	{213		let post_info = PostDispatchInfo {214			actual_weight: Some(self.gas_spent().saturating_add(base_weight)),215			pays_fee: Default::default(),216		};217218		result219			.map(|_| post_info)220			.map_err(|e| DispatchErrorWithPostInfo {221				post_info,222				error: e.into().error,223			})224	}225226	#[cfg(test)]227	pub fn tokens(&self) -> &[ErasedToken] {228		&self.tokens229	}230}231232#[cfg(test)]233mod tests {234	use super::{GasMeter, Token};235	use crate::tests::Test;236237	/// A simple utility macro that helps to match against a238	/// list of tokens.239	macro_rules! match_tokens {240		($tokens_iter:ident,) => {241		};242		($tokens_iter:ident, $x:expr, $($rest:tt)*) => {243			{244				let next = ($tokens_iter).next().unwrap();245				let pattern = $x;246247				// Note that we don't specify the type name directly in this macro,248				// we only have some expression $x of some type. At the same time, we249				// have an iterator of Box<dyn Any> and to downcast we need to specify250				// the type which we want downcast to.251				//252				// So what we do is we assign `_pattern_typed_next_ref` to a variable which has253				// the required type.254				//255				// Then we make `_pattern_typed_next_ref = token.downcast_ref()`. This makes256				// rustc infer the type `T` (in `downcast_ref<T: Any>`) to be the same as in $x.257258				let mut _pattern_typed_next_ref = &pattern;259				_pattern_typed_next_ref = match next.token.downcast_ref() {260					Some(p) => {261						assert_eq!(p, &pattern);262						p263					}264					None => {265						panic!("expected type {} got {}", stringify!($x), next.description);266					}267				};268			}269270			match_tokens!($tokens_iter, $($rest)*);271		};272	}273274	/// A trivial token that charges the specified number of gas units.275	#[derive(Copy, Clone, PartialEq, Eq, Debug)]276	struct SimpleToken(u64);277	impl Token<Test> for SimpleToken {278		type Metadata = ();279		fn calculate_amount(&self, _metadata: &()) -> u64 {280			self.0281		}282	}283284	struct MultiplierTokenMetadata {285		multiplier: u64,286	}287	/// A simple token that charges for the given amount multiplied to288	/// a multiplier taken from a given metadata.289	#[derive(Copy, Clone, PartialEq, Eq, Debug)]290	struct MultiplierToken(u64);291292	impl Token<Test> for MultiplierToken {293		type Metadata = MultiplierTokenMetadata;294		fn calculate_amount(&self, metadata: &MultiplierTokenMetadata) -> u64 {295			// Probably you want to use saturating mul in production code.296			self.0 * metadata.multiplier297		}298	}299300	#[test]301	fn it_works() {302		let gas_meter = GasMeter::<Test>::new(50000);303		assert_eq!(gas_meter.gas_left(), 50000);304	}305306	#[test]307	fn simple() {308		let mut gas_meter = GasMeter::<Test>::new(50000);309310		let result = gas_meter.charge(311			&MultiplierTokenMetadata { multiplier: 3 },312			MultiplierToken(10),313		);314		assert!(!result.is_err());315316		assert_eq!(gas_meter.gas_left(), 49_970);317	}318319	#[test]320	fn tracing() {321		let mut gas_meter = GasMeter::<Test>::new(50000);322		assert!(!gas_meter.charge(&(), SimpleToken(1)).is_err());323		assert!(!gas_meter324			.charge(325				&MultiplierTokenMetadata { multiplier: 3 },326				MultiplierToken(10)327			)328			.is_err());329330		let mut tokens = gas_meter.tokens()[0..2].iter();331		match_tokens!(tokens, SimpleToken(1), MultiplierToken(10),);332	}333334	// This test makes sure that nothing can be executed if there is no gas.335	#[test]336	fn refuse_to_execute_anything_if_zero() {337		let mut gas_meter = GasMeter::<Test>::new(0);338		assert!(gas_meter.charge(&(), SimpleToken(1)).is_err());339	}340341	// Make sure that if the gas meter is charged by exceeding amount then not only an error342	// returned for that charge, but also for all consequent charges.343	//344	// This is not strictly necessary, because the execution should be interrupted immediately345	// if the gas meter runs out of gas. However, this is just a nice property to have.346	#[test]347	fn overcharge_is_unrecoverable() {348		let mut gas_meter = GasMeter::<Test>::new(200);349350		// The first charge is should lead to OOG.351		assert!(gas_meter.charge(&(), SimpleToken(300)).is_err());352353		// The gas meter is emptied at this moment, so this should also fail.354		assert!(gas_meter.charge(&(), SimpleToken(1)).is_err());355	}356357	// Charging the exact amount that the user paid for should be358	// possible.359	#[test]360	fn charge_exact_amount() {361		let mut gas_meter = GasMeter::<Test>::new(25);362		assert!(!gas_meter.charge(&(), SimpleToken(25)).is_err());363	}364}