Skip to content
10 changes: 8 additions & 2 deletions src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,10 @@ where
"[SCROLL] Failed to load transaction rlp_bytes.".to_string(),
));
};

// Deduct l1 fee from caller.
let tx_l1_cost = l1_block_info.calculate_tx_l1_cost(rlp_bytes, spec);
let tx_l1_cost =
l1_block_info.calculate_tx_l1_cost(rlp_bytes, spec, ctx.tx().compression_ratio());
let caller_account = ctx.journal().load_account(caller)?;
if tx_l1_cost.gt(&caller_account.info.balance) {
return Err(InvalidTransaction::LackOfFundForMaxFee {
Expand Down Expand Up @@ -212,7 +214,11 @@ where
"[SCROLL] Failed to load transaction rlp_bytes.".to_string(),
));
};
let l1_cost = l1_block_info.calculate_tx_l1_cost(rlp_bytes, ctx.cfg().spec());
let l1_cost = l1_block_info.calculate_tx_l1_cost(
rlp_bytes,
ctx.cfg().spec(),
ctx.tx().compression_ratio(),
);

// reward the beneficiary with the gas fee including the L1 cost of the transaction and mark
// the account as touched.
Expand Down
88 changes: 83 additions & 5 deletions src/l1block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ const NON_ZERO_BYTE_COST: u64 = 16;
const TX_L1_COMMIT_EXTRA_COST: U256 = U256::from_limbs([64u64, 0, 0, 0]);

/// The precision used for L1 fee calculations.
const TX_L1_FEE_PRECISION: U256 = U256::from_limbs([1_000_000_000u64, 0, 0, 0]);
pub const TX_L1_FEE_PRECISION: u64 = 1_000_000_000u64;
pub const TX_L1_FEE_PRECISION_U256: U256 = U256::from_limbs([TX_L1_FEE_PRECISION, 0, 0, 0]);

/// The L1 gas price oracle address.
pub const L1_GAS_PRICE_ORACLE_ADDRESS: Address =
Expand All @@ -37,6 +38,8 @@ const L1_SCALAR_SLOT: U256 = U256::from_limbs([3u64, 0, 0, 0]);
const L1_BLOB_BASE_FEE_SLOT: U256 = U256::from_limbs([5u64, 0, 0, 0]);

/// The L1 commit scalar storage slot.
///
/// Post-FEYNMAN this represents the exec_scalar.
const L1_COMMIT_SCALAR_SLOT: U256 = U256::from_limbs([6u64, 0, 0, 0]);

/// The L1 blob scalar storage slot.
Expand Down Expand Up @@ -128,22 +131,97 @@ impl L1BlockInfo {
tx_l1_gas
.saturating_mul(self.l1_base_fee)
.saturating_mul(self.l1_base_fee_scalar)
.wrapping_div(TX_L1_FEE_PRECISION)
.wrapping_div(TX_L1_FEE_PRECISION_U256)
}

fn calculate_tx_l1_cost_curie(&self, input: &[u8], spec_id: ScrollSpecId) -> U256 {
// "commitScalar * l1BaseFee + blobScalar * _data.length * l1BlobBaseFee"
let blob_gas = self.data_gas(input, spec_id);

self.calldata_gas.unwrap().saturating_add(blob_gas).wrapping_div(TX_L1_FEE_PRECISION)
self.calldata_gas.unwrap().saturating_add(blob_gas).wrapping_div(TX_L1_FEE_PRECISION_U256)
}

