diff --git a/crates/op-revm/src/constants.rs b/crates/op-revm/src/constants.rs index 40e9b00092..243343e804 100644 --- a/crates/op-revm/src/constants.rs +++ b/crates/op-revm/src/constants.rs @@ -18,6 +18,10 @@ pub const OPERATOR_FEE_SCALAR_OFFSET: usize = 20; /// the storage slot of the 8-byte operatorFeeConstant attribute. pub const OPERATOR_FEE_CONSTANT_OFFSET: usize = 24; +/// The Jovian daFootprintGasScalar value is packed into a single storage slot. Byte offset within +/// the storage slot of the 16-byte daFootprintGasScalar attribute. +pub const DA_FOOTPRINT_GAS_SCALAR_OFFSET: usize = 0; + /// The fixed point decimal scaling factor associated with the operator fee scalar. /// /// Allows users to use 6 decimal points of precision when specifying the operator_fee_scalar. @@ -44,6 +48,10 @@ pub const ECOTONE_L1_FEE_SCALARS_SLOT: U256 = U256::from_limbs([3u64, 0, 0, 0]); /// offsets [OPERATOR_FEE_SCALAR_OFFSET] and [OPERATOR_FEE_CONSTANT_OFFSET] respectively. pub const OPERATOR_FEE_SCALARS_SLOT: U256 = U256::from_limbs([8u64, 0, 0, 0]); +/// As of the Jovian upgrade, this storage slot stores the 32-bit daFootprintGasScalar attribute at +/// offset [DA_FOOTPRINT_GAS_SCALAR_OFFSET]. +pub const DA_FOOTPRINT_GAS_SCALAR_SLOT: U256 = U256::from_limbs([9u64, 0, 0, 0]); + /// An empty 64-bit set of scalar values. pub const EMPTY_SCALARS: [u8; 8] = [0u8; 8]; diff --git a/crates/op-revm/src/handler.rs b/crates/op-revm/src/handler.rs index c23c34ac7c..7904280d31 100644 --- a/crates/op-revm/src/handler.rs +++ b/crates/op-revm/src/handler.rs @@ -496,8 +496,9 @@ mod tests { use crate::{ api::default_ctx::OpContext, constants::{ - BASE_FEE_SCALAR_OFFSET, ECOTONE_L1_BLOB_BASE_FEE_SLOT, ECOTONE_L1_FEE_SCALARS_SLOT, - L1_BASE_FEE_SLOT, L1_BLOCK_CONTRACT, OPERATOR_FEE_SCALARS_SLOT, + BASE_FEE_SCALAR_OFFSET, DA_FOOTPRINT_GAS_SCALAR_SLOT, ECOTONE_L1_BLOB_BASE_FEE_SLOT, + ECOTONE_L1_FEE_SCALARS_SLOT, L1_BASE_FEE_SLOT, L1_BLOCK_CONTRACT, + OPERATOR_FEE_SCALARS_SLOT, }, DefaultOp, OpBuilder, OpTransaction, }; @@ -788,6 +789,105 @@ mod tests { operator_fee_scalar: Some(U256::from(OPERATOR_FEE_SCALAR)), operator_fee_constant: Some(U256::from(OPERATOR_FEE_CONST)), tx_l1_cost: Some(U256::ZERO), + da_footprint_gas_scalar: None + } + ); + } + + #[test] + fn test_parse_da_footprint_gas_scalar_jovian() { + const BLOCK_NUM: U256 = uint!(100_U256); + const L1_BASE_FEE: U256 = uint!(1_U256); + const L1_BLOB_BASE_FEE: U256 = uint!(2_U256); + const L1_BASE_FEE_SCALAR: u64 = 3; + const L1_BLOB_BASE_FEE_SCALAR: u64 = 4; + const L1_FEE_SCALARS: U256 = U256::from_limbs([ + 0, + (L1_BASE_FEE_SCALAR << (64 - BASE_FEE_SCALAR_OFFSET * 2)) | L1_BLOB_BASE_FEE_SCALAR, + 0, + 0, + ]); + const OPERATOR_FEE_SCALAR: u64 = 5; + const OPERATOR_FEE_CONST: u64 = 6; + const OPERATOR_FEE: U256 = + U256::from_limbs([OPERATOR_FEE_CONST, OPERATOR_FEE_SCALAR, 0, 0]); + const DA_FOOTPRINT_GAS_SCALAR: u16 = 7; + const DA_FOOTPRINT_GAS_SCALAR_U64: u64 = + u64::from_be_bytes([0, DA_FOOTPRINT_GAS_SCALAR as u8, 0, 0, 0, 0, 0, 0]); + const DA_FOOTPRINT_GAS_SCALAR_SLOT_VALUE: U256 = + U256::from_limbs([0, 0, 0, DA_FOOTPRINT_GAS_SCALAR_U64]); + + let mut db = InMemoryDB::default(); + let l1_block_contract = db.load_account(L1_BLOCK_CONTRACT).unwrap(); + l1_block_contract + .storage + .insert(L1_BASE_FEE_SLOT, L1_BASE_FEE); + l1_block_contract + .storage + .insert(ECOTONE_L1_BLOB_BASE_FEE_SLOT, L1_BLOB_BASE_FEE); + l1_block_contract + .storage + .insert(ECOTONE_L1_FEE_SCALARS_SLOT, L1_FEE_SCALARS); + l1_block_contract + .storage + .insert(OPERATOR_FEE_SCALARS_SLOT, OPERATOR_FEE); + l1_block_contract.storage.insert( + DA_FOOTPRINT_GAS_SCALAR_SLOT, + DA_FOOTPRINT_GAS_SCALAR_SLOT_VALUE, + ); + db.insert_account_info( + Address::ZERO, + AccountInfo { + balance: U256::from(6000), + ..Default::default() + }, + ); + + let ctx = Context::op() + .with_db(db) + .with_chain(L1BlockInfo { + l2_block: BLOCK_NUM + U256::from(1), // ahead by one block + operator_fee_scalar: Some(U256::from(2)), + operator_fee_constant: Some(U256::from(50)), + ..Default::default() + }) + .with_block(BlockEnv { + number: BLOCK_NUM, + ..Default::default() + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::JOVIAN) + // set the operator fee to a low value + .with_tx( + OpTransaction::builder() + .base(TxEnv::builder().gas_limit(10)) + .enveloped_tx(Some(bytes!("FACADE"))) + .build_fill(), + ); + + let mut evm = ctx.build_op(); + + assert_ne!(evm.ctx().chain().l2_block, BLOCK_NUM); + + let handler = + OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame>::new(); + handler + .validate_against_state_and_deduct_caller(&mut evm) + .unwrap(); + + assert_eq!( + *evm.ctx().chain(), + L1BlockInfo { + l2_block: BLOCK_NUM, + l1_base_fee: L1_BASE_FEE, + l1_base_fee_scalar: U256::from(L1_BASE_FEE_SCALAR), + l1_blob_base_fee: Some(L1_BLOB_BASE_FEE), + l1_blob_base_fee_scalar: Some(U256::from(L1_BLOB_BASE_FEE_SCALAR)), + empty_ecotone_scalars: false, + l1_fee_overhead: None, + operator_fee_scalar: Some(U256::from(OPERATOR_FEE_SCALAR)), + operator_fee_constant: Some(U256::from(OPERATOR_FEE_CONST)), + tx_l1_cost: Some(U256::ZERO), + da_footprint_gas_scalar: Some(DA_FOOTPRINT_GAS_SCALAR), } ); } diff --git a/crates/op-revm/src/l1block.rs b/crates/op-revm/src/l1block.rs index 70553ede72..788b47f7df 100644 --- a/crates/op-revm/src/l1block.rs +++ b/crates/op-revm/src/l1block.rs @@ -1,11 +1,11 @@ //! Contains the `[L1BlockInfo]` type and its implementation. use crate::{ constants::{ - BASE_FEE_SCALAR_OFFSET, BLOB_BASE_FEE_SCALAR_OFFSET, ECOTONE_L1_BLOB_BASE_FEE_SLOT, - ECOTONE_L1_FEE_SCALARS_SLOT, EMPTY_SCALARS, L1_BASE_FEE_SLOT, L1_BLOCK_CONTRACT, - L1_OVERHEAD_SLOT, L1_SCALAR_SLOT, NON_ZERO_BYTE_COST, OPERATOR_FEE_CONSTANT_OFFSET, - OPERATOR_FEE_JOVIAN_MULTIPLIER, OPERATOR_FEE_SCALARS_SLOT, OPERATOR_FEE_SCALAR_DECIMAL, - OPERATOR_FEE_SCALAR_OFFSET, + BASE_FEE_SCALAR_OFFSET, BLOB_BASE_FEE_SCALAR_OFFSET, DA_FOOTPRINT_GAS_SCALAR_OFFSET, + DA_FOOTPRINT_GAS_SCALAR_SLOT, ECOTONE_L1_BLOB_BASE_FEE_SLOT, ECOTONE_L1_FEE_SCALARS_SLOT, + EMPTY_SCALARS, L1_BASE_FEE_SLOT, L1_BLOCK_CONTRACT, L1_OVERHEAD_SLOT, L1_SCALAR_SLOT, + NON_ZERO_BYTE_COST, OPERATOR_FEE_CONSTANT_OFFSET, OPERATOR_FEE_JOVIAN_MULTIPLIER, + OPERATOR_FEE_SCALARS_SLOT, OPERATOR_FEE_SCALAR_DECIMAL, OPERATOR_FEE_SCALAR_OFFSET, }, transaction::estimate_tx_compressed_size, OpSpecId, @@ -49,13 +49,84 @@ pub struct L1BlockInfo { pub operator_fee_scalar: Option, /// The current L1 blob base fee scalar. None if Isthmus is not activated. pub operator_fee_constant: Option, + /// Da footprint gas scalar. Used to set the DA footprint block limit on the L2. Always null prior to the Jovian hardfork. + pub da_footprint_gas_scalar: Option, /// True if Ecotone is activated, but the L1 fee scalars have not yet been set. - pub(crate) empty_ecotone_scalars: bool, + pub empty_ecotone_scalars: bool, /// Last calculated l1 fee cost. Uses as a cache between validation and pre execution stages. pub tx_l1_cost: Option, } impl L1BlockInfo { + /// Try to fetch the L1 block info from the database, post-Jovian. + fn try_fetch_jovian(&mut self, db: &mut DB) -> Result<(), DB::Error> { + let da_footprint_gas_scalar_slot = db + .storage(L1_BLOCK_CONTRACT, DA_FOOTPRINT_GAS_SCALAR_SLOT)? + .to_be_bytes::<32>(); + + // Extract the first 2 bytes directly as a u16 in big-endian format + let bytes = [ + da_footprint_gas_scalar_slot[DA_FOOTPRINT_GAS_SCALAR_OFFSET], + da_footprint_gas_scalar_slot[DA_FOOTPRINT_GAS_SCALAR_OFFSET + 1], + ]; + self.da_footprint_gas_scalar = Some(u16::from_be_bytes(bytes)); + + Ok(()) + } + + /// Try to fetch the L1 block info from the database, post-Isthmus. + fn try_fetch_isthmus(&mut self, db: &mut DB) -> Result<(), DB::Error> { + // Post-isthmus L1 block info + let operator_fee_scalars = db + .storage(L1_BLOCK_CONTRACT, OPERATOR_FEE_SCALARS_SLOT)? + .to_be_bytes::<32>(); + + // The `operator_fee_scalar` is stored as a big endian u32 at + // OPERATOR_FEE_SCALAR_OFFSET. + self.operator_fee_scalar = Some(U256::from_be_slice( + operator_fee_scalars[OPERATOR_FEE_SCALAR_OFFSET..OPERATOR_FEE_SCALAR_OFFSET + 4] + .as_ref(), + )); + // The `operator_fee_constant` is stored as a big endian u64 at + // OPERATOR_FEE_CONSTANT_OFFSET. + self.operator_fee_constant = Some(U256::from_be_slice( + operator_fee_scalars[OPERATOR_FEE_CONSTANT_OFFSET..OPERATOR_FEE_CONSTANT_OFFSET + 8] + .as_ref(), + )); + + Ok(()) + } + + /// Try to fetch the L1 block info from the database, post-Ecotone. + fn try_fetch_ecotone(&mut self, db: &mut DB) -> Result<(), DB::Error> { + self.l1_blob_base_fee = Some(db.storage(L1_BLOCK_CONTRACT, ECOTONE_L1_BLOB_BASE_FEE_SLOT)?); + + let l1_fee_scalars = db + .storage(L1_BLOCK_CONTRACT, ECOTONE_L1_FEE_SCALARS_SLOT)? + .to_be_bytes::<32>(); + + self.l1_base_fee_scalar = U256::from_be_slice( + l1_fee_scalars[BASE_FEE_SCALAR_OFFSET..BASE_FEE_SCALAR_OFFSET + 4].as_ref(), + ); + + let l1_blob_base_fee = U256::from_be_slice( + l1_fee_scalars[BLOB_BASE_FEE_SCALAR_OFFSET..BLOB_BASE_FEE_SCALAR_OFFSET + 4].as_ref(), + ); + self.l1_blob_base_fee_scalar = Some(l1_blob_base_fee); + + // Check if the L1 fee scalars are empty. If so, we use the Bedrock cost function. + // The L1 fee overhead is only necessary if `empty_ecotone_scalars` is true, as it was deprecated in Ecotone. + self.empty_ecotone_scalars = l1_blob_base_fee.is_zero() + && l1_fee_scalars[BASE_FEE_SCALAR_OFFSET..BLOB_BASE_FEE_SCALAR_OFFSET + 4] + == EMPTY_SCALARS; + self.l1_fee_overhead = self + .empty_ecotone_scalars + .then(|| db.storage(L1_BLOCK_CONTRACT, L1_OVERHEAD_SLOT)) + .transpose()?; + + Ok(()) + } + /// Try to fetch the L1 block info from the database. pub fn try_fetch( db: &mut DB, @@ -68,88 +139,33 @@ impl L1BlockInfo { let _ = db.basic(L1_BLOCK_CONTRACT)?; } - let l1_base_fee = db.storage(L1_BLOCK_CONTRACT, L1_BASE_FEE_SLOT)?; + let mut out = L1BlockInfo { + l2_block, + l1_base_fee: db.storage(L1_BLOCK_CONTRACT, L1_BASE_FEE_SLOT)?, + ..Default::default() + }; + // Post-Ecotone if !spec_id.is_enabled_in(OpSpecId::ECOTONE) { - let l1_fee_overhead = db.storage(L1_BLOCK_CONTRACT, L1_OVERHEAD_SLOT)?; - let l1_fee_scalar = db.storage(L1_BLOCK_CONTRACT, L1_SCALAR_SLOT)?; - - Ok(L1BlockInfo { - l2_block, - l1_base_fee, - l1_fee_overhead: Some(l1_fee_overhead), - l1_base_fee_scalar: l1_fee_scalar, - ..Default::default() - }) - } else { - let l1_blob_base_fee = db.storage(L1_BLOCK_CONTRACT, ECOTONE_L1_BLOB_BASE_FEE_SLOT)?; - let l1_fee_scalars = db - .storage(L1_BLOCK_CONTRACT, ECOTONE_L1_FEE_SCALARS_SLOT)? - .to_be_bytes::<32>(); - - let l1_base_fee_scalar = U256::from_be_slice( - l1_fee_scalars[BASE_FEE_SCALAR_OFFSET..BASE_FEE_SCALAR_OFFSET + 4].as_ref(), - ); - let l1_blob_base_fee_scalar = U256::from_be_slice( - l1_fee_scalars[BLOB_BASE_FEE_SCALAR_OFFSET..BLOB_BASE_FEE_SCALAR_OFFSET + 4] - .as_ref(), - ); - - // Check if the L1 fee scalars are empty. If so, we use the Bedrock cost function. - // The L1 fee overhead is only necessary if `empty_ecotone_scalars` is true, as it was deprecated in Ecotone. - let empty_ecotone_scalars = l1_blob_base_fee.is_zero() - && l1_fee_scalars[BASE_FEE_SCALAR_OFFSET..BLOB_BASE_FEE_SCALAR_OFFSET + 4] - == EMPTY_SCALARS; - let l1_fee_overhead = empty_ecotone_scalars - .then(|| db.storage(L1_BLOCK_CONTRACT, L1_OVERHEAD_SLOT)) - .transpose()?; - - if spec_id.is_enabled_in(OpSpecId::ISTHMUS) { - let operator_fee_scalars = db - .storage(L1_BLOCK_CONTRACT, OPERATOR_FEE_SCALARS_SLOT)? - .to_be_bytes::<32>(); - - // Post-isthmus L1 block info - // The `operator_fee_scalar` is stored as a big endian u32 at - // OPERATOR_FEE_SCALAR_OFFSET. - let operator_fee_scalar = U256::from_be_slice( - operator_fee_scalars - [OPERATOR_FEE_SCALAR_OFFSET..OPERATOR_FEE_SCALAR_OFFSET + 4] - .as_ref(), - ); - // The `operator_fee_constant` is stored as a big endian u64 at - // OPERATOR_FEE_CONSTANT_OFFSET. - let operator_fee_constant = U256::from_be_slice( - operator_fee_scalars - [OPERATOR_FEE_CONSTANT_OFFSET..OPERATOR_FEE_CONSTANT_OFFSET + 8] - .as_ref(), - ); - Ok(L1BlockInfo { - l2_block, - l1_base_fee, - l1_base_fee_scalar, - l1_blob_base_fee: Some(l1_blob_base_fee), - l1_blob_base_fee_scalar: Some(l1_blob_base_fee_scalar), - empty_ecotone_scalars, - l1_fee_overhead, - operator_fee_scalar: Some(operator_fee_scalar), - operator_fee_constant: Some(operator_fee_constant), - tx_l1_cost: None, - }) - } else { - // Pre-isthmus L1 block info - Ok(L1BlockInfo { - l2_block, - l1_base_fee, - l1_base_fee_scalar, - l1_blob_base_fee: Some(l1_blob_base_fee), - l1_blob_base_fee_scalar: Some(l1_blob_base_fee_scalar), - empty_ecotone_scalars, - l1_fee_overhead, - ..Default::default() - }) - } + out.l1_base_fee_scalar = db.storage(L1_BLOCK_CONTRACT, L1_SCALAR_SLOT)?; + out.l1_fee_overhead = Some(db.storage(L1_BLOCK_CONTRACT, L1_OVERHEAD_SLOT)?); + + return Ok(out); } + + out.try_fetch_ecotone(db)?; + + // Post-Isthmus L1 block info + if spec_id.is_enabled_in(OpSpecId::ISTHMUS) { + out.try_fetch_isthmus(db)?; + } + + // Pre-Jovian + if spec_id.is_enabled_in(OpSpecId::JOVIAN) { + out.try_fetch_jovian(db)?; + } + + Ok(out) } /// Calculate the operator fee for executing this transaction. diff --git a/crates/op-revm/src/precompiles.rs b/crates/op-revm/src/precompiles.rs index d0f67bddb4..f27bf339b5 100644 --- a/crates/op-revm/src/precompiles.rs +++ b/crates/op-revm/src/precompiles.rs @@ -34,7 +34,7 @@ impl OpPrecompiles { | OpSpecId::ECOTONE) => Precompiles::new(spec.into_eth_spec().into()), OpSpecId::FJORD => fjord(), OpSpecId::GRANITE | OpSpecId::HOLOCENE => granite(), - OpSpecId::ISTHMUS | OpSpecId::JOVIAN | OpSpecId::INTEROP | OpSpecId::OSAKA => isthmus(), + OpSpecId::ISTHMUS | OpSpecId::INTEROP | OpSpecId::OSAKA | OpSpecId::JOVIAN => isthmus(), }; Self { diff --git a/crates/op-revm/src/spec.rs b/crates/op-revm/src/spec.rs index c36ff8b293..35b15c3c9a 100644 --- a/crates/op-revm/src/spec.rs +++ b/crates/op-revm/src/spec.rs @@ -40,8 +40,8 @@ impl OpSpecId { Self::BEDROCK | Self::REGOLITH => SpecId::MERGE, Self::CANYON => SpecId::SHANGHAI, Self::ECOTONE | Self::FJORD | Self::GRANITE | Self::HOLOCENE => SpecId::CANCUN, - Self::ISTHMUS | Self::JOVIAN | Self::INTEROP => SpecId::PRAGUE, - Self::OSAKA => SpecId::OSAKA, + Self::ISTHMUS | Self::INTEROP => SpecId::PRAGUE, + Self::JOVIAN | Self::OSAKA => SpecId::OSAKA, } } @@ -194,6 +194,25 @@ mod tests { (OpSpecId::FJORD, true), ], ), + ( + OpSpecId::JOVIAN, + vec![ + (SpecId::PRAGUE, true), + (SpecId::SHANGHAI, true), + (SpecId::CANCUN, true), + (SpecId::MERGE, true), + (SpecId::OSAKA, true), + ], + vec![ + (OpSpecId::BEDROCK, true), + (OpSpecId::REGOLITH, true), + (OpSpecId::CANYON, true), + (OpSpecId::ECOTONE, true), + (OpSpecId::FJORD, true), + (OpSpecId::HOLOCENE, true), + (OpSpecId::ISTHMUS, true), + ], + ), ]; for (op_spec, eth_tests, op_tests) in test_cases {