diff --git a/src/handler.rs b/src/handler.rs index 92233fc..35ae13c 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -100,8 +100,10 @@ where "[SCROLL] Failed to load transaction rlp_bytes.".to_string(), )); }; + // Deduct l1 fee from caller. - let tx_l1_cost = l1_block_info.calculate_tx_l1_cost(rlp_bytes, spec); + let tx_l1_cost = + l1_block_info.calculate_tx_l1_cost(rlp_bytes, spec, ctx.tx().compression_ratio()); let caller_account = ctx.journal().load_account(caller)?; if tx_l1_cost.gt(&caller_account.info.balance) { return Err(InvalidTransaction::LackOfFundForMaxFee { @@ -212,7 +214,11 @@ where "[SCROLL] Failed to load transaction rlp_bytes.".to_string(), )); }; - let l1_cost = l1_block_info.calculate_tx_l1_cost(rlp_bytes, ctx.cfg().spec()); + let l1_cost = l1_block_info.calculate_tx_l1_cost( + rlp_bytes, + ctx.cfg().spec(), + ctx.tx().compression_ratio(), + ); // reward the beneficiary with the gas fee including the L1 cost of the transaction and mark // the account as touched. diff --git a/src/l1block.rs b/src/l1block.rs index 973e865..a0ce313 100644 --- a/src/l1block.rs +++ b/src/l1block.rs @@ -18,7 +18,8 @@ const NON_ZERO_BYTE_COST: u64 = 16; const TX_L1_COMMIT_EXTRA_COST: U256 = U256::from_limbs([64u64, 0, 0, 0]); /// The precision used for L1 fee calculations. -const TX_L1_FEE_PRECISION: U256 = U256::from_limbs([1_000_000_000u64, 0, 0, 0]); +pub const TX_L1_FEE_PRECISION: u64 = 1_000_000_000u64; +pub const TX_L1_FEE_PRECISION_U256: U256 = U256::from_limbs([TX_L1_FEE_PRECISION, 0, 0, 0]); /// The L1 gas price oracle address. pub const L1_GAS_PRICE_ORACLE_ADDRESS: Address = @@ -37,6 +38,8 @@ const L1_SCALAR_SLOT: U256 = U256::from_limbs([3u64, 0, 0, 0]); const L1_BLOB_BASE_FEE_SLOT: U256 = U256::from_limbs([5u64, 0, 0, 0]); /// The L1 commit scalar storage slot. +/// +/// Post-FEYNMAN this represents the exec_scalar. const L1_COMMIT_SCALAR_SLOT: U256 = U256::from_limbs([6u64, 0, 0, 0]); /// The L1 blob scalar storage slot. @@ -128,22 +131,97 @@ impl L1BlockInfo { tx_l1_gas .saturating_mul(self.l1_base_fee) .saturating_mul(self.l1_base_fee_scalar) - .wrapping_div(TX_L1_FEE_PRECISION) + .wrapping_div(TX_L1_FEE_PRECISION_U256) } fn calculate_tx_l1_cost_curie(&self, input: &[u8], spec_id: ScrollSpecId) -> U256 { // "commitScalar * l1BaseFee + blobScalar * _data.length * l1BlobBaseFee" let blob_gas = self.data_gas(input, spec_id); - self.calldata_gas.unwrap().saturating_add(blob_gas).wrapping_div(TX_L1_FEE_PRECISION) + self.calldata_gas.unwrap().saturating_add(blob_gas).wrapping_div(TX_L1_FEE_PRECISION_U256) + } + + fn calculate_tx_l1_cost_feynman( + &self, + input: &[u8], + spec_id: ScrollSpecId, + compression_ratio: U256, + ) -> U256 { + // rollup_fee(tx) = compression_factor(tx) * size(tx) * (component_exec + component_blob) + // + // - compression_factor(tx): compression_factor = 1 / compression_ratio, where + // compression_ratio is the estimated compressibility of the signed tx data. The tx is + // eventually a part of a L2 batch that should likely result in a better compression ratio, + // however a conservative estimate is the size of zstd-encoding of the signed tx. + // + // - size(tx): denotes the size of the signed tx. + // + // - component_exec: The component that accounts towards commiting this tx as part of a L2 + // batch as well as gas costs for the eventual on-chain proof verification. + // => (compression_scalar + commit_scalar + verification_scalar) * l1_base_fee + // => (exec_scalar) * l1_base_fee + // + // - component_blob: The component that accounts the costs associated with data + // availability, i.e. the costs of posting this tx's data in the EIP-4844 blob. + // => (compression_scalar + blob_scalar) * l1_blob_base_fee + // => (new_blob_scalar) * l1_blob_base_fee + // + // Note that the same slots for L1_COMMIT_SCALAR_SLOT and L1_BLOB_SCALAR_SLOT are + // re-used/updated for the new values post-FEYNMAN. + assert!( + compression_ratio >= TX_L1_FEE_PRECISION_U256, + "transaction compression ratio must be greater or equal to {TX_L1_FEE_PRECISION_U256:?} - compression ratio: {compression_ratio:?}" + ); + + let component_exec = { + let exec_scalar = self + .l1_commit_scalar + .unwrap_or_else(|| panic!("missing exec scalar in spec_id={:?}", spec_id)); + exec_scalar.saturating_mul(self.l1_base_fee) + }; + let component_blob = { + let blob_scalar = self + .l1_blob_scalar + .unwrap_or_else(|| panic!("missing l1 blob scalar in spec_id={:?}", spec_id)); + let blob_base_fee = self + .l1_blob_base_fee + .unwrap_or_else(|| panic!("missing l1 blob base fee in spec_id={:?}", spec_id)); + blob_scalar.saturating_mul(blob_base_fee) + }; + + // Assume compression_factor = 1 until we have specification for estimating compression + // ratio based on previous finalised batches. + // + // We use the `TX_L1_FEE_PRECISION` to allow fractions. We then divide the overall product + // by the precision value as well. + // let compression_factor = |_input: &[u8]| -> U256 { TX_L1_FEE_PRECISION }; + + // size(tx) is just the length of the RLP-encoded signed tx data. + let tx_size = |input: &[u8]| -> U256 { U256::from(input.len()) }; + + tx_size(input) + .saturating_mul(component_exec.saturating_add(component_blob)) + .saturating_mul(TX_L1_FEE_PRECISION_U256) + .wrapping_div(compression_ratio) + .wrapping_div(TX_L1_FEE_PRECISION_U256) } /// Calculate the gas cost of a transaction based on L1 block data posted on L2. - pub fn calculate_tx_l1_cost(&self, input: &[u8], spec_id: ScrollSpecId) -> U256 { + pub fn calculate_tx_l1_cost( + &self, + input: &[u8], + spec_id: ScrollSpecId, + compression_ratio: Option, + ) -> U256 { let l1_cost = if !spec_id.is_enabled_in(ScrollSpecId::CURIE) { self.calculate_tx_l1_cost_shanghai(input, spec_id) - } else { + } else if !spec_id.is_enabled_in(ScrollSpecId::FEYNMAN) { self.calculate_tx_l1_cost_curie(input, spec_id) + } else { + let compression_ratio = compression_ratio.unwrap_or_else(|| { + panic!("compression ratio should be set in spec_id={:?}", spec_id) + }); + self.calculate_tx_l1_cost_feynman(input, spec_id, compression_ratio) }; l1_cost.min(U64_MAX) } diff --git a/src/transaction.rs b/src/transaction.rs index b112fa6..b3121af 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -12,6 +12,10 @@ pub trait ScrollTxTr: Transaction { /// The RLP encoded transaction bytes which are used to calculate the cost associated with /// posting the transaction on L1. fn rlp_bytes(&self) -> Option<&Bytes>; + + /// The compression ratio of the transaction which is used to calculate the cost associated + /// with posting the transaction on L1. + fn compression_ratio(&self) -> Option; } /// A Scroll transaction. Wraps around a base transaction and provides the optional RLPed bytes for @@ -21,17 +25,18 @@ pub trait ScrollTxTr: Transaction { pub struct ScrollTransaction { pub base: T, pub rlp_bytes: Option, + pub compression_ratio: Option, } impl ScrollTransaction { - pub fn new(base: T, rlp_bytes: Option) -> Self { - Self { base, rlp_bytes } + pub fn new(base: T, rlp_bytes: Option, compression_ratio: Option) -> Self { + Self { base, rlp_bytes, compression_ratio } } } impl Default for ScrollTransaction { fn default() -> Self { - Self { base: TxEnv::default(), rlp_bytes: None } + Self { base: TxEnv::default(), rlp_bytes: None, compression_ratio: None } } } @@ -114,4 +119,8 @@ impl ScrollTxTr for ScrollTransaction { fn rlp_bytes(&self) -> Option<&Bytes> { self.rlp_bytes.as_ref() } + + fn compression_ratio(&self) -> Option { + self.compression_ratio + } }