Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions crates/op-revm/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
54 changes: 50 additions & 4 deletions crates/op-revm/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -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
};
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -977,7 +983,7 @@ mod tests {
let handler =
OpHandler::<_, EVMError<_, OpTransactionError>, EthFrame<EthInterpreter>>::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)
Expand All @@ -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<EthInterpreter>>::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;
Expand Down
46 changes: 36 additions & 10 deletions crates/op-revm/src/l1block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -154,26 +155,30 @@ 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");
let operator_fee_constant = self
.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)
}
Expand All @@ -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)
}
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion crates/op-revm/src/precompiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
8 changes: 7 additions & 1 deletion crates/op-revm/src/spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ pub enum OpSpecId {
/// Isthmus spec id.
#[default]
ISTHMUS,
/// Jovian spec id.
JOVIAN,
/// Interop spec id.
INTEROP,
/// Osaka spec id.
Expand All @@ -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,
}
}
Expand Down Expand Up @@ -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),
Expand All @@ -86,6 +89,7 @@ impl From<OpSpecId> 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,
}
Expand All @@ -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";
}
Expand Down
Loading