Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Light and Heavy operation gas costs #622

Merged
merged 6 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading