Skip to content

Commit

Permalink
implement FIP-0032 gas parameters and charges (#534)
Browse files Browse the repository at this point in the history
* exec gas: apply only on non-structural instructions.

* pricelist: add new gas fees.

* gas: fully switch to milligas as canonical unit.

* price list: update gas cost formulae.

* price list: vertical spacing.

* kernel: apply extern gas.

* syscalls: charge for gas; small refactors.

* remove Kernel#charge_milligas().

Canonical unit is now milligas everywhere.

* fix tests.

* fix param.

* clippy.

* add comment.

* fix gas instrumentation.

* rename GasTracker#{charge_gas=>apply_charge} to disambiguate.

* add Gas and Milligas unit types to disambiguate.

* convert public gas charges to milligas.

* inclusion cost: adapt to milligas.

* charge_gas: move conversion to milligas to syscall handler.

* {to_milligas=>static_milligas}; make private.

* clippy.

* price list: remove compute_gas_multiplier (unused).

* polish comment.

* use saturation arithmetics for mem costs.

* price list: use Milligas type for cost fields.

* rename Kernel#charge_{=>milli}gas.

* add extern cost in syscall pricing formulae.

* charge for syscall gas at the right place.
  • Loading branch information
raulk authored May 10, 2022
1 parent 9beea15 commit 6a3dec1
Show file tree
Hide file tree
Showing 13 changed files with 351 additions and 239 deletions.
2 changes: 1 addition & 1 deletion fvm/src/call_manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ pub trait CallManager: 'static {

/// Charge gas.
fn charge_gas(&mut self, charge: GasCharge) -> Result<()> {
self.gas_tracker_mut().charge_gas(charge)?;
self.gas_tracker_mut().apply_charge(charge)?;
Ok(())
}
}
Expand Down
4 changes: 2 additions & 2 deletions fvm/src/executor/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use num_traits::Zero;

use super::{ApplyFailure, ApplyKind, ApplyRet, Executor};
use crate::call_manager::{backtrace, CallManager, InvocationResult};
use crate::gas::{GasCharge, GasOutputs};
use crate::gas::{milligas_to_gas, GasCharge, GasOutputs};
use crate::kernel::{ClassifyResult, Context as _, ExecutionError, Kernel};
use crate::machine::{Machine, BURNT_FUNDS_ACTOR_ADDR, REWARD_ACTOR_ADDR};

