Skip to content

Commit

Permalink
feat: Light and Heavy operation gas costs (#622)
Browse files Browse the repository at this point in the history
* Light and Heavy gas costs

* Update

* Changelog

* Update fuel-tx/src/transaction/consensus_parameters/gas.rs

* Update fuel-tx/src/transaction/consensus_parameters/gas.rs

Co-authored-by: Green Baneling <XgreenX9999@gmail.com>

---------

Co-authored-by: Green Baneling <XgreenX9999@gmail.com>
  • Loading branch information
Brandon Vrooman and xgreenx authored Nov 8, 2023
1 parent 09cc333 commit c419c9b
Show file tree
Hide file tree
Showing 11 changed files with 185 additions and 100 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).

#### Breaking

- [#622](https://github.com/FuelLabs/fuel-vm/pull/622): Divide `DependentCost` into "light" and "heavy" operations: Light operations consume `0 < x < 1` gas per unit, while heavy operations consume `x` gas per unit. This distinction provides more precision when calculating dependent costs.
- [#618](https://github.com/FuelLabs/fuel-vm/pull/618): Transaction fees for `Create` now include the cost of metadata calculations, including: contract root calculation, state root calculation, and contract id calculation.
- [#613](https://github.com/FuelLabs/fuel-vm/pull/613): Transaction fees now include the cost of signature verification for each input. For signed inputs, the cost of an EC recovery is charged. For predicate inputs, the cost of a BMT root of bytecode is charged.
- [#607](https://github.com/FuelLabs/fuel-vm/pull/607): The `Interpreter` expects the third generic argument during type definition that specifies the implementer of the `EcalHandler` trait for `ecal` opcode.
Expand Down
132 changes: 116 additions & 16 deletions fuel-tx/src/transaction/consensus_parameters/gas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,18 +324,29 @@ pub struct GasCostsValues {
}

/// Dependent cost is a cost that depends on the number of units.
/// The cost starts at the base and grows by `dep_per_unit` for every unit.
///
/// For example, if the base is 10 and the `dep_per_unit` is 2,
/// then the cost for 0 units is 10, 1 unit is 12, 2 units is 14, etc.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DependentCost {
/// The minimum that this operation can cost.
pub base: Word,
/// The amount that this operation costs per
/// increase in unit.
pub dep_per_unit: Word,
pub enum DependentCost {
/// When an operation is dependent on the magnitude of its inputs, and the
/// time per unit of input is less than a single no-op operation
LightOperation {
/// The minimum that this operation can cost.
base: Word,
/// How many elements can be processed with a single gas. The
/// higher the `units_per_gas`, the less additional cost you will incur
/// for a given number of units, because you need more units to increase
/// the total cost.
units_per_gas: Word,
},

/// When an operation is dependent on the magnitude of its inputs, and the
/// time per unit of input is greater than a single no-op operation
HeavyOperation {
/// The minimum that this operation can cost.
base: Word,
/// How much gas is required to process a single unit.
gas_per_unit: Word,
},
}

#[cfg(feature = "alloc")]
Expand Down Expand Up @@ -584,22 +595,66 @@ impl GasCostsValues {
impl DependentCost {
/// Create costs that are all set to zero.
pub fn free() -> Self {
Self {
Self::HeavyOperation {
base: 0,
dep_per_unit: 0,
gas_per_unit: 0,
}
}

/// Create costs that are all set to one.
pub fn unit() -> Self {
Self {
Self::HeavyOperation {
base: 1,
dep_per_unit: 0,
gas_per_unit: 0,
}
}

pub fn from_units_per_gas(base: Word, units_per_gas: Word) -> Self {
debug_assert!(
units_per_gas > 0,
"Cannot create dependent gas cost with per-0-gas ratio"
);
DependentCost::LightOperation {
base,
units_per_gas,
}
}

pub fn from_gas_per_unit(base: Word, gas_per_unit: Word) -> Self {
DependentCost::HeavyOperation { base, gas_per_unit }
}

pub fn base(&self) -> Word {
match self {
DependentCost::LightOperation { base, .. } => *base,
DependentCost::HeavyOperation { base, .. } => *base,
}
}

pub fn set_base(&mut self, value: Word) {
match self {
DependentCost::LightOperation { base, .. } => *base = value,
DependentCost::HeavyOperation { base, .. } => *base = value,
};
}

pub fn resolve(&self, units: Word) -> Word {
self.base + units.saturating_div(self.dep_per_unit)
let base = self.base();
let dependent_value = match self {
DependentCost::LightOperation { units_per_gas, .. } => {
// Apply the linear transformation f(x) = 1/m * x = x/m = where:
// x is the number of units
// 1/m is the gas_per_unit
units.saturating_div(*units_per_gas)
}
DependentCost::HeavyOperation { gas_per_unit, .. } => {
// Apply the linear transformation f(x) = mx, where:
// x is the number of units
// m is the gas per unit
units.saturating_mul(*gas_per_unit)
}
};
base + dependent_value
}
}

Expand All @@ -623,3 +678,48 @@ impl From<GasCosts> for GasCostsValues {
(*i.0).clone()
}
}

#[cfg(test)]
mod tests {
use crate::DependentCost;

#[test]
fn light_operation_gas_cost_resolves_correctly() {
// Create a linear gas cost function with a slope of 1/10
let cost = DependentCost::from_units_per_gas(0, 10);
let total = cost.resolve(0);
assert_eq!(total, 0);

let total = cost.resolve(5);
assert_eq!(total, 0);

let total = cost.resolve(10);
assert_eq!(total, 1);

let total = cost.resolve(100);
assert_eq!(total, 10);

let total = cost.resolve(721);
assert_eq!(total, 72);
}

#[test]
fn heavy_operation_gas_cost_resolves_correctly() {
// Create a linear gas cost function with a slope of 10
let cost = DependentCost::from_gas_per_unit(0, 10);
let total = cost.resolve(0);
assert_eq!(total, 0);

let total = cost.resolve(5);
assert_eq!(total, 50);

let total = cost.resolve(10);
assert_eq!(total, 100);

let total = cost.resolve(100);
assert_eq!(total, 1_000);

let total = cost.resolve(721);
assert_eq!(total, 7_210);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,82 +92,82 @@ pub fn default_gas_costs() -> GasCostsValues {
wqmm: 3,
xor: 1,
xori: 1,
k256: DependentCost {
k256: DependentCost::LightOperation {
base: 11,
dep_per_unit: 214,
units_per_gas: 214,
},
s256: DependentCost {
s256: DependentCost::LightOperation {
base: 2,
dep_per_unit: 214,
units_per_gas: 214,
},
call: DependentCost {
call: DependentCost::LightOperation {
base: 144,
dep_per_unit: 214,
units_per_gas: 214,
},
ccp: DependentCost {
ccp: DependentCost::LightOperation {
base: 15,
dep_per_unit: 103,
units_per_gas: 103,
},
csiz: DependentCost {
csiz: DependentCost::LightOperation {
base: 17,
dep_per_unit: 790,
units_per_gas: 790,
},
ldc: DependentCost {
ldc: DependentCost::LightOperation {
base: 15,
dep_per_unit: 272,
units_per_gas: 272,
},
logd: DependentCost {
logd: DependentCost::LightOperation {
base: 26,
dep_per_unit: 64,
units_per_gas: 64,
},
mcl: DependentCost {
mcl: DependentCost::LightOperation {
base: 1,
dep_per_unit: 3333,
units_per_gas: 3333,
},
mcli: DependentCost {
mcli: DependentCost::LightOperation {
base: 1,
dep_per_unit: 3333,
units_per_gas: 3333,
},
mcp: DependentCost {
mcp: DependentCost::LightOperation {
base: 1,
dep_per_unit: 2000,
units_per_gas: 2000,
},
mcpi: DependentCost {
mcpi: DependentCost::LightOperation {
base: 3,
dep_per_unit: 2000,
units_per_gas: 2000,
},
meq: DependentCost {
meq: DependentCost::LightOperation {
base: 1,
dep_per_unit: 2500,
units_per_gas: 2500,
},
rvrt: 13,
smo: DependentCost {
smo: DependentCost::LightOperation {
base: 209,
dep_per_unit: 55,
units_per_gas: 55,
},
retd: DependentCost {
retd: DependentCost::LightOperation {
base: 29,
dep_per_unit: 62,
units_per_gas: 62,
},
srwq: DependentCost {
srwq: DependentCost::LightOperation {
base: 47,
dep_per_unit: 5,
units_per_gas: 5,
},
scwq: DependentCost {
scwq: DependentCost::LightOperation {
base: 13,
dep_per_unit: 5,
units_per_gas: 5,
},
swwq: DependentCost {
swwq: DependentCost::LightOperation {
base: 44,
dep_per_unit: 5,
units_per_gas: 5,
},
contract_root: DependentCost {
contract_root: DependentCost::LightOperation {
base: 75,
dep_per_unit: 1,
units_per_gas: 1,
},
state_root: DependentCost {
state_root: DependentCost::LightOperation {
base: 412,
dep_per_unit: 1,
units_per_gas: 1,
},
}
}
8 changes: 4 additions & 4 deletions fuel-vm/src/interpreter/blockchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ where
let mut gas_cost = self.gas_costs().ldc;
// Charge only for the `base` execution.
// We will charge for the contracts size in the `load_contract_code`.
self.gas_charge(gas_cost.base)?;
gas_cost.base = 0;
self.gas_charge(gas_cost.base())?;
gas_cost.set_base(0);
let contract_max_size = self.contract_max_size();
let current_contract =
current_contract(&self.context, self.registers.fp(), self.memory.as_ref())?
Expand Down Expand Up @@ -262,8 +262,8 @@ where
let mut gas_cost = self.gas_costs().csiz;
// Charge only for the `base` execution.
// We will charge for the contracts size in the `code_size`.
self.gas_charge(gas_cost.base)?;
gas_cost.base = 0;
self.gas_charge(gas_cost.base())?;
gas_cost.set_base(0);
let current_contract =
current_contract(&self.context, self.registers.fp(), self.memory.as_ref())?
.copied();
Expand Down
5 changes: 1 addition & 4 deletions fuel-vm/src/interpreter/blockchain/code_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,7 @@ fn test_load_contract() -> IoResult<(), Infallible> {
profiler: &mut Profiler::default(),
input_contracts: InputContracts::new(input_contracts.iter(), &mut panic_context),
current_contract: None,
gas_cost: DependentCost {
base: 13,
dep_per_unit: 1,
},
gas_cost: DependentCost::from_units_per_gas(13, 1),
cgas: RegMut::new(&mut cgas),
ggas: RegMut::new(&mut ggas),
ssp: RegMut::new(&mut ssp),
Expand Down
15 changes: 3 additions & 12 deletions fuel-vm/src/interpreter/blockchain/other_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,10 +322,7 @@ fn test_code_size() {
let input = CodeSizeCtx {
storage: &mut storage,
memory: &mut memory,
gas_cost: DependentCost {
base: 0,
dep_per_unit: 0,
},
gas_cost: DependentCost::free(),
profiler: &mut Profiler::default(),
input_contracts: InputContracts::new(input_contract.iter(), &mut panic_context),
current_contract: None,
Expand All @@ -343,10 +340,7 @@ fn test_code_size() {
let input = CodeSizeCtx {
storage: &mut storage,
memory: &mut memory,
gas_cost: DependentCost {
base: 0,
dep_per_unit: 0,
},
gas_cost: DependentCost::free(),
input_contracts: InputContracts::new(input_contract.iter(), &mut panic_context),
profiler: &mut Profiler::default(),
current_contract: None,
Expand All @@ -363,10 +357,7 @@ fn test_code_size() {
let input = CodeSizeCtx {
storage: &mut storage,
memory: &mut memory,
gas_cost: DependentCost {
base: 0,
dep_per_unit: 0,
},
gas_cost: DependentCost::free(),
input_contracts: InputContracts::new(iter::empty(), &mut panic_context),
profiler: &mut Profiler::default(),
current_contract: None,
Expand Down
4 changes: 2 additions & 2 deletions fuel-vm/src/interpreter/flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,8 +390,8 @@ where
let mut gas_cost = self.gas_costs().call;
// Charge only for the `base` execution.
// We will charge for the frame size in the `prepare_call`.
self.gas_charge(gas_cost.base)?;
gas_cost.base = 0;
self.gas_charge(gas_cost.base())?;
gas_cost.set_base(0);
let current_contract =
current_contract(&self.context, self.registers.fp(), self.memory.as_ref())?
.copied();
Expand Down
5 changes: 1 addition & 4 deletions fuel-vm/src/interpreter/flow/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,7 @@ impl Default for Input {
input_contracts: vec![Default::default()],
storage_balance: Default::default(),
memory: vec![0u8; MEM_SIZE].try_into().unwrap(),
gas_cost: DependentCost {
base: 10,
dep_per_unit: 10,
},
gas_cost: DependentCost::from_units_per_gas(10, 10),
storage_contract: vec![(ContractId::default(), vec![0u8; 10])],
script: None,
}
Expand Down
Loading

0 comments on commit c419c9b

Please sign in to comment.