From 19e54f74243afa72069d440eab799d13f0dbf352 Mon Sep 17 00:00:00 2001 From: amosStarkware <88497213+amosStarkware@users.noreply.github.com> Date: Thu, 21 Sep 2023 13:28:29 +0300 Subject: [PATCH] Objects and methods for STRK gas price calculation (#928) * Added objects and methods to compute gas price in STRK. * fix clippy error. --- crates/blockifier/src/fee.rs | 2 + crates/blockifier/src/fee/errors.rs | 7 ++ crates/blockifier/src/fee/strk_gas_price.rs | 103 ++++++++++++++++++ .../blockifier/src/fee/strk_gas_price_test.rs | 56 ++++++++++ .../src/transaction/transactions_test.rs | 1 - 5 files changed, 168 insertions(+), 1 deletion(-) create mode 100644 crates/blockifier/src/fee/errors.rs create mode 100644 crates/blockifier/src/fee/strk_gas_price.rs create mode 100644 crates/blockifier/src/fee/strk_gas_price_test.rs diff --git a/crates/blockifier/src/fee.rs b/crates/blockifier/src/fee.rs index 0e4bd2a406..a38344ded2 100644 --- a/crates/blockifier/src/fee.rs +++ b/crates/blockifier/src/fee.rs @@ -1,5 +1,7 @@ +pub mod errors; pub mod eth_gas_constants; pub mod fee_utils; pub mod gas_usage; pub mod os_resources; pub mod os_usage; +pub mod strk_gas_price; diff --git a/crates/blockifier/src/fee/errors.rs b/crates/blockifier/src/fee/errors.rs new file mode 100644 index 0000000000..19871d75b9 --- /dev/null +++ b/crates/blockifier/src/fee/errors.rs @@ -0,0 +1,7 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum StrkGasPriceCalcError { + #[error("No pool states provided.")] + NoPoolStatesError, +} diff --git a/crates/blockifier/src/fee/strk_gas_price.rs b/crates/blockifier/src/fee/strk_gas_price.rs new file mode 100644 index 0000000000..4011e000a9 --- /dev/null +++ b/crates/blockifier/src/fee/strk_gas_price.rs @@ -0,0 +1,103 @@ +use std::cmp::Ordering; + +use num_bigint::BigUint; +use num_traits::Zero; + +use super::errors::StrkGasPriceCalcError; + +#[cfg(test)] +#[path = "strk_gas_price_test.rs"] +pub mod test; + +/// Struct representing the current state of a STRK<->ETH AMM pool. +#[derive(Clone, Debug)] +pub struct PoolState { + pub total_wei: BigUint, + pub total_strk: BigUint, +} + +impl PoolState { + pub fn tvl_in_wei(&self) -> BigUint { + // Assumption on pool is the two pools have the same total value. + self.total_wei.clone() << 1 + } + /// Returns the result of comparing two pool states by STRK / Wei ratio. + pub fn compare_strk_to_wei_ratio(&self, other: &Self) -> Ordering { + // a / b < c / d <=> a * d < c * b. The same is true for the other orders. + let lhs = self.total_strk.clone() * other.total_wei.clone(); + let rhs = other.total_strk.clone() * self.total_wei.clone(); + lhs.cmp(&rhs) + } +} + +/// Struct representing aggregate of STRK<->ETH AMM pools. +/// Converts Wei to STRK at the STRK / Wei ratio of the weighted median pool state. +#[derive(Clone, Debug)] +pub struct PoolStateAggregator { + // Pool states are sorted by STRK / Wei ratio. + pub sorted_pool_states: Vec, + // See PoolStateAggregator::calc_median_values() for more info on median calculation. + pub median_pool_strk_tvl: BigUint, + pub median_pool_wei_tvl: BigUint, +} + +impl PoolStateAggregator { + pub fn new(pool_states: &[PoolState]) -> Result { + if pool_states.is_empty() { + return Err(StrkGasPriceCalcError::NoPoolStatesError); + } + + let mut sorted_pool_states: Vec = pool_states.to_vec(); + sorted_pool_states.sort_unstable_by(|pool_state_a, pool_state_b| { + pool_state_a.compare_strk_to_wei_ratio(pool_state_b) + }); + + let (median_pool_strk_tvl, median_pool_wei_tvl) = + Self::calc_median_values(&sorted_pool_states); + + Ok(Self { sorted_pool_states, median_pool_strk_tvl, median_pool_wei_tvl }) + } + + /// Returns the STRK and Wei TVL of the weighted median pool state: + /// The pool state with a STRK TVL / Wei TVL ratio such that the sum of the weights of the pool + /// states with a smaller ratio is smaller or equal to half the total weight (and the same for + /// pools with a larger ratio). If two such pools exist the average is returned. + /// The pool states are weighted by the total TVL in Wei. + /// This function assumes the given slice is sorted by STRK / Wei ratio. + pub fn calc_median_values(sorted_pool_states: &[PoolState]) -> (BigUint, BigUint) { + let total_weight: BigUint = + sorted_pool_states.iter().map(|state| state.tvl_in_wei()).sum::(); + + // Find index of weighted median STRK / Wei ratio. + let mut current_weight: BigUint = BigUint::zero(); + let mut median_idx = 0; + let equal_weight_partition: bool; + loop { + current_weight += sorted_pool_states[median_idx].tvl_in_wei().clone(); + if current_weight.clone() << 1 >= total_weight { + equal_weight_partition = current_weight << 1 == total_weight; + break; + } + median_idx += 1; + } + + let median_pool_strk_tvl: BigUint; + let median_pool_wei_tvl: BigUint; + if equal_weight_partition { + median_pool_strk_tvl = (sorted_pool_states[median_idx].total_strk.clone() + + sorted_pool_states[median_idx + 1].total_strk.clone()) + / BigUint::from(2_u32); + median_pool_wei_tvl = (sorted_pool_states[median_idx].total_wei.clone() + + sorted_pool_states[median_idx + 1].total_wei.clone()) + / BigUint::from(2_u32); + } else { + median_pool_strk_tvl = sorted_pool_states[median_idx].total_strk.clone(); + median_pool_wei_tvl = sorted_pool_states[median_idx].total_wei.clone(); + } + (median_pool_strk_tvl, median_pool_wei_tvl) + } + + pub fn convert_wei_to_strk(&self, wei_amount: BigUint) -> BigUint { + (wei_amount * self.median_pool_strk_tvl.clone()) / self.median_pool_wei_tvl.clone() + } +} diff --git a/crates/blockifier/src/fee/strk_gas_price_test.rs b/crates/blockifier/src/fee/strk_gas_price_test.rs new file mode 100644 index 0000000000..354be4b0db --- /dev/null +++ b/crates/blockifier/src/fee/strk_gas_price_test.rs @@ -0,0 +1,56 @@ +use num_bigint::BigUint; + +use crate::fee::errors::StrkGasPriceCalcError; +use crate::fee::strk_gas_price::{PoolState, PoolStateAggregator}; + +/// Sanity tests for STRK<->ETH price computation. +#[test] +fn test_convert_wei_to_strk() { + let (wei_1, wei_2, wei_3) = + (BigUint::from(10_u32), BigUint::from(14_u32), BigUint::from(12_u32)); + let (strk_1, strk_2, strk_3, strk_4) = ( + BigUint::from(50_u32), + BigUint::from(42_u32), + BigUint::from(24_u32), + BigUint::from(150_u32), + ); + let state_1 = PoolState { total_wei: wei_1.clone(), total_strk: strk_1 }; + let state_2 = PoolState { total_wei: wei_2, total_strk: strk_2 }; + let state_3 = PoolState { total_wei: wei_3, total_strk: strk_3 }; + let state_4 = PoolState { total_wei: wei_1, total_strk: strk_4 }; + let wei_amount = BigUint::from(10_000_000_000_u64); + + // Bad flow: ratio computation on empty array. + assert!(matches!(PoolStateAggregator::new(&[]), Err(StrkGasPriceCalcError::NoPoolStatesError))); + + // Convert Wei -> STRK with a single pool state. + assert_eq!( + PoolStateAggregator::new(&[state_1.clone()]) + .unwrap() + .convert_wei_to_strk(wei_amount.clone()), + (state_1.total_strk.clone() * wei_amount.clone()) / state_1.total_wei.clone() + ); + + // Convert Wei -> STRK with multiple pool states, no equal weight partition. + assert_eq!( + PoolStateAggregator::new(&[state_3.clone(), state_1.clone()]) + .unwrap() + .convert_wei_to_strk(wei_amount.clone()), + (state_3.total_strk.clone() * wei_amount.clone()) / state_3.total_wei.clone() + ); + assert_eq!( + PoolStateAggregator::new(&[state_3, state_1.clone(), state_2.clone()]) + .unwrap() + .convert_wei_to_strk(wei_amount.clone()), + (state_2.total_strk.clone() * wei_amount.clone()) / state_2.total_wei.clone() + ); + + // Convert Wei -> STRK with multiple pool states with equal weight partition. + assert_eq!( + PoolStateAggregator::new(&[state_1.clone(), state_4.clone()]) + .unwrap() + .convert_wei_to_strk(wei_amount.clone()), + ((state_1.total_strk + state_4.total_strk) * wei_amount) + / (state_1.total_wei + state_4.total_wei) + ); +} diff --git a/crates/blockifier/src/transaction/transactions_test.rs b/crates/blockifier/src/transaction/transactions_test.rs index abbdd26a7b..e15f6358f5 100644 --- a/crates/blockifier/src/transaction/transactions_test.rs +++ b/crates/blockifier/src/transaction/transactions_test.rs @@ -605,7 +605,6 @@ fn test_declare_tx( tx_hash: TransactionHash::default(), contract_class: contract_class.clone(), }); - let fee_type = &account_tx.fee_type(); // Check state before transaction application. assert_matches!(