Skip to content

Commit f5ae93d

Browse files
authored
feat: Galileo rollup fee (#65)
* feat: Galileo rollup fee * update * update comment * add test cases * sanitize zero penalty factor * fix ci test run * fix * use usize * update test
1 parent d6b77e2 commit f5ae93d

File tree

3 files changed

+120
-6
lines changed

3 files changed

+120
-6
lines changed

src/handler.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,12 @@ where
102102
};
103103

104104
// Deduct l1 fee from caller.
105-
let tx_l1_cost =
106-
l1_block_info.calculate_tx_l1_cost(rlp_bytes, spec, ctx.tx().compression_ratio());
105+
let tx_l1_cost = l1_block_info.calculate_tx_l1_cost(
106+
rlp_bytes,
107+
spec,
108+
ctx.tx().compression_ratio(),
109+
ctx.tx().compressed_size(),
110+
);
107111
let caller_account = ctx.journal_mut().load_account(caller)?;
108112
if tx_l1_cost.gt(&caller_account.info.balance) {
109113
return Err(InvalidTransaction::LackOfFundForMaxFee {
@@ -242,6 +246,7 @@ where
242246
rlp_bytes,
243247
ctx.cfg().spec(),
244248
ctx.tx().compression_ratio(),
249+
ctx.tx().compressed_size(),
245250
)
246251
} else {
247252
U256::from(0)

src/l1block.rs

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,23 +248,111 @@ impl L1BlockInfo {
248248
.wrapping_div(TX_L1_FEE_PRECISION_U256) // account for penalty
249249
}
250250

251+
fn calculate_tx_l1_cost_galileo(
252+
&self,
253+
tx_size: usize, // size of the original rlp-encoded transaction
254+
spec_id: ScrollSpecId,
255+
compressed_size: usize, // size of the compressed rlp-encoded transaction
256+
) -> U256 {
257+
// Post Galileo rollup fee formula:
258+
// rollup_fee(tx) = fee_per_byte * compressed_size(tx) * (1 + penalty(tx)) / PRECISION
259+
//
260+
// Where:
261+
// fee_per_byte = (exec_scalar * l1_base_fee + blob_scalar * l1_blob_base_fee)
262+
// compressed_size(tx) = min(len(zstd(rlp(tx))), len(rlp(tx)))
263+
// penalty(tx) = compressed_size(tx) / penalty_factor
264+
265+
assert!(
266+
compressed_size <= tx_size,
267+
"transaction compressed size {compressed_size} must be less than or equal to the original size {tx_size}"
268+
);
269+
270+
let compressed_size = U256::from(compressed_size);
271+
272+
let exec_scalar = self
273+
.l1_commit_scalar
274+
.unwrap_or_else(|| panic!("missing exec scalar in spec_id={spec_id:?}"));
275+
276+
let blob_scalar = self
277+
.l1_blob_scalar
278+
.unwrap_or_else(|| panic!("missing l1 blob scalar in spec_id={spec_id:?}"));
279+
280+
let l1_blob_base_fee = self
281+
.l1_blob_base_fee
282+
.unwrap_or_else(|| panic!("missing l1 blob base fee in spec_id={spec_id:?}"));
283+
284+
let penalty_factor = match self.penalty_factor {
285+
Some(f) if f == U256::ZERO => U256::ONE, // sanitize zero penalty factor
286+
Some(f) => f,
287+
None => panic!("missing penalty factor in spec_id={spec_id:?}"),
288+
};
289+
290+
// fee_per_byte = (exec_scalar * l1_base_fee) + (blob_scalar * l1_blob_base_fee)
291+
let component_exec = exec_scalar.saturating_mul(self.l1_base_fee);
292+
let component_blob = blob_scalar.saturating_mul(l1_blob_base_fee);
293+
let fee_per_byte = component_exec.saturating_add(component_blob);
294+
295+
// base_term = fee_per_byte * compressed_size
296+
let base_term = fee_per_byte.saturating_mul(compressed_size);
297+
298+
// penalty_term = (base_term * compressed_size) / penalty_factor
299+
let penalty_term = base_term.saturating_mul(compressed_size).wrapping_div(penalty_factor);
300+
301+
// rollup_fee = (base_term + penalty_term) / PRECISION
302+
base_term.saturating_add(penalty_term).wrapping_div(TX_L1_FEE_PRECISION_U256)
303+
}
304+
251305
/// Calculate the gas cost of a transaction based on L1 block data posted on L2.
252306
pub fn calculate_tx_l1_cost(
253307
&self,
254308
input: &[u8],
255309
spec_id: ScrollSpecId,
256310
compression_ratio: Option<U256>,
311+
compressed_size: Option<usize>,
257312
) -> U256 {
258313
let l1_cost = if !spec_id.is_enabled_in(ScrollSpecId::CURIE) {
259314
self.calculate_tx_l1_cost_shanghai(input, spec_id)
260315
} else if !spec_id.is_enabled_in(ScrollSpecId::FEYNMAN) {
261316
self.calculate_tx_l1_cost_curie(input, spec_id)
262-
} else {
317+
} else if !spec_id.is_enabled_in(ScrollSpecId::GALILEO) {
263318
let compression_ratio = compression_ratio.unwrap_or_else(|| {
264319
panic!("compression ratio should be set in spec_id={spec_id:?}")
265320
});
266321
self.calculate_tx_l1_cost_feynman(input, spec_id, compression_ratio)
322+
} else {
323+
let compressed_size = compressed_size
324+
.unwrap_or_else(|| panic!("compressed size should be set in spec_id={spec_id:?}"));
325+
self.calculate_tx_l1_cost_galileo(input.len(), spec_id, compressed_size)
267326
};
268327
l1_cost.min(U64_MAX)
269328
}
270329
}
330+
331+
#[cfg(test)]
332+
mod tests {
333+
use super::*;
334+
use revm::primitives::uint;
335+
use rstest::rstest;
336+
337+
#[rstest]
338+
#[case(50, uint!(171557471810_U256))] // ~0.06 cents
339+
#[case(100, uint!(344821983141_U256))] // ~0.12 cents
340+
#[case(1024, uint!(3854009072433_U256))] // ~1.35 cents
341+
#[case(10 * 1024, uint!(70759382824796_U256))] // ~24.77 cents
342+
#[case(1024 * 1024, uint!(378961881717079120_U256))] // ~1325 USD
343+
fn test_rollup_fee_galileo(#[case] compressed_size: usize, #[case] expected: U256) {
344+
let gpo = L1BlockInfo {
345+
l1_base_fee: uint!(1_000_000_000_U256), // 1 gwei
346+
l1_blob_base_fee: Some(uint!(1_000_000_000_U256)), // 1 gwei
347+
l1_commit_scalar: Some(uint!(2394981796_U256)), // 2.39
348+
l1_blob_scalar: Some(uint!(1019097245_U256)), // 1.02
349+
penalty_factor: Some(uint!(10000_U256)),
350+
..Default::default()
351+
};
352+
353+
let tx_size = 1e10 as usize; // dummy, but make sure this value is larger than the compressed size
354+
let spec = ScrollSpecId::GALILEO;
355+
let actual = gpo.calculate_tx_l1_cost_galileo(tx_size, spec, compressed_size);
356+
assert_eq!(expected, actual);
357+
}
358+
}

src/transaction.rs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ pub trait ScrollTxTr: Transaction {
2626
/// with posting the transaction on L1.
2727
/// Note: compression_ratio(tx) = size(tx) * 1e9 / size(zstd(tx))
2828
fn compression_ratio(&self) -> Option<U256>;
29+
30+
/// The size of the full rlp-encoded transaction after compression.
31+
/// This is used for calculating the cost associated with posting the transaction on L1.
32+
/// Note: compressed_size(tx) = min(size(zstd(rlp(tx))), size(rlp(tx)))
33+
fn compressed_size(&self) -> Option<usize>;
2934
}
3035

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

4147
impl<T: Transaction> ScrollTransaction<T> {
42-
pub fn new(base: T, rlp_bytes: Option<Bytes>, compression_ratio: Option<U256>) -> Self {
43-
Self { base, rlp_bytes, compression_ratio }
48+
pub fn new(
49+
base: T,
50+
rlp_bytes: Option<Bytes>,
51+
compression_ratio: Option<U256>,
52+
compressed_size: Option<usize>,
53+
) -> Self {
54+
Self { base, rlp_bytes, compression_ratio, compressed_size }
4455
}
4556
}
4657

4758
impl Default for ScrollTransaction<TxEnv> {
4859
fn default() -> Self {
49-
Self { base: TxEnv::default(), rlp_bytes: None, compression_ratio: None }
60+
Self {
61+
base: TxEnv::default(),
62+
rlp_bytes: None,
63+
compression_ratio: None,
64+
compressed_size: None,
65+
}
5066
}
5167
}
5268

@@ -137,6 +153,10 @@ impl<T: Transaction> ScrollTxTr for ScrollTransaction<T> {
137153
fn compression_ratio(&self) -> Option<U256> {
138154
self.compression_ratio
139155
}
156+
157+
fn compressed_size(&self) -> Option<usize> {
158+
self.compressed_size
159+
}
140160
}
141161

142162
impl<TX: Transaction + SystemCallTx> SystemCallTx for ScrollTransaction<TX> {
@@ -151,6 +171,7 @@ impl<TX: Transaction + SystemCallTx> SystemCallTx for ScrollTransaction<TX> {
151171
TX::new_system_tx_with_caller(caller, system_contract_address, data),
152172
None,
153173
None,
174+
None,
154175
)
155176
}
156177
}

0 commit comments

Comments
 (0)