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
9 changes: 7 additions & 2 deletions src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,12 @@ where
};

// Deduct l1 fee from caller.
let tx_l1_cost =
l1_block_info.calculate_tx_l1_cost(rlp_bytes, spec, ctx.tx().compression_ratio());
let tx_l1_cost = l1_block_info.calculate_tx_l1_cost(
rlp_bytes,
spec,
ctx.tx().compression_ratio(),
ctx.tx().compressed_size(),
);
let caller_account = ctx.journal_mut().load_account(caller)?;
if tx_l1_cost.gt(&caller_account.info.balance) {
return Err(InvalidTransaction::LackOfFundForMaxFee {
Expand Down Expand Up @@ -242,6 +246,7 @@ where
rlp_bytes,
ctx.cfg().spec(),
ctx.tx().compression_ratio(),
ctx.tx().compressed_size(),
)
} else {
U256::from(0)
Expand Down
90 changes: 89 additions & 1 deletion src/l1block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,23 +248,111 @@ impl L1BlockInfo {
.wrapping_div(TX_L1_FEE_PRECISION_U256) // account for penalty
}

fn calculate_tx_l1_cost_galileo(
&self,
tx_size: usize, // size of the original rlp-encoded transaction
spec_id: ScrollSpecId,
compressed_size: usize, // size of the compressed rlp-encoded transaction
) -> U256 {
// Post Galileo rollup fee formula:
// rollup_fee(tx) = fee_per_byte * compressed_size(tx) * (1 + penalty(tx)) / PRECISION
//
// Where:
// fee_per_byte = (exec_scalar * l1_base_fee + blob_scalar * l1_blob_base_fee)
// compressed_size(tx) = min(len(zstd(rlp(tx))), len(rlp(tx)))
// penalty(tx) = compressed_size(tx) / penalty_factor

assert!(
compressed_size <= tx_size,
"transaction compressed size {compressed_size} must be less than or equal to the original size {tx_size}"
);

let compressed_size = U256::from(compressed_size);

let exec_scalar = self
.l1_commit_scalar
.unwrap_or_else(|| panic!("missing exec scalar in spec_id={spec_id:?}"));

let blob_scalar = self
.l1_blob_scalar
.unwrap_or_else(|| panic!("missing l1 blob scalar in spec_id={spec_id:?}"));

let l1_blob_base_fee = self
.l1_blob_base_fee
.unwrap_or_else(|| panic!("missing l1 blob base fee in spec_id={spec_id:?}"));

let penalty_factor = match self.penalty_factor {
Some(f) if f == U256::ZERO => U256::ONE, // sanitize zero penalty factor
Some(f) => f,
None => panic!("missing penalty factor in spec_id={spec_id:?}"),
};

// fee_per_byte = (exec_scalar * l1_base_fee) + (blob_scalar * l1_blob_base_fee)
let component_exec = exec_scalar.saturating_mul(self.l1_base_fee);
let component_blob = blob_scalar.saturating_mul(l1_blob_base_fee);
let fee_per_byte = component_exec.saturating_add(component_blob);

// base_term = fee_per_byte * compressed_size
let base_term = fee_per_byte.saturating_mul(compressed_size);

// penalty_term = (base_term * compressed_size) / penalty_factor
let penalty_term = base_term.saturating_mul(compressed_size).wrapping_div(penalty_factor);

// rollup_fee = (base_term + penalty_term) / PRECISION
base_term.saturating_add(penalty_term).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,
compression_ratio: Option<U256>,
compressed_size: Option<usize>,
) -> U256 {
let l1_cost = if !spec_id.is_enabled_in(ScrollSpecId::CURIE) {
self.calculate_tx_l1_cost_shanghai(input, spec_id)
} else if !spec_id.is_enabled_in(ScrollSpecId::FEYNMAN) {
self.calculate_tx_l1_cost_curie(input, spec_id)
} else {
} else if !spec_id.is_enabled_in(ScrollSpecId::GALILEO) {
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)
} else {
let compressed_size = compressed_size
.unwrap_or_else(|| panic!("compressed size should be set in spec_id={spec_id:?}"));
self.calculate_tx_l1_cost_galileo(input.len(), spec_id, compressed_size)
};
l1_cost.min(U64_MAX)
}
}

#[cfg(test)]
mod tests {
use super::*;
use revm::primitives::uint;
use rstest::rstest;

#[rstest]
#[case(50, uint!(171557471810_U256))] // ~0.06 cents
#[case(100, uint!(344821983141_U256))] // ~0.12 cents
#[case(1024, uint!(3854009072433_U256))] // ~1.35 cents
#[case(10 * 1024, uint!(70759382824796_U256))] // ~24.77 cents
#[case(1024 * 1024, uint!(378961881717079120_U256))] // ~1325 USD
fn test_rollup_fee_galileo(#[case] compressed_size: usize, #[case] expected: U256) {
let gpo = L1BlockInfo {
l1_base_fee: uint!(1_000_000_000_U256), // 1 gwei
l1_blob_base_fee: Some(uint!(1_000_000_000_U256)), // 1 gwei
l1_commit_scalar: Some(uint!(2394981796_U256)), // 2.39
l1_blob_scalar: Some(uint!(1019097245_U256)), // 1.02
penalty_factor: Some(uint!(10000_U256)),
..Default::default()
};

let tx_size = 1e10 as usize; // dummy, but make sure this value is larger than the compressed size
let spec = ScrollSpecId::GALILEO;
let actual = gpo.calculate_tx_l1_cost_galileo(tx_size, spec, compressed_size);
assert_eq!(expected, actual);
}
}
27 changes: 24 additions & 3 deletions src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ pub trait ScrollTxTr: Transaction {
/// with posting the transaction on L1.
/// Note: compression_ratio(tx) = size(tx) * 1e9 / size(zstd(tx))
fn compression_ratio(&self) -> Option<U256>;

/// The size of the full rlp-encoded transaction after compression.
/// This is used for calculating the cost associated with posting the transaction on L1.
/// Note: compressed_size(tx) = min(size(zstd(rlp(tx))), size(rlp(tx)))
fn compressed_size(&self) -> Option<usize>;
}

/// A Scroll transaction. Wraps around a base transaction and provides the optional RLPed bytes for
Expand All @@ -36,17 +41,28 @@ pub struct ScrollTransaction<T: Transaction> {
pub base: T,
pub rlp_bytes: Option<Bytes>,
pub compression_ratio: Option<U256>,
pub compressed_size: Option<usize>,
}

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

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

Expand Down Expand Up @@ -137,6 +153,10 @@ impl<T: Transaction> ScrollTxTr for ScrollTransaction<T> {
fn compression_ratio(&self) -> Option<U256> {
self.compression_ratio
}

fn compressed_size(&self) -> Option<usize> {
self.compressed_size
}
}

impl<TX: Transaction + SystemCallTx> SystemCallTx for ScrollTransaction<TX> {
Expand All @@ -151,6 +171,7 @@ impl<TX: Transaction + SystemCallTx> SystemCallTx for ScrollTransaction<TX> {
TX::new_system_tx_with_caller(caller, system_contract_address, data),
None,
None,
None,
)
}
}
Loading