From 609ee827c85a98a35ab026b9531e03e7eacbfd0c Mon Sep 17 00:00:00 2001 From: rakita Date: Sat, 8 Feb 2025 01:23:38 +0100 Subject: [PATCH 1/2] chore: Bump licence year to 2025 --- LICENSE | 2 +- bins/revme/LICENSE | 2 +- crates/bytecode/LICENSE | 2 +- crates/database/LICENSE | 2 +- crates/database/interface/LICENSE | 2 +- crates/inspector/LICENSE | 2 +- crates/interpreter/LICENSE | 2 +- crates/optimism/LICENSE | 2 +- crates/precompile/LICENSE | 2 +- crates/primitives/LICENSE | 2 +- crates/revm/LICENSE | 2 +- crates/specification/LICENSE | 2 +- crates/state/LICENSE | 2 +- crates/statetest-types/LICENSE | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/LICENSE b/LICENSE index ad98ff22cc..be6d350ebe 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2024 draganrakita +Copyright (c) 2021-2025 draganrakita Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/bins/revme/LICENSE b/bins/revme/LICENSE index ad98ff22cc..be6d350ebe 100644 --- a/bins/revme/LICENSE +++ b/bins/revme/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2024 draganrakita +Copyright (c) 2021-2025 draganrakita Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/crates/bytecode/LICENSE b/crates/bytecode/LICENSE index ad98ff22cc..be6d350ebe 100644 --- a/crates/bytecode/LICENSE +++ b/crates/bytecode/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2024 draganrakita +Copyright (c) 2021-2025 draganrakita Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/crates/database/LICENSE b/crates/database/LICENSE index ad98ff22cc..be6d350ebe 100644 --- a/crates/database/LICENSE +++ b/crates/database/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2024 draganrakita +Copyright (c) 2021-2025 draganrakita Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/crates/database/interface/LICENSE b/crates/database/interface/LICENSE index ad98ff22cc..be6d350ebe 100644 --- a/crates/database/interface/LICENSE +++ b/crates/database/interface/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2024 draganrakita +Copyright (c) 2021-2025 draganrakita Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/crates/inspector/LICENSE b/crates/inspector/LICENSE index ad98ff22cc..be6d350ebe 100644 --- a/crates/inspector/LICENSE +++ b/crates/inspector/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2024 draganrakita +Copyright (c) 2021-2025 draganrakita Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/crates/interpreter/LICENSE b/crates/interpreter/LICENSE index ad98ff22cc..be6d350ebe 100644 --- a/crates/interpreter/LICENSE +++ b/crates/interpreter/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2024 draganrakita +Copyright (c) 2021-2025 draganrakita Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/crates/optimism/LICENSE b/crates/optimism/LICENSE index ad98ff22cc..be6d350ebe 100644 --- a/crates/optimism/LICENSE +++ b/crates/optimism/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2024 draganrakita +Copyright (c) 2021-2025 draganrakita Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/crates/precompile/LICENSE b/crates/precompile/LICENSE index ad98ff22cc..be6d350ebe 100644 --- a/crates/precompile/LICENSE +++ b/crates/precompile/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2024 draganrakita +Copyright (c) 2021-2025 draganrakita Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/crates/primitives/LICENSE b/crates/primitives/LICENSE index ad98ff22cc..be6d350ebe 100644 --- a/crates/primitives/LICENSE +++ b/crates/primitives/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2024 draganrakita +Copyright (c) 2021-2025 draganrakita Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/crates/revm/LICENSE b/crates/revm/LICENSE index ad98ff22cc..be6d350ebe 100644 --- a/crates/revm/LICENSE +++ b/crates/revm/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2024 draganrakita +Copyright (c) 2021-2025 draganrakita Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/crates/specification/LICENSE b/crates/specification/LICENSE index ad98ff22cc..be6d350ebe 100644 --- a/crates/specification/LICENSE +++ b/crates/specification/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2024 draganrakita +Copyright (c) 2021-2025 draganrakita Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/crates/state/LICENSE b/crates/state/LICENSE index ad98ff22cc..be6d350ebe 100644 --- a/crates/state/LICENSE +++ b/crates/state/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2024 draganrakita +Copyright (c) 2021-2025 draganrakita Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/crates/statetest-types/LICENSE b/crates/statetest-types/LICENSE index ad98ff22cc..be6d350ebe 100644 --- a/crates/statetest-types/LICENSE +++ b/crates/statetest-types/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021-2024 draganrakita +Copyright (c) 2021-2025 draganrakita Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From b2cfa544fdd799eb9f486fcaaaaaeefbe8701bbf Mon Sep 17 00:00:00 2001 From: rakita Date: Sat, 8 Feb 2025 02:17:33 +0100 Subject: [PATCH 2/2] chore: backport handle isthmus operator fee --- crates/optimism/src/constants.rs | 53 +++++++++ crates/optimism/src/handler.rs | 106 +++++++++++++++++- crates/optimism/src/l1block.rs | 178 ++++++++++++++++++++++--------- crates/optimism/src/lib.rs | 3 +- 4 files changed, 281 insertions(+), 59 deletions(-) create mode 100644 crates/optimism/src/constants.rs diff --git a/crates/optimism/src/constants.rs b/crates/optimism/src/constants.rs new file mode 100644 index 0000000000..f983620cf6 --- /dev/null +++ b/crates/optimism/src/constants.rs @@ -0,0 +1,53 @@ +use revm::primitives::{address, Address, U256}; + +pub const ZERO_BYTE_COST: u64 = 4; +pub const NON_ZERO_BYTE_COST: u64 = 16; + +/// The two 4-byte Ecotone fee scalar values are packed into the same storage slot as the 8-byte sequence number. +/// Byte offset within the storage slot of the 4-byte baseFeeScalar attribute. +pub const BASE_FEE_SCALAR_OFFSET: usize = 16; +/// The two 4-byte Ecotone fee scalar values are packed into the same storage slot as the 8-byte sequence number. +/// Byte offset within the storage slot of the 4-byte blobBaseFeeScalar attribute. +pub const BLOB_BASE_FEE_SCALAR_OFFSET: usize = 20; + +/// The Isthmus operator fee scalar values are similarly packed. Byte offset within +/// the storage slot of the 4-byte operatorFeeScalar attribute. +pub const OPERATOR_FEE_SCALAR_OFFSET: usize = 20; +/// The Isthmus operator fee scalar values are similarly packed. Byte offset within +/// the storage slot of the 8-byte operatorFeeConstant attribute. +pub const OPERATOR_FEE_CONSTANT_OFFSET: usize = 24; + +/// 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. +pub const OPERATOR_FEE_SCALAR_DECIMAL: u64 = 1_000_000; + +pub const L1_BASE_FEE_SLOT: U256 = U256::from_limbs([1u64, 0, 0, 0]); +pub const L1_OVERHEAD_SLOT: U256 = U256::from_limbs([5u64, 0, 0, 0]); +pub const L1_SCALAR_SLOT: U256 = U256::from_limbs([6u64, 0, 0, 0]); + +/// [ECOTONE_L1_BLOB_BASE_FEE_SLOT] was added in the Ecotone upgrade and stores the L1 blobBaseFee attribute. +pub const ECOTONE_L1_BLOB_BASE_FEE_SLOT: U256 = U256::from_limbs([7u64, 0, 0, 0]); + +/// As of the ecotone upgrade, this storage slot stores the 32-bit basefeeScalar and blobBaseFeeScalar attributes at +/// offsets [BASE_FEE_SCALAR_OFFSET] and [BLOB_BASE_FEE_SCALAR_OFFSET] respectively. +pub const ECOTONE_L1_FEE_SCALARS_SLOT: U256 = U256::from_limbs([3u64, 0, 0, 0]); + +/// This storage slot stores the 32-bit operatorFeeScalar and operatorFeeConstant attributes at +/// 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]); + +/// An empty 64-bit set of scalar values. +pub const EMPTY_SCALARS: [u8; 8] = [0u8; 8]; + +/// The address of L1 fee recipient. +pub const L1_FEE_RECIPIENT: Address = address!("420000000000000000000000000000000000001A"); + +/// The address of the operator fee recipient. +pub const OPERATOR_FEE_RECIPIENT: Address = address!("420000000000000000000000000000000000001B"); + +/// The address of the base fee recipient. +pub const BASE_FEE_RECIPIENT: Address = address!("4200000000000000000000000000000000000019"); + +/// The address of the L1Block contract. +pub const L1_BLOCK_CONTRACT: Address = address!("4200000000000000000000000000000000000015"); diff --git a/crates/optimism/src/handler.rs b/crates/optimism/src/handler.rs index 5a78f05e6a..f65e3ba45c 100644 --- a/crates/optimism/src/handler.rs +++ b/crates/optimism/src/handler.rs @@ -3,11 +3,12 @@ pub mod precompiles; use crate::{ + constants::{BASE_FEE_RECIPIENT, L1_FEE_RECIPIENT, OPERATOR_FEE_RECIPIENT}, transaction::{ deposit::{DepositTransaction, DEPOSIT_TRANSACTION_TYPE}, OpTransactionError, OpTxTrait, }, - L1BlockInfo, OpHaltReason, OpSpec, OpSpecId, BASE_FEE_RECIPIENT, L1_FEE_RECIPIENT, + L1BlockInfo, OpHaltReason, OpSpec, OpSpecId, }; use precompile::Log; use revm::{ @@ -119,6 +120,7 @@ where fn deduct_caller(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> { let ctx = evm.ctx(); + let spec = ctx.cfg().spec(); let caller = ctx.tx().caller(); let is_deposit = ctx.tx().tx_type() == DEPOSIT_TRANSACTION_TYPE; @@ -148,8 +150,22 @@ where // If the transaction is not a deposit transaction, subtract the L1 data fee from the // caller's balance directly after minting the requested amount of ETH. + // Additionally deduct the operator fee from the caller's account. if !is_deposit { - let mut caller_account = evm.ctx().journal().load_account(caller)?; + let ctx = evm.ctx(); + + // Deduct the operator fee from the caller's account. + let gas_limit = U256::from(ctx.tx().gas_limit()); + let enveloped_tx = ctx + .tx() + .enveloped_tx() + .expect("all not deposit tx have enveloped tx") + .clone(); + let operator_fee_charge = + ctx.chain() + .operator_fee_charge(&enveloped_tx, gas_limit, spec); + + let mut caller_account = ctx.journal().load_account(caller)?; if tx_l1_cost > caller_account.info.balance { return Err(InvalidTransaction::LackOfFundForMaxFee { @@ -158,7 +174,12 @@ where } .into()); } - caller_account.info.balance = caller_account.info.balance.saturating_sub(tx_l1_cost); + + // TODO check if operator_fee_charge needs to be checked against balance! + caller_account.info.balance = caller_account + .info + .balance + .saturating_sub(tx_l1_cost.saturating_add(operator_fee_charge)); } Ok(()) } @@ -229,6 +250,33 @@ where Ok(()) } + fn reimburse_caller( + &self, + evm: &mut Self::Evm, + exec_result: &mut ::FrameResult, + ) -> Result<(), Self::Error> { + self.mainnet.reimburse_caller(evm, exec_result)?; + + let context = evm.ctx(); + if context.tx().source_hash().is_zero() { + let caller = context.tx().caller(); + let spec = context.cfg().spec(); + let operator_fee_refund = context.chain().operator_fee_refund(exec_result.gas(), spec); + + let caller_account = context.journal().load_account(caller)?; + + // In additional to the normal transaction fee, additionally refund the caller + // for the operator fee. + caller_account.data.info.balance = caller_account + .data + .info + .balance + .saturating_add(operator_fee_refund); + } + + Ok(()) + } + fn refund( &self, evm: &mut Self::Evm, @@ -264,17 +312,22 @@ where // If the transaction is not a deposit transaction, fees are paid out // to both the Base Fee Vault as well as the L1 Fee Vault. let ctx = evm.ctx(); - let envolepo = ctx.tx().enveloped_tx().cloned(); + let enveloped = ctx.tx().enveloped_tx().cloned(); let spec = ctx.cfg().spec(); let l1_block_info = ctx.chain(); - let Some(enveloped_tx) = &envolepo else { + let Some(enveloped_tx) = &enveloped else { return Err(ERROR::from_string( "[OPTIMISM] Failed to load enveloped transaction.".into(), )); }; let l1_cost = l1_block_info.calculate_tx_l1_cost(enveloped_tx, spec); + let operator_fee_cost = l1_block_info.operator_fee_charge( + enveloped_tx, + U256::from(exec_result.gas().spent() - exec_result.gas().refunded() as u64), + spec, + ); // Send the L1 cost of the transaction to the L1 Fee Vault. let mut l1_fee_vault_account = ctx.journal().load_account(L1_FEE_RECIPIENT)?; @@ -288,6 +341,13 @@ where base_fee_vault_account.info.balance += U256::from(basefee.saturating_mul( (exec_result.gas().spent() - exec_result.gas().refunded() as u64) as u128, )); + + // Send the operator fee of the transaction to the coinbase. + let mut operator_fee_vault_account = + evm.ctx().journal().load_account(OPERATOR_FEE_RECIPIENT)?; + + operator_fee_vault_account.mark_touch(); + operator_fee_vault_account.data.info.balance += operator_fee_cost; } Ok(()) } @@ -623,6 +683,42 @@ mod tests { assert_eq!(account.info.balance, U256::from(1)); } + #[test] + fn test_remove_operator_cost() { + let caller = Address::ZERO; + let mut db = InMemoryDB::default(); + db.insert_account_info( + caller, + AccountInfo { + balance: U256::from(151), + ..Default::default() + }, + ); + let ctx = Context::op() + .with_db(db) + .with_chain(L1BlockInfo { + operator_fee_scalar: Some(U256::from(10_000_000)), + operator_fee_constant: Some(U256::from(50)), + ..Default::default() + }) + .modify_cfg_chained(|cfg| cfg.spec = OpSpecId::ISTHMUS.into()) + .modify_tx_chained(|tx| { + tx.base.gas_limit = 10; + tx.enveloped_tx = Some(bytes!("FACADE")); + }); + + let mut evm = ctx.build_op(); + let handler = OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<_, _, _>>::new(); + + // operator fee cost is operator_fee_scalar * gas_limit / 1e6 + operator_fee_constant + // 10_000_000 * 10 / 1_000_000 + 50 = 150 + handler.deduct_caller(&mut evm).unwrap(); + + // Check the account balance is updated. + let account = evm.ctx().journal().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/optimism/src/l1block.rs b/crates/optimism/src/l1block.rs index 0457b058ea..f6f64412c7 100644 --- a/crates/optimism/src/l1block.rs +++ b/crates/optimism/src/l1block.rs @@ -1,46 +1,20 @@ -use crate::{transaction::estimate_tx_compressed_size, OpSpecId}; +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_SCALARS_SLOT, OPERATOR_FEE_SCALAR_DECIMAL, OPERATOR_FEE_SCALAR_OFFSET, + ZERO_BYTE_COST, + }, + transaction::estimate_tx_compressed_size, + OpSpec, OpSpecId, +}; use core::ops::Mul; use revm::{ - database_interface::Database, - primitives::{address, Address, U256}, + database_interface::Database, interpreter::Gas, primitives::U256, specification::hardfork::SpecId, }; -use super::OpSpec; - -pub const ZERO_BYTE_COST: u64 = 4; -pub const NON_ZERO_BYTE_COST: u64 = 16; - -/// The two 4-byte Ecotone fee scalar values are packed into the same storage slot as the 8-byte sequence number. -/// Byte offset within the storage slot of the 4-byte baseFeeScalar attribute. -pub const BASE_FEE_SCALAR_OFFSET: usize = 16; -/// The two 4-byte Ecotone fee scalar values are packed into the same storage slot as the 8-byte sequence number. -/// Byte offset within the storage slot of the 4-byte blobBaseFeeScalar attribute. -pub const BLOB_BASE_FEE_SCALAR_OFFSET: usize = 20; - -pub const L1_BASE_FEE_SLOT: U256 = U256::from_limbs([1u64, 0, 0, 0]); -pub const L1_OVERHEAD_SLOT: U256 = U256::from_limbs([5u64, 0, 0, 0]); -pub const L1_SCALAR_SLOT: U256 = U256::from_limbs([6u64, 0, 0, 0]); - -/// [ECOTONE_L1_BLOB_BASE_FEE_SLOT] was added in the Ecotone upgrade and stores the L1 blobBaseFee attribute. -pub const ECOTONE_L1_BLOB_BASE_FEE_SLOT: U256 = U256::from_limbs([7u64, 0, 0, 0]); - -/// As of the ecotone upgrade, this storage slot stores the 32-bit basefeeScalar and blobBaseFeeScalar attributes at -/// offsets [BASE_FEE_SCALAR_OFFSET] and [BLOB_BASE_FEE_SCALAR_OFFSET] respectively. -pub const ECOTONE_L1_FEE_SCALARS_SLOT: U256 = U256::from_limbs([3u64, 0, 0, 0]); - -/// An empty 64-bit set of scalar values. -const EMPTY_SCALARS: [u8; 8] = [0u8; 8]; - -/// The address of L1 fee recipient. -pub const L1_FEE_RECIPIENT: Address = address!("420000000000000000000000000000000000001A"); - -/// The address of the base fee recipient. -pub const BASE_FEE_RECIPIENT: Address = address!("4200000000000000000000000000000000000019"); - -/// The address of the L1Block contract. -pub const L1_BLOCK_CONTRACT: Address = address!("4200000000000000000000000000000000000015"); - /// L1 block info /// /// We can extract L1 epoch data from each L2 block, by looking at the `setL1BlockValues` @@ -60,12 +34,16 @@ pub struct L1BlockInfo { pub l1_fee_overhead: Option, /// The current L1 fee scalar. pub l1_base_fee_scalar: U256, - /// The current L1 blob base fee. None if Ecotone is not activated, except if `empty_scalars` is `true`. + /// The current L1 blob base fee. None if Ecotone is not activated, except if `empty_ecotone_scalars` is `true`. pub l1_blob_base_fee: Option, /// The current L1 blob base fee scalar. None if Ecotone is not activated. pub l1_blob_base_fee_scalar: Option, + /// The current L1 blob base fee. None if Isthmus is not activated, except if `empty_ecotone_scalars` is `true`. + pub operator_fee_scalar: Option, + /// The current L1 blob base fee scalar. None if Isthmus is not activated. + pub operator_fee_constant: Option, /// True if Ecotone is activated, but the L1 fee scalars have not yet been set. - pub(crate) empty_scalars: bool, + pub(crate) empty_ecotone_scalars: bool, } impl L1BlockInfo { @@ -104,23 +82,100 @@ impl L1BlockInfo { ); // 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_scalars` is true, as it was deprecated in Ecotone. - let empty_scalars = l1_blob_base_fee.is_zero() + // 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_scalars + let l1_fee_overhead = empty_ecotone_scalars .then(|| db.storage(L1_BLOCK_CONTRACT, L1_OVERHEAD_SLOT)) .transpose()?; - Ok(L1BlockInfo { - 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_scalars, - l1_fee_overhead, - }) + 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 { + 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), + }) + } else { + // Pre-isthmus L1 block info + Ok(L1BlockInfo { + 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() + }) + } + } + } + + /// 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, spec_id: OpSpec) -> U256 { + // If the input is a deposit transaction or empty, the default value is zero. + if input.is_empty() || input.first() == Some(&0x7F) { + return U256::ZERO; } + if !spec_id.is_enabled_in(OpSpecId::ISTHMUS) { + return U256::ZERO; + } + let operator_fee_scalar = self + .operator_fee_scalar + .expect("Missing operator fee scalar for isthmus L1 Block"); + let operator_fee_constant = self + .operator_fee_constant + .expect("Missing operator fee constant for isthmus L1 Block"); + + let product = gas_limit.saturating_mul(operator_fee_scalar) + / (U256::from(OPERATOR_FEE_SCALAR_DECIMAL)); + + product.saturating_add(operator_fee_constant) + } + + /// Calculate the operator fee for executing this transaction. + /// + /// Introduced in isthmus. Prior to isthmus, the operator fee is always zero. + pub fn operator_fee_refund(&self, gas: &Gas, spec_id: OpSpec) -> U256 { + if !spec_id.is_enabled_in(OpSpecId::ISTHMUS) { + return U256::ZERO; + } + + let operator_fee_scalar = self + .operator_fee_scalar + .expect("Missing operator fee scalar for isthmus L1 Block"); + + // We're computing the difference between two operator fees, so no need to include the + // constant. + + operator_fee_scalar.saturating_mul(U256::from(gas.remaining() + gas.refunded() as u64)) + / (U256::from(OPERATOR_FEE_SCALAR_DECIMAL)) } /// Calculate the data gas for posting the transaction on L1. Calldata costs 16 gas per byte @@ -202,7 +257,7 @@ impl L1BlockInfo { // There is an edgecase where, for the very first Ecotone block (unless it is activated at Genesis), we must // use the Bedrock cost function. To determine if this is the case, we can check if the Ecotone parameters are // unset. - if self.empty_scalars { + if self.empty_ecotone_scalars { return self.calculate_tx_l1_cost_bedrock(input, spec_id); } @@ -360,7 +415,7 @@ mod tests { assert_eq!(gas_cost, U256::ZERO); // If the scalars are empty, the bedrock cost function should be used. - l1_block_info.empty_scalars = true; + l1_block_info.empty_ecotone_scalars = true; let input = bytes!("FACADE"); let gas_cost = l1_block_info.calculate_tx_l1_cost(&input, OpSpecId::ECOTONE.into()); assert_eq!(gas_cost, U256::from(1048)); @@ -491,4 +546,21 @@ mod tests { assert_eq!(l1_fee, expected_l1_fee) } + + #[test] + fn test_operator_fee_refund() { + let gas = Gas::new(50000); + + let l1_block_info = L1BlockInfo { + l1_base_fee: U256::from(1055991687), + l1_base_fee_scalar: U256::from(5227), + operator_fee_scalar: Some(U256::from(2000)), + operator_fee_constant: Some(U256::from(5)), + ..Default::default() + }; + + let refunded = l1_block_info.operator_fee_refund(&gas, OpSpecId::ISTHMUS.into()); + + assert_eq!(refunded, U256::from(100)) + } } diff --git a/crates/optimism/src/lib.rs b/crates/optimism/src/lib.rs index dd2d917cca..ec5209c116 100644 --- a/crates/optimism/src/lib.rs +++ b/crates/optimism/src/lib.rs @@ -7,6 +7,7 @@ extern crate alloc as std; pub mod api; pub mod bn128; +pub mod constants; pub mod evm; pub mod fast_lz; pub mod handler; @@ -20,7 +21,7 @@ pub use api::{ default_ctx::DefaultOp, }; pub use evm::OpEvm; -pub use l1block::{L1BlockInfo, BASE_FEE_RECIPIENT, L1_BLOCK_CONTRACT, L1_FEE_RECIPIENT}; +pub use l1block::L1BlockInfo; pub use result::OpHaltReason; pub use spec::*; pub use transaction::{error::OpTransactionError, estimate_tx_compressed_size, OpTransaction};