diff --git a/crates/op-revm/src/constants.rs b/crates/op-revm/src/constants.rs index c639e8573c..40e9b00092 100644 --- a/crates/op-revm/src/constants.rs +++ b/crates/op-revm/src/constants.rs @@ -23,6 +23,9 @@ pub const OPERATOR_FEE_CONSTANT_OFFSET: usize = 24; /// Allows users to use 6 decimal points of precision when specifying the operator_fee_scalar. pub const OPERATOR_FEE_SCALAR_DECIMAL: u64 = 1_000_000; +/// The Jovian multiplier applied to the operator fee scalar component. +pub const OPERATOR_FEE_JOVIAN_MULTIPLIER: u64 = 100; + /// The L1 base fee slot. pub const L1_BASE_FEE_SLOT: U256 = U256::from_limbs([1u64, 0, 0, 0]); /// The L1 overhead slot. diff --git a/crates/op-revm/src/handler.rs b/crates/op-revm/src/handler.rs index e28150a7ef..c23c34ac7c 100644 --- a/crates/op-revm/src/handler.rs +++ b/crates/op-revm/src/handler.rs @@ -135,7 +135,9 @@ where // compute operator fee if spec.is_enabled_in(OpSpecId::ISTHMUS) { let gas_limit = U256::from(ctx.tx().gas_limit()); - let operator_fee_charge = ctx.chain().operator_fee_charge(&enveloped_tx, gas_limit); + let operator_fee_charge = + ctx.chain() + .operator_fee_charge(&enveloped_tx, gas_limit, spec); additional_cost = additional_cost.saturating_add(operator_fee_charge); } } @@ -352,7 +354,11 @@ where let l1_cost = l1_block_info.calculate_tx_l1_cost(enveloped_tx, spec); let operator_fee_cost = if spec.is_enabled_in(OpSpecId::ISTHMUS) { - l1_block_info.operator_fee_charge(enveloped_tx, U256::from(frame_result.gas().used())) + l1_block_info.operator_fee_charge( + enveloped_tx, + U256::from(frame_result.gas().used()), + spec, + ) } else { U256::ZERO }; @@ -948,7 +954,7 @@ mod tests { } #[test] - fn test_remove_operator_cost() { + fn test_remove_operator_cost_isthmus() { let caller = Address::ZERO; let mut db = InMemoryDB::default(); db.insert_account_info( @@ -977,7 +983,7 @@ mod tests { let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame>::new(); - // operator fee cost is operator_fee_scalar * gas_limit / 1e6 + operator_fee_constant + // Under Isthmus the operator fee cost is operator_fee_scalar * gas_limit / 1e6 + operator_fee_constant // 10_000_000 * 10 / 1_000_000 + 50 = 150 handler .validate_against_state_and_deduct_caller(&mut evm) @@ -988,6 +994,46 @@ mod tests { assert_eq!(account.info.balance, U256::from(1)); } + #[test] + fn test_remove_operator_cost_jovian() { + let caller = Address::ZERO; + let mut db = InMemoryDB::default(); + db.insert_account_info( + caller, + AccountInfo { + balance: U256::from(2_051), + ..Default::default() + }, + ); + let ctx = Context::op() + .with_db(db) + .with_chain(L1BlockInfo { + operator_fee_scalar: Some(U256::from(2)), + operator_fee_constant: Some(U256::from(50)), + ..Default::default() + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::JOVIAN) + .with_tx( + OpTransaction::builder() + .base(TxEnv::builder().gas_limit(10)) + .enveloped_tx(Some(bytes!("FACADE"))) + .build_fill(), + ); + + let mut evm = ctx.build_op(); + let handler = + OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame>::new(); + + // Under Jovian the operator fee cost is operator_fee_scalar * gas_limit * 100 + operator_fee_constant + // 2 * 10 * 100 + 50 = 2_050 + handler + .validate_against_state_and_deduct_caller(&mut evm) + .unwrap(); + + let account = evm.ctx().journal_mut().load_account(caller).unwrap(); + assert_eq!(account.info.balance, U256::from(1)); + } + #[test] fn test_remove_l1_cost_lack_of_funds() { let caller = Address::ZERO; diff --git a/crates/op-revm/src/l1block.rs b/crates/op-revm/src/l1block.rs index 8c4e5b936f..70553ede72 100644 --- a/crates/op-revm/src/l1block.rs +++ b/crates/op-revm/src/l1block.rs @@ -4,7 +4,8 @@ use crate::{ 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_SCALARS_SLOT, OPERATOR_FEE_SCALAR_DECIMAL, OPERATOR_FEE_SCALAR_OFFSET, + OPERATOR_FEE_JOVIAN_MULTIPLIER, OPERATOR_FEE_SCALARS_SLOT, OPERATOR_FEE_SCALAR_DECIMAL, + OPERATOR_FEE_SCALAR_OFFSET, }, transaction::estimate_tx_compressed_size, OpSpecId, @@ -154,17 +155,17 @@ impl L1BlockInfo { /// Calculate the operator fee for executing this transaction. /// /// Introduced in isthmus. Prior to isthmus, the operator fee is always zero. - pub fn operator_fee_charge(&self, input: &[u8], gas_limit: U256) -> U256 { + pub fn operator_fee_charge(&self, input: &[u8], gas_limit: U256, spec_id: OpSpecId) -> U256 { // If the input is a deposit transaction or empty, the default value is zero. if input.is_empty() || input.first() == Some(&0x7E) { return U256::ZERO; } - self.operator_fee_charge_inner(gas_limit) + self.operator_fee_charge_inner(gas_limit, spec_id) } /// Calculate the operator fee for the given `gas`. - fn operator_fee_charge_inner(&self, gas: U256) -> U256 { + fn operator_fee_charge_inner(&self, gas: U256, spec_id: OpSpecId) -> U256 { let operator_fee_scalar = self .operator_fee_scalar .expect("Missing operator fee scalar for isthmus L1 Block"); @@ -172,8 +173,12 @@ impl L1BlockInfo { .operator_fee_constant .expect("Missing operator fee constant for isthmus L1 Block"); - let product = - gas.saturating_mul(operator_fee_scalar) / (U256::from(OPERATOR_FEE_SCALAR_DECIMAL)); + let product = if spec_id.is_enabled_in(OpSpecId::JOVIAN) { + gas.saturating_mul(operator_fee_scalar) + .saturating_mul(U256::from(OPERATOR_FEE_JOVIAN_MULTIPLIER)) + } else { + gas.saturating_mul(operator_fee_scalar) / U256::from(OPERATOR_FEE_SCALAR_DECIMAL) + }; product.saturating_add(operator_fee_constant) } @@ -186,10 +191,12 @@ impl L1BlockInfo { return U256::ZERO; } - let operator_cost_gas_limit = self.operator_fee_charge_inner(U256::from(gas.limit())); - let operator_cost_gas_used = self.operator_fee_charge_inner(U256::from( - gas.limit() - (gas.remaining() + gas.refunded() as u64), - )); + let operator_cost_gas_limit = + self.operator_fee_charge_inner(U256::from(gas.limit()), spec_id); + let operator_cost_gas_used = self.operator_fee_charge_inner( + U256::from(gas.limit() - (gas.remaining() + gas.refunded() as u64)), + spec_id, + ); operator_cost_gas_limit.saturating_sub(operator_cost_gas_used) } @@ -575,6 +582,25 @@ mod tests { assert_eq!(l1_fee, expected_l1_fee) } + #[test] + fn test_operator_fee_charge_formulas() { + let l1_block_info = L1BlockInfo { + operator_fee_scalar: Some(U256::from(1_000u64)), + operator_fee_constant: Some(U256::from(10u64)), + ..Default::default() + }; + + let input = [0x01u8]; + + let isthmus_fee = + l1_block_info.operator_fee_charge(&input, U256::from(1_000u64), OpSpecId::ISTHMUS); + assert_eq!(isthmus_fee, U256::from(11u64)); + + let jovian_fee = + l1_block_info.operator_fee_charge(&input, U256::from(1_000u64), OpSpecId::JOVIAN); + assert_eq!(jovian_fee, U256::from(100_000_010u64)); + } + #[test] fn test_operator_fee_refund() { let gas = Gas::new(50000); diff --git a/crates/op-revm/src/precompiles.rs b/crates/op-revm/src/precompiles.rs index 033b2f3a00..d0f67bddb4 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::INTEROP | OpSpecId::OSAKA => isthmus(), + OpSpecId::ISTHMUS | OpSpecId::JOVIAN | OpSpecId::INTEROP | OpSpecId::OSAKA => isthmus(), }; Self { diff --git a/crates/op-revm/src/spec.rs b/crates/op-revm/src/spec.rs index c0e764c598..c36ff8b293 100644 --- a/crates/op-revm/src/spec.rs +++ b/crates/op-revm/src/spec.rs @@ -25,6 +25,8 @@ pub enum OpSpecId { /// Isthmus spec id. #[default] ISTHMUS, + /// Jovian spec id. + JOVIAN, /// Interop spec id. INTEROP, /// Osaka spec id. @@ -38,7 +40,7 @@ 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::INTEROP => SpecId::PRAGUE, + Self::ISTHMUS | Self::JOVIAN | Self::INTEROP => SpecId::PRAGUE, Self::OSAKA => SpecId::OSAKA, } } @@ -68,6 +70,7 @@ impl FromStr for OpSpecId { name::GRANITE => Ok(OpSpecId::GRANITE), name::HOLOCENE => Ok(OpSpecId::HOLOCENE), name::ISTHMUS => Ok(OpSpecId::ISTHMUS), + name::JOVIAN => Ok(OpSpecId::JOVIAN), name::INTEROP => Ok(OpSpecId::INTEROP), eth_name::OSAKA => Ok(OpSpecId::OSAKA), _ => Err(UnknownHardfork), @@ -86,6 +89,7 @@ impl From for &'static str { OpSpecId::GRANITE => name::GRANITE, OpSpecId::HOLOCENE => name::HOLOCENE, OpSpecId::ISTHMUS => name::ISTHMUS, + OpSpecId::JOVIAN => name::JOVIAN, OpSpecId::INTEROP => name::INTEROP, OpSpecId::OSAKA => eth_name::OSAKA, } @@ -110,6 +114,8 @@ pub mod name { pub const HOLOCENE: &str = "Holocene"; /// Isthmus spec name. pub const ISTHMUS: &str = "Isthmus"; + /// Jovian spec name. + pub const JOVIAN: &str = "Jovian"; /// Interop spec name. pub const INTEROP: &str = "Interop"; }