From 09aa67e5ede1b1d996908e86970198e74d01238a Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Fri, 3 Mar 2023 11:14:55 +0100 Subject: [PATCH] normalize fuel costs to roughly 1 fuel per executed instruction --- crates/wasmi/src/engine/bytecode/utils.rs | 11 +-- crates/wasmi/src/engine/config.rs | 94 +++++++++++++++---- crates/wasmi/src/engine/executor.rs | 16 ++-- crates/wasmi/src/engine/func_builder/mod.rs | 11 +++ .../src/engine/func_builder/translator.rs | 33 +++++-- crates/wasmi/src/engine/tests.rs | 62 +++++++----- crates/wasmi/src/module/compile/mod.rs | 1 + crates/wasmi/tests/e2e/v1/fuel_metering.rs | 4 +- 8 files changed, 161 insertions(+), 71 deletions(-) diff --git a/crates/wasmi/src/engine/bytecode/utils.rs b/crates/wasmi/src/engine/bytecode/utils.rs index 2e13af300a..13b5e73862 100644 --- a/crates/wasmi/src/engine/bytecode/utils.rs +++ b/crates/wasmi/src/engine/bytecode/utils.rs @@ -1,4 +1,4 @@ -use crate::engine::{config::FuelCosts, Instr}; +use crate::engine::Instr; use core::fmt::Display; /// Defines how many stack values are going to be dropped and kept after branching. @@ -62,15 +62,6 @@ impl DropKeep { pub fn keep(self) -> usize { self.keep as usize } - - /// Returns the fuel consumption required for this [`DropKeep`]. - pub fn fuel_consumption(self, costs: &FuelCosts) -> u64 { - if self.drop == 0 { - // If nothing is dropped no copy is required. - return 0; - } - u64::from(self.keep) * costs.branch_per_kept - } } /// A function index. diff --git a/crates/wasmi/src/engine/config.rs b/crates/wasmi/src/engine/config.rs index 44a4a811c1..0bb076007c 100644 --- a/crates/wasmi/src/engine/config.rs +++ b/crates/wasmi/src/engine/config.rs @@ -1,4 +1,6 @@ -use super::stack::StackLimits; +use super::{stack::StackLimits, DropKeep}; +use core::{mem::size_of, num::NonZeroU64}; +use wasmi_core::UntypedValue; use wasmparser::WasmFeatures; /// The default amount of stacks kept in the cache at most. @@ -54,33 +56,85 @@ pub struct FuelCosts { pub store: u64, /// The fuel cost offset for `call` and `call_indirect` instructions. pub call: u64, - /// The fuel cost offset per local variable for `call` and `call_indirect` instruction. + /// Determines how many moved stack values consume one fuel upon a branch or return instruction. /// /// # Note /// - /// This is also applied to all function parameters since - /// they are translated to local variable slots. - pub call_per_local: u64, - /// The fuel cost offset per kept value for branches that need to copy values on the stack. - pub branch_per_kept: u64, - /// The fuel cost offset per byte for `bulk-memory` instructions. - pub memory_per_byte: u64, - /// The fuel cost offset per element for `bulk-table` instructions. - pub table_per_element: u64, + /// If this is zero then processing [`DropKeep`] costs nothing. + branch_kept_per_fuel: u64, + /// Determines how many function locals consume one fuel per function call. + /// + /// # Note + /// + /// - This is also applied to all function parameters since + /// they are translated to local variable slots. + /// - If this is zero then processing function locals costs nothing. + func_locals_per_fuel: u64, + /// How many memory bytes can be processed per fuel in a `bulk-memory` instruction. + /// + /// # Note + /// + /// If this is zero then processing memory bytes costs nothing. + memory_bytes_per_fuel: u64, + /// How many table elements can be processed per fuel in a `bulk-table` instruction. + /// + /// # Note + /// + /// If this is zero then processing table elements costs nothing. + table_elements_per_fuel: u64, +} + +impl FuelCosts { + /// Returns the fuel consumption of the amount of items with costs per items. + fn costs_per(len_items: u64, items_per_fuel: u64) -> u64 { + NonZeroU64::new(items_per_fuel) + .map(|items_per_fuel| len_items / items_per_fuel) + .unwrap_or(0) + } + + /// Returns the fuel consumption for branches and returns using the given [`DropKeep`]. + pub fn fuel_for_drop_keep(&self, drop_keep: DropKeep) -> u64 { + if drop_keep.drop() == 0 { + return 0; + } + Self::costs_per(drop_keep.keep() as u64, self.branch_kept_per_fuel) + } + + /// Returns the fuel consumption for calling a function with the amount of local variables. + /// + /// # Note + /// + /// Function parameters are also treated as local variables. + pub fn fuel_for_locals(&self, locals: u64) -> u64 { + Self::costs_per(locals, self.func_locals_per_fuel) + } + + /// Returns the fuel consumption for processing the amount of memory bytes. + pub fn fuel_for_bytes(&self, bytes: u64) -> u64 { + Self::costs_per(bytes, self.memory_bytes_per_fuel) + } + + /// Returns the fuel consumption for processing the amount of table elements. + pub fn fuel_for_elements(&self, elements: u64) -> u64 { + Self::costs_per(elements, self.table_elements_per_fuel) + } } impl Default for FuelCosts { fn default() -> Self { + let memory_bytes_per_fuel = 64; + let bytes_per_register = size_of::() as u64; + let registers_per_fuel = memory_bytes_per_fuel / bytes_per_register; Self { - base: 100, - entity: 500, - load: 650, - store: 450, - call: 1500, - call_per_local: 10, - branch_per_kept: 10, - memory_per_byte: 2, - table_per_element: 10, + base: 1, + entity: 1, + load: 1, + store: 1, + call: 1, + func_locals_per_fuel: registers_per_fuel, + branch_kept_per_fuel: registers_per_fuel, + memory_bytes_per_fuel, + table_elements_per_fuel: registers_per_fuel, } } } diff --git a/crates/wasmi/src/engine/executor.rs b/crates/wasmi/src/engine/executor.rs index 6d7b539932..dcb03fae0e 100644 --- a/crates/wasmi/src/engine/executor.rs +++ b/crates/wasmi/src/engine/executor.rs @@ -895,7 +895,7 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> { let result = self.consume_fuel_on_success( |costs| { let delta_in_bytes = delta.to_bytes().unwrap_or(0) as u64; - delta_in_bytes * costs.memory_per_byte + costs.fuel_for_bytes(delta_in_bytes) }, |this| { let memory = this.cache.default_memory(this.ctx); @@ -929,7 +929,7 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> { let offset = i32::from(d) as usize; let byte = u8::from(val); self.consume_fuel_on_success( - |costs| n as u64 * costs.memory_per_byte, + |costs| costs.fuel_for_bytes(n as u64), |this| { let memory = this .cache @@ -952,7 +952,7 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> { let src_offset = i32::from(s) as usize; let dst_offset = i32::from(d) as usize; self.consume_fuel_on_success( - |costs| n as u64 * costs.memory_per_byte, + |costs| costs.fuel_for_bytes(n as u64), |this| { let data = this.cache.default_memory_bytes(this.ctx); // These accesses just perform the bounds checks required by the Wasm spec. @@ -977,7 +977,7 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> { let src_offset = i32::from(s) as usize; let dst_offset = i32::from(d) as usize; self.consume_fuel_on_success( - |costs| n as u64 * costs.memory_per_byte, + |costs| costs.fuel_for_bytes(n as u64), |this| { let (memory, data) = this .cache @@ -1019,7 +1019,7 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> { let (init, delta) = self.sp.pop2(); let delta: u32 = delta.into(); let result = self.consume_fuel_on_success( - |costs| u64::from(delta) * costs.table_per_element, + |costs| costs.fuel_for_elements(u64::from(delta)), |this| { let table = this.cache.get_table(this.ctx, table_index); this.ctx @@ -1044,7 +1044,7 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> { let dst: u32 = i.into(); let len: u32 = n.into(); self.consume_fuel_on_success( - |costs| u64::from(len) * costs.table_per_element, + |costs| costs.fuel_for_elements(u64::from(len)), |this| { let table = this.cache.get_table(this.ctx, table_index); this.ctx @@ -1089,7 +1089,7 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> { let src_index = u32::from(s); let dst_index = u32::from(d); self.consume_fuel_on_success( - |costs| u64::from(len) * costs.table_per_element, + |costs| costs.fuel_for_elements(u64::from(len)), |this| { // Query both tables and check if they are the same: let dst = this.cache.get_table(this.ctx, dst); @@ -1121,7 +1121,7 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> { let src_index = u32::from(s); let dst_index = u32::from(d); self.consume_fuel_on_success( - |costs| u64::from(len) * costs.table_per_element, + |costs| costs.fuel_for_elements(u64::from(len)), |this| { let (instance, table, element) = this .cache diff --git a/crates/wasmi/src/engine/func_builder/mod.rs b/crates/wasmi/src/engine/func_builder/mod.rs index 1c161ee43c..0b52a6da05 100644 --- a/crates/wasmi/src/engine/func_builder/mod.rs +++ b/crates/wasmi/src/engine/func_builder/mod.rs @@ -65,6 +65,17 @@ impl<'parser> FuncBuilder<'parser> { Ok(()) } + /// This informs the [`FuncBuilder`] that the function header translation is finished. + /// + /// # Note + /// + /// This was introduced to properly calculate the fuel costs for all local variables + /// and function parameters. After this function call no more locals and parameters may + /// be added to this function translation. + pub fn finish_translate_locals(&mut self) { + self.translator.finish_translate_locals() + } + /// Updates the current position within the Wasm binary while parsing operators. pub fn update_pos(&mut self, pos: usize) { self.pos = pos; diff --git a/crates/wasmi/src/engine/func_builder/translator.rs b/crates/wasmi/src/engine/func_builder/translator.rs index 9a52d8f5a3..62b7d01344 100644 --- a/crates/wasmi/src/engine/func_builder/translator.rs +++ b/crates/wasmi/src/engine/func_builder/translator.rs @@ -153,7 +153,6 @@ impl<'parser> FuncTranslator<'parser> { /// Registers the function parameters in the emulated value stack. fn init_func_params(&mut self) { for _param_type in self.func_type().params() { - self.bump_fuel_consumption(self.fuel_costs().call_per_local); self.locals.register_locals(1); } } @@ -164,11 +163,23 @@ impl<'parser> FuncTranslator<'parser> { /// /// If too many local variables have been registered. pub fn register_locals(&mut self, amount: u32) { - let fuel_costs = u64::from(amount) * self.fuel_costs().call_per_local; - self.bump_fuel_consumption(fuel_costs); self.locals.register_locals(amount); } + /// This informs the [`FuncTranslator`] that the function header translation is finished. + /// + /// # Note + /// + /// This was introduced to properly calculate the fuel costs for all local variables + /// and function parameters. After this function call no more locals and parameters may + /// be added to this function translation. + pub fn finish_translate_locals(&mut self) { + self.bump_fuel_consumption( + self.fuel_costs() + .fuel_for_locals(u64::from(self.locals.len_registered())), + ) + } + /// Finishes constructing the function and returns its [`FuncBody`]. pub fn finish(&mut self) -> Result { let func_body = self.alloc.inst_builder.finish( @@ -996,7 +1007,8 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> { match builder.acquire_target(relative_depth)? { AcquiredTarget::Branch(end_label, drop_keep) => { builder.bump_fuel_consumption(builder.fuel_costs().base); - builder.bump_fuel_consumption(drop_keep.fuel_consumption(builder.fuel_costs())); + builder + .bump_fuel_consumption(builder.fuel_costs().fuel_for_drop_keep(drop_keep)); let params = builder.branch_params(end_label, drop_keep); builder .alloc @@ -1019,7 +1031,8 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> { match builder.acquire_target(relative_depth)? { AcquiredTarget::Branch(end_label, drop_keep) => { builder.bump_fuel_consumption(builder.fuel_costs().base); - builder.bump_fuel_consumption(drop_keep.fuel_consumption(builder.fuel_costs())); + builder + .bump_fuel_consumption(builder.fuel_costs().fuel_for_drop_keep(drop_keep)); let params = builder.branch_params(end_label, drop_keep); builder .alloc @@ -1052,7 +1065,7 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> { match builder.acquire_target(depth.into_u32())? { AcquiredTarget::Branch(label, drop_keep) => { *max_drop_keep_fuel = (*max_drop_keep_fuel) - .max(drop_keep.fuel_consumption(builder.fuel_costs())); + .max(builder.fuel_costs().fuel_for_drop_keep(drop_keep)); let base = builder.alloc.inst_builder.current_pc(); let instr = offset_instr(base, n + 1); let offset = builder @@ -1065,7 +1078,7 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> { } AcquiredTarget::Return(drop_keep) => { *max_drop_keep_fuel = (*max_drop_keep_fuel) - .max(drop_keep.fuel_consumption(builder.fuel_costs())); + .max(builder.fuel_costs().fuel_for_drop_keep(drop_keep)); Ok(Instruction::Return(drop_keep)) } } @@ -1118,7 +1131,7 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> { self.translate_if_reachable(|builder| { let drop_keep = builder.drop_keep_return()?; builder.bump_fuel_consumption(builder.fuel_costs().base); - builder.bump_fuel_consumption(drop_keep.fuel_consumption(builder.fuel_costs())); + builder.bump_fuel_consumption(builder.fuel_costs().fuel_for_drop_keep(drop_keep)); builder .alloc .inst_builder @@ -1134,7 +1147,7 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> { let func_type = builder.func_type_of(func_idx.into()); let drop_keep = builder.drop_keep_return_call(&func_type)?; builder.bump_fuel_consumption(builder.fuel_costs().call); - builder.bump_fuel_consumption(drop_keep.fuel_consumption(builder.fuel_costs())); + builder.bump_fuel_consumption(builder.fuel_costs().fuel_for_drop_keep(drop_keep)); builder .alloc .inst_builder @@ -1156,7 +1169,7 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> { builder.stack_height.pop1(); let drop_keep = builder.drop_keep_return_call(&func_type)?; builder.bump_fuel_consumption(builder.fuel_costs().call); - builder.bump_fuel_consumption(drop_keep.fuel_consumption(builder.fuel_costs())); + builder.bump_fuel_consumption(builder.fuel_costs().fuel_for_drop_keep(drop_keep)); builder .alloc .inst_builder diff --git a/crates/wasmi/src/engine/tests.rs b/crates/wasmi/src/engine/tests.rs index bbc6f3dc1e..9a6d3acd34 100644 --- a/crates/wasmi/src/engine/tests.rs +++ b/crates/wasmi/src/engine/tests.rs @@ -781,7 +781,8 @@ fn metered_simple_01() { "#, ); let costs = fuel_costs(); - let expected_fuel = 3 * costs.base + costs.call_per_local + costs.branch_per_kept; + let expected_fuel = + 3 * costs.base + costs.fuel_for_locals(1) + costs.fuel_for_drop_keep(drop_keep(1, 1)); let expected = [ Instruction::consume_fuel(expected_fuel), Instruction::local_get(1), @@ -806,7 +807,8 @@ fn metered_simple_02() { "#, ); let costs = fuel_costs(); - let expected_fuel = 5 * costs.base + costs.call_per_local + costs.branch_per_kept; + let expected_fuel = + 5 * costs.base + costs.fuel_for_locals(1) + costs.fuel_for_drop_keep(drop_keep(1, 1)); let expected = [ Instruction::consume_fuel(expected_fuel), Instruction::local_get(1), @@ -835,7 +837,8 @@ fn metered_simple_03() { "#, ); let costs = fuel_costs(); - let expected_fuel = 9 * costs.base + 2 * costs.call_per_local + costs.branch_per_kept; + let expected_fuel = + 9 * costs.base + costs.fuel_for_locals(2) + costs.fuel_for_drop_keep(drop_keep(2, 1)); let expected = [ Instruction::consume_fuel(expected_fuel), Instruction::local_get(2), @@ -869,8 +872,9 @@ fn metered_if_01() { "#, ); let costs = fuel_costs(); - let expected_fuel_fn = 4 * costs.base + 3 * costs.call_per_local + costs.branch_per_kept; - let expected_fuel_then = 3 * costs.base + costs.branch_per_kept; + let expected_fuel_fn = + 4 * costs.base + costs.fuel_for_locals(3) + costs.fuel_for_drop_keep(drop_keep(3, 1)); + let expected_fuel_then = 3 * costs.base + costs.fuel_for_drop_keep(drop_keep(3, 1)); let expected_fuel_else = expected_fuel_then; let expected = [ /* 0 */ Instruction::consume_fuel(expected_fuel_fn), // function body @@ -910,8 +914,9 @@ fn metered_block_in_if_01() { "#, ); let costs = fuel_costs(); - let expected_fuel_fn = 5 * costs.base + 3 * costs.call_per_local + costs.branch_per_kept; - let expected_fuel_then = 3 * costs.base + costs.branch_per_kept; + let expected_fuel_fn = + 5 * costs.base + costs.fuel_for_locals(3) + costs.fuel_for_drop_keep(drop_keep(3, 1)); + let expected_fuel_then = 3 * costs.base + costs.fuel_for_drop_keep(drop_keep(3, 1)); let expected_fuel_else = expected_fuel_then; #[rustfmt::skip] let expected = [ @@ -956,7 +961,8 @@ fn metered_block_in_if_02() { "#, ); let costs = fuel_costs(); - let expected_fuel_fn = 5 * costs.base + 3 * costs.call_per_local + costs.branch_per_kept; + let expected_fuel_fn = + 5 * costs.base + costs.fuel_for_locals(3) + costs.fuel_for_drop_keep(drop_keep(3, 1)); let expected_fuel_then = 2 * costs.base; let expected_fuel_else = expected_fuel_then; let expected = [ @@ -996,7 +1002,8 @@ fn metered_loop_in_if() { "#, ); let costs = fuel_costs(); - let expected_fuel_fn = 5 * costs.base + 3 * costs.call_per_local + costs.branch_per_kept; + let expected_fuel_fn = + 5 * costs.base + costs.fuel_for_locals(3) + costs.fuel_for_drop_keep(drop_keep(3, 1)); let expected_fuel_then = costs.base; let expected_fuel_else = expected_fuel_then; let expected_fuel_loop = 2 * costs.base; @@ -1044,7 +1051,8 @@ fn metered_nested_blocks() { "#, ); let costs = fuel_costs(); - let expected_fuel = 11 * costs.base + costs.call_per_local + costs.branch_per_kept; + let expected_fuel = + 11 * costs.base + costs.fuel_for_locals(1) + costs.fuel_for_drop_keep(drop_keep(1, 1)); let expected = [ Instruction::consume_fuel(expected_fuel), Instruction::local_get(1), @@ -1089,7 +1097,8 @@ fn metered_nested_loops() { "#, ); let costs = fuel_costs(); - let expected_fuel_outer = 3 * costs.base + costs.call_per_local + costs.branch_per_kept; + let expected_fuel_outer = + 3 * costs.base + costs.fuel_for_locals(1) + costs.fuel_for_drop_keep(drop_keep(1, 1)); let expected_fuel_inner = 3 * costs.base; let expected = [ Instruction::consume_fuel(expected_fuel_outer), @@ -1131,8 +1140,10 @@ fn metered_global_bump() { "#, ); let costs = fuel_costs(); - let expected_fuel = - 3 * costs.entity + 4 * costs.base + costs.call_per_local + costs.branch_per_kept; + let expected_fuel = 3 * costs.entity + + 4 * costs.base + + costs.fuel_for_locals(1) + + costs.fuel_for_drop_keep(drop_keep(1, 1)); let expected = [ Instruction::consume_fuel(expected_fuel), Instruction::GlobalGet(GlobalIdx::from(0)), @@ -1196,7 +1207,8 @@ fn metered_calls_02() { "#, ); let costs = fuel_costs(); - let expected_fuel_f0 = 5 * costs.base + 2 * costs.call_per_local + costs.branch_per_kept; + let expected_fuel_f0 = + 5 * costs.base + costs.fuel_for_locals(2) + costs.fuel_for_drop_keep(drop_keep(2, 1)); let expected_f0 = [ Instruction::consume_fuel(expected_fuel_f0), Instruction::local_get(2), @@ -1204,8 +1216,10 @@ fn metered_calls_02() { Instruction::I32Add, Instruction::Return(drop_keep(2, 1)), ]; - let expected_fuel_f1 = - 4 * costs.base + costs.call + 2 * costs.call_per_local + costs.branch_per_kept; + let expected_fuel_f1 = 4 * costs.base + + costs.call + + costs.fuel_for_locals(2) + + costs.fuel_for_drop_keep(drop_keep(2, 1)); let expected_f1 = [ Instruction::consume_fuel(expected_fuel_f1), Instruction::local_get(2), @@ -1238,7 +1252,8 @@ fn metered_calls_03() { "#, ); let costs = fuel_costs(); - let expected_fuel_f0 = 7 * costs.base + 2 * costs.call_per_local + costs.branch_per_kept; + let expected_fuel_f0 = + 7 * costs.base + costs.fuel_for_locals(2) + costs.fuel_for_drop_keep(drop_keep(2, 1)); let expected_f0 = [ Instruction::consume_fuel(expected_fuel_f0), Instruction::local_get(2), @@ -1248,8 +1263,10 @@ fn metered_calls_03() { Instruction::I32Add, Instruction::Return(drop_keep(2, 1)), ]; - let expected_fuel_f1 = - 3 * costs.base + costs.call + costs.call_per_local + costs.branch_per_kept; + let expected_fuel_f1 = 3 * costs.base + + costs.call + + costs.fuel_for_locals(1) + + costs.fuel_for_drop_keep(drop_keep(1, 1)); let expected_f1 = [ Instruction::consume_fuel(expected_fuel_f1), Instruction::local_get(1), @@ -1275,7 +1292,10 @@ fn metered_load_01() { "#, ); let costs = fuel_costs(); - let expected_fuel = 3 * costs.base + costs.load + costs.call_per_local + costs.branch_per_kept; + let expected_fuel = 3 * costs.base + + costs.load + + costs.fuel_for_locals(1) + + costs.fuel_for_drop_keep(drop_keep(1, 1)); let expected = [ Instruction::consume_fuel(expected_fuel), Instruction::local_get(1), @@ -1300,7 +1320,7 @@ fn metered_store_01() { "#, ); let costs = fuel_costs(); - let expected_fuel = 4 * costs.base + costs.store + 2 * costs.call_per_local; + let expected_fuel = 4 * costs.base + costs.store + costs.fuel_for_locals(2); let expected = [ Instruction::consume_fuel(expected_fuel), Instruction::local_get(2), diff --git a/crates/wasmi/src/module/compile/mod.rs b/crates/wasmi/src/module/compile/mod.rs index a5f89ff89a..052a085746 100644 --- a/crates/wasmi/src/module/compile/mod.rs +++ b/crates/wasmi/src/module/compile/mod.rs @@ -78,6 +78,7 @@ impl<'parser> FunctionTranslator<'parser> { self.func_builder .translate_locals(offset, amount, value_type)?; } + self.func_builder.finish_translate_locals(); Ok(()) } diff --git a/crates/wasmi/tests/e2e/v1/fuel_metering.rs b/crates/wasmi/tests/e2e/v1/fuel_metering.rs index c8aa768494..9f795f4f1a 100644 --- a/crates/wasmi/tests/e2e/v1/fuel_metering.rs +++ b/crates/wasmi/tests/e2e/v1/fuel_metering.rs @@ -89,7 +89,7 @@ fn metered_i32_add() { assert_out_of_fuel(func.call(&mut store, (1, 2))); assert_eq!(store.fuel_consumed(), Some(0)); // Now add enough fuel, so execution should succeed. - store.add_fuel(10_000).unwrap(); + store.add_fuel(10).unwrap(); assert_success(func.call(&mut store, (1, 2))); - assert_eq!(store.fuel_consumed(), Some(530)); + assert_eq!(store.fuel_consumed(), Some(5)); }