// Copyright 2018 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see .
use {Trait, Module, GasSpent};
use runtime_primitives::traits::{As, CheckedMul, CheckedSub, Zero};
use runtime_support::StorageValue;
use balances;
#[must_use]
#[derive(Debug, PartialEq, Eq)]
pub enum GasMeterResult {
Proceed,
OutOfGas,
}
impl GasMeterResult {
pub fn is_out_of_gas(&self) -> bool {
match *self {
GasMeterResult::OutOfGas => true,
GasMeterResult::Proceed => false,
}
}
}
pub struct GasMeter {
limit: T::Gas,
/// Amount of gas left from initial gas limit. Can reach zero.
gas_left: T::Gas,
gas_price: T::Balance,
}
impl GasMeter {
#[cfg(test)]
pub fn with_limit(gas_limit: T::Gas, gas_price: T::Balance) -> GasMeter {
GasMeter {
limit: gas_limit,
gas_left: gas_limit,
gas_price,
}
}
/// Account for used gas.
///
/// Returns `OutOfGas` if there is not enough gas or addition of the specified
/// amount of gas has lead to overflow. On success returns `Proceed`.
///
/// NOTE that `amount` is always consumed, i.e. if there is not enough gas
/// then the counter will be set to zero.
pub fn charge(&mut self, amount: T::Gas) -> GasMeterResult {
let new_value = match self.gas_left.checked_sub(&amount) {
None => None,
Some(val) if val.is_zero() => None,
Some(val) => Some(val),
};
// We always consume the gas even if there is not enough gas.
self.gas_left = new_value.unwrap_or_else(Zero::zero);
match new_value {
Some(_) => GasMeterResult::Proceed,
None => GasMeterResult::OutOfGas,
}
}
/// Account for used gas expressed in balance units.
///
/// Same as [`charge`], but amount to be charged is converted from units of balance to
/// units of gas.
///
/// [`charge`]: #method.charge
pub fn charge_by_balance(&mut self, amount: T::Balance) -> GasMeterResult {
let amount_in_gas: T::Balance = amount / self.gas_price;
let amount_in_gas: T::Gas = >::sa(amount_in_gas);
self.charge(amount_in_gas)
}
/// Allocate some amount of gas and perform some work with
/// a newly created nested gas meter.
///
/// Invokes `f` with either the gas meter that has `amount` gas left or
/// with `None`, if this gas meter has not enough gas to allocate given `amount`.
///
/// All unused gas in the nested gas meter is returned to this gas meter.
pub fn with_nested>) -> R>(
&mut self,
amount: T::Gas,
f: F,
) -> R {
// NOTE that it is ok to allocate all available gas since it still ensured
// by `charge` that it doesn't reach zero.
if self.gas_left < amount {
f(None)
} else {
self.gas_left = self.gas_left - amount;
let mut nested = GasMeter {
limit: amount,
gas_left: amount,
gas_price: self.gas_price,
};
let r = f(Some(&mut nested));
self.gas_left = self.gas_left + nested.gas_left;
r
}
}
/// Returns how much gas left from the initial budget.
pub fn gas_left(&self) -> T::Gas {
self.gas_left
}
/// Returns how much gas was spent.
fn spent(&self) -> T::Gas {
self.limit - self.gas_left
}
}
/// Buy the given amount of gas.
///
/// Cost is calculated by multiplying the gas cost (taken from the storage) by the `gas_limit`.
/// The funds are deducted from `transactor`.
pub fn buy_gas(
transactor: &T::AccountId,
gas_limit: T::Gas,
) -> Result, &'static str> {
// Check if the specified amount of gas is available in the current block.
// This cannot underflow since `gas_spent` is never greater than `block_gas_limit`.
let gas_available = >::block_gas_limit() - >::gas_spent();
if gas_limit > gas_available {
return Err("block gas limit is reached");
}
// Buy the specified amount of gas.
let gas_price = >::gas_price();
let b = >::free_balance(transactor);
let cost = >::as_(gas_limit.clone())
.checked_mul(&gas_price)
.ok_or("overflow multiplying gas limit by price")?;
if b < cost + >::existential_deposit() {
return Err("not enough funds for transaction fee");
}
>::set_free_balance(transactor, b - cost);
>::decrease_total_stake_by(cost);
Ok(GasMeter {
limit: gas_limit,
gas_left: gas_limit,
gas_price,
})
}
/// Refund the unused gas.
pub fn refund_unused_gas(transactor: &T::AccountId, gas_meter: GasMeter) {
// Increase total spent gas.
// This cannot overflow, since `gas_spent` is never greater than `block_gas_limit`, which
// also has T::Gas type.
let gas_spent = >::gas_spent() + gas_meter.spent();
>::put(gas_spent);
// Refund gas left by the price it was bought.
let b = >::free_balance(transactor);
let refund = >::as_(gas_meter.gas_left) * gas_meter.gas_price;
>::set_free_balance(transactor, b + refund);
>::increase_total_stake_by(refund);
}