123456789101112131415161718use 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 {}5051525354555657pub trait Token<T: Config>: Copy + Clone + TestAuxiliaries {58 59 60 61 type Metadata;6263 64 65 66 67 68 69 70 71 fn calculate_amount(&self, metadata: &Self::Metadata) -> Weight;72}737475#[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 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 105 106 107 108 109 110 111 112 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 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 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 142 143 144 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 158 159 160 161 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 167 168 169 170 171 172 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 179 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 195 pub fn gas_spent(&self) -> Weight {196 self.gas_limit - self.gas_left197 }198199 200 pub fn gas_left(&self) -> Weight {201 self.gas_left202 }203204 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 238 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 248 249 250 251 252 253 254 255 256 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 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 288 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 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 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 342 343 344 345 346 #[test]347 fn overcharge_is_unrecoverable() {348 let mut gas_meter = GasMeter::<Test>::new(200);349350 351 assert!(gas_meter.charge(&(), SimpleToken(300)).is_err());352353 354 assert!(gas_meter.charge(&(), SimpleToken(1)).is_err());355 }356357 358 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}