fn calculate_tx_l1_cost_feynman(
&self,
input: &[u8],
spec_id: ScrollSpecId,
compression_ratio: U256,
) -> U256 {
// rollup_fee(tx) = compression_factor(tx) * size(tx) * (component_exec + component_blob)
//
// - compression_factor(tx): compression_factor = 1 / compression_ratio, where
// compression_ratio is the estimated compressibility of the signed tx data. The tx is
// eventually a part of a L2 batch that should likely result in a better compression ratio,
// however a conservative estimate is the size of zstd-encoding of the signed tx.
//
// - size(tx): denotes the size of the signed tx.
//
// - component_exec: The component that accounts towards commiting this tx as part of a L2
// batch as well as gas costs for the eventual on-chain proof verification.
// => (compression_scalar + commit_scalar + verification_scalar) * l1_base_fee
// => (exec_scalar) * l1_base_fee
//
// - component_blob: The component that accounts the costs associated with data
// availability, i.e. the costs of posting this tx's data in the EIP-4844 blob.
// => (compression_scalar + blob_scalar) * l1_blob_base_fee
// => (new_blob_scalar) * l1_blob_base_fee
//
// Note that the same slots for L1_COMMIT_SCALAR_SLOT and L1_BLOB_SCALAR_SLOT are
// re-used/updated for the new values post-FEYNMAN.
assert!(
compression_ratio >= TX_L1_FEE_PRECISION_U256,
"transaction compression ratio must be greater or equal to {TX_L1_FEE_PRECISION_U256:?} - compression ratio: {compression_ratio:?}"
);

let component_exec = {
let exec_scalar = self
.l1_commit_scalar
.unwrap_or_else(|| panic!("missing exec scalar in spec_id={:?}", spec_id));
exec_scalar.saturating_mul(self.l1_base_fee)
};
let component_blob = {
let blob_scalar = self
.l1_blob_scalar
.unwrap_or_else(|| panic!("missing l1 blob scalar in spec_id={:?}", spec_id));
let blob_base_fee = self
.l1_blob_base_fee
.unwrap_or_else(|| panic!("missing l1 blob base fee in spec_id={:?}", spec_id));
blob_scalar.saturating_mul(blob_base_fee)
};

// Assume compression_factor = 1 until we have specification for estimating compression
// ratio based on previous finalised batches.
//
// We use the `TX_L1_FEE_PRECISION` to allow fractions. We then divide the overall product
// by the precision value as well.
// let compression_factor = |_input: &[u8]| -> U256 { TX_L1_FEE_PRECISION };

// size(tx) is just the length of the RLP-encoded signed tx data.
let tx_size = |input: &[u8]| -> U256 { U256::from(input.len()) };

tx_size(input)
.saturating_mul(component_exec.saturating_add(component_blob))
.saturating_mul(TX_L1_FEE_PRECISION_U256)
.wrapping_div(compression_ratio)
.wrapping_div(TX_L1_FEE_PRECISION_U256)
}

/// Calculate the gas cost of a transaction based on L1 block data posted on L2.
pub fn calculate_tx_l1_cost(&self, input: &[u8], spec_id: ScrollSpecId) -> U256 {
pub fn calculate_tx_l1_cost(
&self,
input: &[u8],
spec_id: ScrollSpecId,
compression_ratio: Option<U256>,
) -> U256 {
let l1_cost = if !spec_id.is_enabled_in(ScrollSpecId::CURIE) {
self.calculate_tx_l1_cost_shanghai(input, spec_id)
} else {
} else if !spec_id.is_enabled_in(ScrollSpecId::FEYNMAN) {
self.calculate_tx_l1_cost_curie(input, spec_id)
} else {
let compression_ratio = compression_ratio.unwrap_or_else(|| {
panic!("compression ratio should be set in spec_id={:?}", spec_id)
});
self.calculate_tx_l1_cost_feynman(input, spec_id, compression_ratio)
};
l1_cost.min(U64_MAX)
}
Expand Down
15 changes: 12 additions & 3 deletions src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ pub trait ScrollTxTr: Transaction {
/// The RLP encoded transaction bytes which are used to calculate the cost associated with
/// posting the transaction on L1.
fn rlp_bytes(&self) -> Option<&Bytes>;

/// The compression ratio of the transaction which is used to calculate the cost associated
/// with posting the transaction on L1.
fn compression_ratio(&self) -> Option<U256>;
}

/// A Scroll transaction. Wraps around a base transaction and provides the optional RLPed bytes for
Expand All @@ -21,17 +25,18 @@ pub trait ScrollTxTr: Transaction {
pub struct ScrollTransaction<T: Transaction> {
pub base: T,
pub rlp_bytes: Option<Bytes>,
pub compression_ratio: Option<U256>,
}

impl<T: Transaction> ScrollTransaction<T> {
pub fn new(base: T, rlp_bytes: Option<Bytes>) -> Self {
Self { base, rlp_bytes }
pub fn new(base: T, rlp_bytes: Option<Bytes>, compression_ratio: Option<U256>) -> Self {
Self { base, rlp_bytes, compression_ratio }
}
}

impl Default for ScrollTransaction<TxEnv> {
fn default() -> Self {
Self { base: TxEnv::default(), rlp_bytes: None }
Self { base: TxEnv::default(), rlp_bytes: None, compression_ratio: None }
}
}

Expand Down Expand Up @@ -114,4 +119,8 @@ impl<T: Transaction> ScrollTxTr for ScrollTransaction<T> {
fn rlp_bytes(&self) -> Option<&Bytes> {
self.rlp_bytes.as_ref()
}

fn compression_ratio(&self) -> Option<U256> {
self.compression_ratio
}
}