fuel_gas_price_algorithm/v0.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
use std::{
cmp::max,
num::NonZeroU64,
};
use crate::utils::cumulative_percentage_change;
#[cfg(test)]
mod tests;
#[derive(Debug, thiserror::Error, PartialEq)]
pub enum Error {
#[error("Skipped L2 block update: expected {expected:?}, got {got:?}")]
SkippedL2Block { expected: u32, got: u32 },
}
#[derive(Debug, Clone, PartialEq)]
pub struct AlgorithmV0 {
/// The gas price for to cover the execution of the next block
new_exec_price: u64,
/// The block height of the next L2 block
for_height: u32,
/// The change percentage per block
percentage: u64,
}
impl AlgorithmV0 {
pub fn calculate(&self) -> u64 {
self.new_exec_price
}
pub fn worst_case(&self, height: u32) -> u64 {
cumulative_percentage_change(
self.new_exec_price,
self.for_height,
self.percentage,
height,
)
}
}
/// The state of the algorithm used to update the gas price algorithm for each block
///
/// Because there will always be a delay between blocks submitted to the L2 chain and the blocks
/// being recorded on the DA chain, the updater needs to make "projections" about the cost of
/// recording any given block to the DA chain. This is done by tracking the cost per byte of recording
/// for the most recent blocks, and using the known bytes of the unrecorded blocks to estimate
/// the cost for that block. Every time the DA recording is updated, the projections are recalculated.
///
/// This projection will inevitably lead to error in the gas price calculation. Special care should be taken
/// to account for the worst case scenario when calculating the parameters of the algorithm.
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)]
pub struct AlgorithmUpdaterV0 {
/// The gas price to cover the execution of the next block
pub new_exec_price: u64,
// Execution
/// The lowest the algorithm allows the exec gas price to go
pub min_exec_gas_price: u64,
/// The Percentage the execution gas price will change in a single block, either increase or decrease
/// based on the fullness of the last L2 block
pub exec_gas_price_change_percent: u64,
/// The height of the next L2 block
pub l2_block_height: u32,
/// The threshold of gas usage above and below which the gas price will increase or decrease
/// This is a percentage of the total capacity of the L2 block
pub l2_block_fullness_threshold_percent: u64,
}
impl AlgorithmUpdaterV0 {
pub fn new(
new_exec_price: u64,
min_exec_gas_price: u64,
exec_gas_price_change_percent: u64,
l2_block_height: u32,
l2_block_fullness_threshold_percent: u64,
) -> Self {
Self {
new_exec_price,
min_exec_gas_price,
exec_gas_price_change_percent,
l2_block_height,
l2_block_fullness_threshold_percent,
}
}
pub fn update_l2_block_data(
&mut self,
height: u32,
used: u64,
capacity: NonZeroU64,
) -> Result<(), Error> {
let expected = self.l2_block_height.saturating_add(1);
if height != expected {
Err(Error::SkippedL2Block {
expected,
got: height,
})
} else {
self.l2_block_height = height;
self.update_exec_gas_price(used, capacity);
Ok(())
}
}
fn update_exec_gas_price(&mut self, used: u64, capacity: NonZeroU64) {
let mut exec_gas_price = self.new_exec_price;
let fullness_percent = used
.saturating_mul(100)
.checked_div(capacity.into())
.unwrap_or(self.l2_block_fullness_threshold_percent);
match fullness_percent.cmp(&self.l2_block_fullness_threshold_percent) {
std::cmp::Ordering::Greater | std::cmp::Ordering::Equal => {
let change_amount = self.change_amount(exec_gas_price);
exec_gas_price = exec_gas_price.saturating_add(change_amount);
}
std::cmp::Ordering::Less => {
let change_amount = self.change_amount(exec_gas_price);
exec_gas_price = exec_gas_price.saturating_sub(change_amount);
}
}
self.new_exec_price = max(self.min_exec_gas_price, exec_gas_price);
}
fn change_amount(&self, principle: u64) -> u64 {
principle
.saturating_mul(self.exec_gas_price_change_percent)
.saturating_div(100)
}
pub fn algorithm(&self) -> AlgorithmV0 {
AlgorithmV0 {
new_exec_price: self.new_exec_price,
for_height: self.l2_block_height,
percentage: self.exec_gas_price_change_percent,
}
}
}