Expand Down Expand Up @@ -220,7 +220,7 @@ where
ApplyKind::Implicit => (GasCharge::new("none", 0, 0), Default::default()),
ApplyKind::Explicit => {
let inclusion_cost = pl.on_chain_message(raw_length);
let inclusion_total = inclusion_cost.total();
let inclusion_total = milligas_to_gas(inclusion_cost.total(), true);

// Verify the cost of the message is not over the message gas limit.
if inclusion_total > msg.gas_limit {
Expand Down
15 changes: 10 additions & 5 deletions fvm/src/gas/charge.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
// Copyright 2019-2022 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

use crate::gas::Milligas;

/// Single gas charge in the VM. Contains information about what gas was for, as well
/// as the amount of gas needed for computation and storage respectively.
pub struct GasCharge<'a> {
pub name: &'a str,
pub compute_gas: i64,
pub storage_gas: i64,
/// Compute costs in milligas.
pub compute_gas: Milligas,
/// Storage costs in milligas.
pub storage_gas: Milligas,
}

impl<'a> GasCharge<'a> {
pub fn new(name: &'a str, compute_gas: i64, storage_gas: i64) -> Self {
pub fn new(name: &'a str, compute_gas: Milligas, storage_gas: Milligas) -> Self {
Self {
name,
compute_gas,
storage_gas,
}
}

/// Calculates total gas charge based on compute and storage multipliers.
pub fn total(&self) -> i64 {
/// Calculates total gas charge (in milligas) by summing compute and
/// storage gas associated with this charge.
pub fn total(&self) -> Milligas {
self.compute_gas + self.storage_gas
}
}
46 changes: 27 additions & 19 deletions fvm/src/gas/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,19 @@ mod price_list;

pub const MILLIGAS_PRECISION: i64 = 1000;

// Type aliases to disambiguate units in interfaces.
pub type Gas = i64;
pub type Milligas = i64;

pub struct GasTracker {
milligas_limit: i64,
milligas_used: i64,
}

impl GasTracker {
pub fn new(gas_limit: i64, gas_used: i64) -> Self {
/// Gas limit and gas used are provided in protocol units (i.e. full units).
/// They are converted to milligas for internal canonical accounting.
pub fn new(gas_limit: Gas, gas_used: Gas) -> Self {
Self {
milligas_limit: gas_to_milligas(gas_limit),
milligas_used: gas_to_milligas(gas_used),
Expand All @@ -27,7 +33,7 @@ impl GasTracker {

/// Safely consumes gas and returns an out of gas error if there is not sufficient
/// enough gas remaining for charge.
pub fn charge_milligas(&mut self, name: &str, to_use: i64) -> Result<()> {
pub fn charge_milligas(&mut self, name: &str, to_use: Milligas) -> Result<()> {
match self.milligas_used.checked_add(to_use) {
None => {
log::trace!("gas overflow: {}", name);
Expand All @@ -48,51 +54,49 @@ impl GasTracker {
}
}

pub fn charge_gas(&mut self, charge: GasCharge) -> Result<()> {
self.charge_milligas(
charge.name,
charge.total().saturating_mul(MILLIGAS_PRECISION),
)
/// Applies the specified gas charge, where quantities are supplied in milligas.
pub fn apply_charge(&mut self, charge: GasCharge) -> Result<()> {
self.charge_milligas(charge.name, charge.total())
}

/// Getter for gas available.
pub fn gas_limit(&self) -> i64 {
pub fn gas_limit(&self) -> Gas {
milligas_to_gas(self.milligas_limit, false)
}

/// Getter for milligas available.
pub fn milligas_limit(&self) -> i64 {
pub fn milligas_limit(&self) -> Milligas {
self.milligas_limit
}

/// Getter for gas used.
pub fn gas_used(&self) -> i64 {
pub fn gas_used(&self) -> Gas {
milligas_to_gas(self.milligas_used, true)
}

/// Getter for milligas used.
pub fn milligas_used(&self) -> i64 {
pub fn milligas_used(&self) -> Milligas {
self.milligas_used
}

pub fn gas_available(&self) -> i64 {
pub fn gas_available(&self) -> Gas {
milligas_to_gas(self.milligas_available(), false)
}

pub fn milligas_available(&self) -> i64 {
pub fn milligas_available(&self) -> Milligas {
self.milligas_limit.saturating_sub(self.milligas_used)
}
}

/// Converts the specified gas into equivalent fractional gas units
#[inline]
fn gas_to_milligas(gas: i64) -> i64 {
pub(crate) fn gas_to_milligas(gas: i64) -> i64 {
gas.saturating_mul(MILLIGAS_PRECISION)
}

/// Converts the specified fractional gas units into gas units
#[inline]
fn milligas_to_gas(milligas: i64, round_up: bool) -> i64 {
pub(crate) fn milligas_to_gas(milligas: i64, round_up: bool) -> i64 {
let mut div_result = milligas / MILLIGAS_PRECISION;
if milligas > 0 && round_up && milligas % MILLIGAS_PRECISION != 0 {
div_result = div_result.saturating_add(1);
Expand All @@ -107,13 +111,17 @@ mod tests {
use super::*;

#[test]
fn basic_gas_tracker() {
#[allow(clippy::identity_op)]
fn basic_gas_tracker() -> Result<()> {
let mut t = GasTracker::new(20, 10);
t.charge_gas(GasCharge::new("", 5, 0)).unwrap();
t.apply_charge(GasCharge::new("", 5 * MILLIGAS_PRECISION, 0))?;
assert_eq!(t.gas_used(), 15);
t.charge_gas(GasCharge::new("", 5, 0)).unwrap();
t.apply_charge(GasCharge::new("", 5 * MILLIGAS_PRECISION, 0))?;
assert_eq!(t.gas_used(), 20);
assert!(t.charge_gas(GasCharge::new("", 1, 0)).is_err())
assert!(t
.apply_charge(GasCharge::new("", 1 * MILLIGAS_PRECISION, 0))
.is_err());
Ok(())
}

#[test]
Expand Down
12 changes: 7 additions & 5 deletions fvm/src/gas/outputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ use std::convert::TryFrom;
use fvm_shared::bigint::BigInt;
use fvm_shared::econ::TokenAmount;

use crate::gas::Gas;

#[derive(Clone, Default)]
pub(crate) struct GasOutputs {
pub base_fee_burn: TokenAmount,
Expand All @@ -11,14 +13,14 @@ pub(crate) struct GasOutputs {
pub miner_tip: TokenAmount,
pub refund: TokenAmount,

pub gas_refund: i64,
pub gas_burned: i64,
pub gas_refund: Gas,
pub gas_burned: Gas,
}

impl GasOutputs {
pub fn compute(
gas_used: i64,
gas_limit: i64,
gas_used: Gas,
gas_limit: Gas,
base_fee: &TokenAmount,
fee_cap: &TokenAmount,
gas_premium: &TokenAmount,
Expand Down Expand Up @@ -57,7 +59,7 @@ impl GasOutputs {
}
}

fn compute_gas_overestimation_burn(gas_used: i64, gas_limit: i64) -> (i64, i64) {
fn compute_gas_overestimation_burn(gas_used: Gas, gas_limit: Gas) -> (Gas, Gas) {
const GAS_OVERUSE_NUM: i64 = 11;
const GAS_OVERUSE_DENOM: i64 = 10;

Expand Down
Loading

0 comments on commit 6a3dec1

Please sign in to comment.