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
8 changes: 8 additions & 0 deletions crates/op-revm/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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];

Expand Down
104 changes: 102 additions & 2 deletions crates/op-revm/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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<EthInterpreter>>::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),
}
);
}
Expand Down
186 changes: 101 additions & 85 deletions crates/op-revm/src/l1block.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -49,13 +49,84 @@ pub struct L1BlockInfo {
pub operator_fee_scalar: Option<U256>,
/// The current L1 blob base fee scalar. None if Isthmus is not activated.
pub operator_fee_constant: Option<U256>,
/// 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<u16>,
/// 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<U256>,
}

impl L1BlockInfo {
/// Try to fetch the L1 block info from the database, post-Jovian.
fn try_fetch_jovian<DB: Database>(&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<DB: Database>(&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<DB: Database>(&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: Database>(
db: &mut DB,
Expand All @@ -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.
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::JOVIAN | OpSpecId::INTEROP | OpSpecId::OSAKA => isthmus(),
OpSpecId::ISTHMUS | OpSpecId::INTEROP | OpSpecId::OSAKA | OpSpecId::JOVIAN => isthmus(),
};

Self {
Expand Down
Loading
Loading