Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(op): backport isthmus operator fee #2059

Merged
merged 3 commits into from
Feb 10, 2025
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
53 changes: 53 additions & 0 deletions crates/optimism/src/constants.rs
Original file line number Diff line number Diff line change
@@ -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");
105 changes: 100 additions & 5 deletions crates/optimism/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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 {
Expand All @@ -158,7 +174,11 @@ where
}
.into());
}
caller_account.info.balance = caller_account.info.balance.saturating_sub(tx_l1_cost);

caller_account.info.balance = caller_account
.info
.balance
.saturating_sub(tx_l1_cost.saturating_add(operator_fee_charge));
}
Ok(())
}
Expand Down Expand Up @@ -229,6 +249,33 @@ where
Ok(())
}

fn reimburse_caller(
&self,
evm: &mut Self::Evm,
exec_result: &mut <Self::Frame as Frame>::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,
Expand Down Expand Up @@ -264,17 +311,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)?;
Expand All @@ -288,6 +340,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(())
}
Expand Down Expand Up @@ -623,6 +682,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;
Expand Down
Loading
Loading