diff --git a/crates/fuel-gas-price-algorithm/gas-price-analysis/src/charts.rs b/crates/fuel-gas-price-algorithm/gas-price-analysis/src/charts.rs index 25d9ad8b2a1..6e5ac008f05 100644 --- a/crates/fuel-gas-price-algorithm/gas-price-analysis/src/charts.rs +++ b/crates/fuel-gas-price-algorithm/gas-price-analysis/src/charts.rs @@ -4,6 +4,8 @@ use std::{ path::PathBuf, }; +const ONE_GWEI: u64 = 1_000_000_000; + pub fn draw_chart( results: SimulationResults, p_comp: i64, @@ -76,11 +78,16 @@ pub fn draw_gas_prices( da_gas_prices: &[u64], title: &str, ) -> anyhow::Result<()> { + let gas_prices_gwei: Vec<_> = gas_prices.into_iter().map(|x| x / ONE_GWEI).collect(); + let _exec_gas_prices_gwei: Vec<_> = + _exec_gas_prices.into_iter().map(|x| x / ONE_GWEI).collect(); + let da_gas_prices_gwei: Vec<_> = + da_gas_prices.into_iter().map(|x| x / ONE_GWEI).collect(); // const GAS_PRICE_COLOR: RGBColor = BLACK; // const EXEC_GAS_PRICE_COLOR: RGBColor = RED; const DA_GAS_PRICE_COLOR: RGBColor = BLUE; let min = 0; - let max = *da_gas_prices.iter().max().unwrap(); + let max = *da_gas_prices_gwei.iter().max().unwrap(); let mut chart = ChartBuilder::on(drawing_area) .caption(title, ("sans-serif", 50).into_font()) @@ -88,7 +95,7 @@ pub fn draw_gas_prices( .x_label_area_size(40) .y_label_area_size(100) .right_y_label_area_size(100) - .build_cartesian_2d(0..gas_prices.len(), min..max)?; + .build_cartesian_2d(0..gas_prices_gwei.len(), min..max)?; chart .configure_mesh() @@ -120,7 +127,7 @@ pub fn draw_gas_prices( // Draw the da gas prices chart .draw_series(LineSeries::new( - da_gas_prices.iter().enumerate().map(|(x, y)| (x, *y)), + da_gas_prices_gwei.iter().enumerate().map(|(x, y)| (x, *y)), DA_GAS_PRICE_COLOR, ))? .label("DA Gas Price") @@ -189,10 +196,11 @@ pub fn draw_bytes_and_cost_per_block( const BYTES_PER_BLOCK_COLOR: RGBColor = BLACK; let (bytes, costs): (Vec, Vec) = bytes_and_costs_per_block.iter().cloned().unzip(); + let costs_gwei: Vec<_> = costs.into_iter().map(|x| x / ONE_GWEI).collect(); let min = 0; let max_left = *bytes.iter().max().unwrap(); - let max_right = *costs.iter().max().unwrap(); + let max_right = *costs_gwei.iter().max().unwrap(); let mut chart = ChartBuilder::on(drawing_area) .caption(title, ("sans-serif", 50).into_font()) @@ -230,7 +238,7 @@ pub fn draw_bytes_and_cost_per_block( chart .draw_secondary_series(LineSeries::new( - costs.iter().enumerate().map(|(x, y)| (x, *y)), + costs_gwei.iter().enumerate().map(|(x, y)| (x, *y)), RED, )) .unwrap() @@ -257,22 +265,35 @@ pub fn draw_profit( const ACTUAL_PROFIT_COLOR: RGBColor = BLACK; const PROJECTED_PROFIT_COLOR: RGBColor = RED; const PESSIMISTIC_BLOCK_COST_COLOR: RGBColor = BLUE; + let actual_profit_gwei: Vec<_> = actual_profit + .into_iter() + .map(|x| x / ONE_GWEI as i128) + .collect(); + let projected_profit_gwei: Vec<_> = projected_profit + .into_iter() + .map(|x| x / ONE_GWEI as i128) + .collect(); + let pessimistic_block_costs_gwei: Vec<_> = pessimistic_block_costs + .into_iter() + .map(|x| x / ONE_GWEI as u128) + .collect(); let min = *std::cmp::min( - actual_profit + actual_profit_gwei .iter() .min() .ok_or(anyhow::anyhow!("Path has no parent"))?, - projected_profit + projected_profit_gwei .iter() .min() .ok_or(anyhow::anyhow!("Path has no parent"))?, ); + let max = *std::cmp::max( - actual_profit + actual_profit_gwei .iter() .max() .ok_or(anyhow::anyhow!("Path has no parent"))?, - projected_profit + projected_profit_gwei .iter() .max() .ok_or(anyhow::anyhow!("Path has no parent"))?, @@ -284,27 +305,27 @@ pub fn draw_profit( .x_label_area_size(40) .y_label_area_size(100) .right_y_label_area_size(100) - .build_cartesian_2d(0..actual_profit.len(), min..max) + .build_cartesian_2d(0..actual_profit_gwei.len(), min..max) .unwrap() .set_secondary_coord( - 0..actual_profit.len(), - 0..*pessimistic_block_costs.iter().max().unwrap(), + 0..actual_profit_gwei.len(), + 0..*pessimistic_block_costs_gwei.iter().max().unwrap(), ); chart .configure_mesh() - .y_desc("Profit") + .y_desc("Profit (Gwei)") .x_desc("Block") .draw()?; chart .configure_secondary_axes() - .y_desc("Pessimistic cost") + .y_desc("Pessimistic cost (Gwei)") .draw()?; chart .draw_series(LineSeries::new( - actual_profit.iter().enumerate().map(|(x, y)| (x, *y)), + actual_profit_gwei.iter().enumerate().map(|(x, y)| (x, *y)), ACTUAL_PROFIT_COLOR, ))? .label("Actual Profit") @@ -314,7 +335,10 @@ pub fn draw_profit( chart .draw_series(LineSeries::new( - projected_profit.iter().enumerate().map(|(x, y)| (x, *y)), + projected_profit_gwei + .iter() + .enumerate() + .map(|(x, y)| (x, *y)), PROJECTED_PROFIT_COLOR, ))? .label("Projected Profit") @@ -325,7 +349,7 @@ pub fn draw_profit( // draw the block bytes chart .draw_secondary_series(LineSeries::new( - pessimistic_block_costs + pessimistic_block_costs_gwei .iter() .enumerate() .map(|(x, y)| (x, *y)), diff --git a/crates/fuel-gas-price-algorithm/gas-price-analysis/src/simulation.rs b/crates/fuel-gas-price-algorithm/gas-price-analysis/src/simulation.rs index a2aae998d61..2cf4c97c4c1 100644 --- a/crates/fuel-gas-price-algorithm/gas-price-analysis/src/simulation.rs +++ b/crates/fuel-gas-price-algorithm/gas-price-analysis/src/simulation.rs @@ -117,13 +117,14 @@ impl Simulator { da_gas_prices.push(updater.new_scaled_da_gas_price); let gas_price = updater.algorithm().calculate(); gas_prices.push(gas_price); + let total_fee = gas_price * fullness; updater .update_l2_block_data( height, fullness, capacity.try_into().unwrap(), bytes, - gas_price, + total_fee, ) .unwrap(); pessimistic_costs diff --git a/crates/fuel-gas-price-algorithm/src/v1.rs b/crates/fuel-gas-price-algorithm/src/v1.rs index b79d5968f28..e517064e6da 100644 --- a/crates/fuel-gas-price-algorithm/src/v1.rs +++ b/crates/fuel-gas-price-algorithm/src/v1.rs @@ -208,7 +208,7 @@ impl AlgorithmUpdaterV1 { used: u64, capacity: NonZeroU64, block_bytes: u64, - _gas_price: u64, + fee_wei: u64, ) -> Result<(), Error> { let expected = self.l2_block_height.saturating_add(1); if height != expected { @@ -220,19 +220,12 @@ impl AlgorithmUpdaterV1 { self.l2_block_height = height; // rewards - let block_da_reward = used.saturating_mul(self.descaled_da_price()); - self.total_da_rewards_excess = self - .total_da_rewards_excess - .saturating_add(block_da_reward.into()); - let rewards = self.total_da_rewards_excess.try_into().unwrap_or(i128::MAX); + self.update_rewards(fee_wei); + let rewards = self.clamped_rewards_as_i128(); // costs - let block_projected_da_cost = - (block_bytes as u128).saturating_mul(self.latest_da_cost_per_byte); - self.projected_total_da_cost = self - .projected_total_da_cost - .saturating_add(block_projected_da_cost); - let projected_total_da_cost = self.projected_cost_as_i128(); + self.update_projected_cost(block_bytes); + let projected_total_da_cost = self.clamped_projected_cost_as_i128(); let last_profit = rewards.saturating_sub(projected_total_da_cost); self.update_last_profit(last_profit); @@ -243,11 +236,43 @@ impl AlgorithmUpdaterV1 { } } - // We are assuming that the difference between u128::MAX and i128::MAX is negligible - fn projected_cost_as_i128(&self) -> i128 { + fn update_rewards(&mut self, fee_wei: u64) { + let block_da_reward = self.da_portion_of_fee(fee_wei); + self.total_da_rewards_excess = self + .total_da_rewards_excess + .saturating_add(block_da_reward.into()); + } + + fn update_projected_cost(&mut self, block_bytes: u64) { + let block_projected_da_cost = + (block_bytes as u128).saturating_mul(self.latest_da_cost_per_byte); + self.projected_total_da_cost = self + .projected_total_da_cost + .saturating_add(block_projected_da_cost); + } + + // Take the `fee_wei` and return the portion of the fee that should be used for paying DA costs + fn da_portion_of_fee(&self, fee_wei: u64) -> u64 { + // fee_wei * (da_price / (exec_price + da_price)) + let numerator = fee_wei.saturating_mul(self.descaled_da_price()); + let denominator = self + .descaled_exec_price() + .saturating_add(self.descaled_da_price()); + if denominator == 0 { + 0 + } else { + numerator.div_ceil(denominator) + } + } + + fn clamped_projected_cost_as_i128(&self) -> i128 { i128::try_from(self.projected_total_da_cost).unwrap_or(i128::MAX) } + fn clamped_rewards_as_i128(&self) -> i128 { + i128::try_from(self.total_da_rewards_excess).unwrap_or(i128::MAX) + } + fn update_last_profit(&mut self, new_profit: i128) { self.second_to_last_profit = self.last_profit; self.last_profit = new_profit; diff --git a/crates/fuel-gas-price-algorithm/src/v1/tests.rs b/crates/fuel-gas-price-algorithm/src/v1/tests.rs index 26e0214fcdf..707173baca4 100644 --- a/crates/fuel-gas-price-algorithm/src/v1/tests.rs +++ b/crates/fuel-gas-price-algorithm/src/v1/tests.rs @@ -44,8 +44,8 @@ impl UpdaterBuilder { Self { min_exec_gas_price: 0, min_da_gas_price: 0, - starting_exec_gas_price: 0, - starting_da_gas_price: 0, + starting_exec_gas_price: 1, + starting_da_gas_price: 1, exec_gas_price_change_percent: 0, max_change_percent: u16::MAX, diff --git a/crates/fuel-gas-price-algorithm/src/v1/tests/update_l2_block_data_tests.rs b/crates/fuel-gas-price-algorithm/src/v1/tests/update_l2_block_data_tests.rs index 0fb7263c233..0be90b5186b 100644 --- a/crates/fuel-gas-price-algorithm/src/v1/tests/update_l2_block_data_tests.rs +++ b/crates/fuel-gas-price-algorithm/src/v1/tests/update_l2_block_data_tests.rs @@ -16,11 +16,11 @@ fn update_l2_block_data__updates_l2_block() { let used = 50; let capacity = 100.try_into().unwrap(); let block_bytes = 1000; - let new_gas_price = 100; + let fee = 100; // when updater - .update_l2_block_data(height, used, capacity, block_bytes, new_gas_price) + .update_l2_block_data(height, used, capacity, block_bytes, fee) .unwrap(); // then @@ -41,11 +41,11 @@ fn update_l2_block_data__skipped_block_height_throws_error() { let used = 50; let capacity = 100.try_into().unwrap(); let block_bytes = 1000; - let new_gas_price = 100; + let fee = 100; // when let actual_error = updater - .update_l2_block_data(height, used, capacity, block_bytes, new_gas_price) + .update_l2_block_data(height, used, capacity, block_bytes, fee) .unwrap_err(); // then @@ -68,11 +68,11 @@ fn update_l2_block_data__updates_projected_cost() { let used = 50; let capacity = 100.try_into().unwrap(); let block_bytes = 1000; - let new_gas_price = 100; + let fee = 100; // when updater - .update_l2_block_data(height, used, capacity, block_bytes, new_gas_price) + .update_l2_block_data(height, used, capacity, block_bytes, fee) .unwrap(); // then @@ -95,15 +95,16 @@ fn update_l2_block_data__updates_the_total_reward_value() { let gas_used = 50; let capacity = 100.try_into().unwrap(); let block_bytes = 1000; - let new_gas_price = 200; + let fee = 10_000; // when updater - .update_l2_block_data(height, gas_used, capacity, block_bytes, new_gas_price) + .update_l2_block_data(height, gas_used, capacity, block_bytes, fee) .unwrap(); // then - let expected = gas_used * starting_da_gas_price; + let expected = (fee * starting_da_gas_price) + .div_ceil(starting_da_gas_price + starting_exec_gas_price); let actual = updater.total_da_rewards_excess; assert_eq!(actual, expected as u128); } @@ -122,11 +123,11 @@ fn update_l2_block_data__even_threshold_will_not_change_exec_gas_price() { let used = 50; let capacity = 100.try_into().unwrap(); let block_bytes = 1000; - let new_gas_price = 200; + let fee = 200; // when updater - .update_l2_block_data(height, used, capacity, block_bytes, new_gas_price) + .update_l2_block_data(height, used, capacity, block_bytes, fee) .unwrap(); // then @@ -151,11 +152,11 @@ fn update_l2_block_data__below_threshold_will_decrease_exec_gas_price() { let used = 40; let capacity = 100.try_into().unwrap(); let block_bytes = 1000; - let new_gas_price = 200; + let fee = 200; // when updater - .update_l2_block_data(height, used, capacity, block_bytes, new_gas_price) + .update_l2_block_data(height, used, capacity, block_bytes, fee) .unwrap(); // then @@ -182,11 +183,11 @@ fn update_l2_block_data__above_threshold_will_increase_exec_gas_price() { let used = 60; let capacity = 100.try_into().unwrap(); let block_bytes = 1000; - let new_gas_price = 200; + let fee = 200; // when updater - .update_l2_block_data(height, used, capacity, block_bytes, new_gas_price) + .update_l2_block_data(height, used, capacity, block_bytes, fee) .unwrap(); // then @@ -214,11 +215,11 @@ fn update_l2_block_data__exec_price_will_not_go_below_min() { let used = 40; let capacity = 100.try_into().unwrap(); let block_bytes = 1000; - let new_gas_price = 200; + let fee = 200; // when updater - .update_l2_block_data(height, used, capacity, block_bytes, new_gas_price) + .update_l2_block_data(height, used, capacity, block_bytes, fee) .unwrap(); // then @@ -244,11 +245,11 @@ fn update_l2_block_data__updates_last_and_last_last_profit() { let used = 50; let capacity = 100.try_into().unwrap(); let block_bytes = 1000; - let new_gas_price = 100; + let fee = 0; // No fee so it's easier to calculate profit // when updater - .update_l2_block_data(height, used, capacity, block_bytes, new_gas_price) + .update_l2_block_data(height, used, capacity, block_bytes, fee) .unwrap(); // then @@ -341,9 +342,9 @@ fn update_l2_block_data__price_does_not_decrease_more_than_max_percent() { let used = 50; let capacity = 100.try_into().unwrap(); let block_bytes = 1000; - let new_gas_price = 200; + let fee = 200; updater - .update_l2_block_data(height, used, capacity, block_bytes, new_gas_price) + .update_l2_block_data(height, used, capacity, block_bytes, fee) .unwrap(); // then @@ -387,9 +388,9 @@ fn update_l2_block_data__da_price_does_not_increase_more_than_max_percent() { let used = 50; let capacity = 100.try_into().unwrap(); let block_bytes = 1000; - let new_gas_price = 200; + let fee = 200; updater - .update_l2_block_data(height, used, capacity, block_bytes, new_gas_price) + .update_l2_block_data(height, used, capacity, block_bytes, fee) .unwrap(); // then @@ -430,13 +431,14 @@ fn update_l2_block_data__never_drops_below_minimum_da_gas_price() { .build(); // when + let fee = 200; updater .update_l2_block_data( updater.l2_block_height + 1, 50, 100.try_into().unwrap(), 1000, - 200, + fee, ) .unwrap(); @@ -453,7 +455,7 @@ fn update_l2_block_data__even_profit_maintains_price() { let starting_exec_gas_price = 100; let starting_da_gas_price = 100; let starting_cost = 500; - let latest_gas_per_byte = 10; + let latest_cost_per_byte = 10; let da_gas_price_denominator = 1; let block_bytes = 500u64; let starting_reward = starting_cost; @@ -464,17 +466,20 @@ fn update_l2_block_data__even_profit_maintains_price() { .with_total_rewards(starting_reward as u128) .with_known_total_cost(starting_cost as u128) .with_projected_total_cost(starting_cost as u128) - .with_da_cost_per_byte(latest_gas_per_byte as u128) + .with_da_cost_per_byte(latest_cost_per_byte as u128) .build(); // when + let da_fee = latest_cost_per_byte * block_bytes; + let total_fee = da_fee * (starting_da_gas_price + starting_exec_gas_price) + / starting_da_gas_price; updater .update_l2_block_data( updater.l2_block_height + 1, 50, 100.try_into().unwrap(), block_bytes, - starting_exec_gas_price + starting_da_gas_price, + total_fee, ) .unwrap(); let algo = updater.algorithm(); @@ -516,9 +521,9 @@ fn update_l2_block_data__negative_profit_increase_gas_price() { let used = 50; let capacity = 100u64.try_into().unwrap(); let block_bytes = 500u64; - let gas_price = 10; + let fee = 0; updater - .update_l2_block_data(height, used, capacity, block_bytes, gas_price) + .update_l2_block_data(height, used, capacity, block_bytes, fee) .unwrap(); // then