kona_protocol/
fee.rs

1//! This module contains the L1 block fee calculation function.
2
3use alloy_primitives::U256;
4use core::ops::Mul;
5
6/// Re-export the fastlz compression length calculation function.
7pub use op_alloy_flz::flz_compress_len;
8
9/// Cost per byte in calldata
10const ZERO_BYTE_COST: u64 = 4;
11const NON_ZERO_BYTE_COST: u64 = 16;
12
13/// Calculate the data gas for posting the transaction on L1.
14///
15/// In bedrock, calldata costs 16 gas per non-zero byte and 4 gas per zero byte, with
16/// an extra 68 non-zero bytes were included in the rollup data costs to account for the empty
17/// signature.
18pub fn data_gas_bedrock(input: &[u8]) -> U256 {
19    data_gas_regolith(input) + U256::from(NON_ZERO_BYTE_COST).mul(U256::from(68))
20}
21
22/// Calculate the data gas for posting the transaction on L1.
23///
24/// In regolith, calldata costs 16 gas per non-zero byte and 4 gas per zero byte
25pub fn data_gas_regolith(input: &[u8]) -> U256 {
26    let rollup_data_gas_cost = U256::from(input.iter().fold(0, |acc, byte| {
27        acc + if *byte == 0x00 { ZERO_BYTE_COST } else { NON_ZERO_BYTE_COST }
28    }));
29
30    rollup_data_gas_cost
31}
32
33/// Calculate the data gas for posting the transaction on L1.
34///
35/// In fjord, Calldata costs 16 gas per byte after compression.
36pub fn data_gas_fjord(input: &[u8]) -> U256 {
37    let estimated_size = U256::from(tx_estimated_size_fjord(input));
38    estimated_size
39        .saturating_mul(U256::from(NON_ZERO_BYTE_COST))
40        .wrapping_div(U256::from(1_000_000))
41}
42
43/// Calculate the estimated compressed transaction size in bytes, scaled by 1e6.
44/// This value is computed based on the following formula:
45/// max(minTransactionSize, intercept + fastlzCoef*fastlzSize)
46pub fn tx_estimated_size_fjord(input: &[u8]) -> u64 {
47    let fastlz_size = flz_compress_len(input) as u64;
48
49    fastlz_size.saturating_mul(836_500).saturating_sub(42_585_600).max(100_000_000)
50}
51
52/// Calculates the bedrock tx cost given the rollup data gas cost and the following parameters.
53/// - `l1_fee_overhead`: The L1 fee overhead.
54/// - `base_fee`: The base fee.
55/// - `l1_fee_scalar`: The L1 fee scalar.
56///
57/// This method is used internally in tx cost methods.
58fn bedrock_tx_cost(
59    rollup_data_gas_cost: U256,
60    l1_fee_overhead: U256,
61    base_fee: U256,
62    l1_fee_scalar: U256,
63) -> U256 {
64    rollup_data_gas_cost
65        .saturating_add(l1_fee_overhead)
66        .saturating_mul(base_fee)
67        .saturating_mul(l1_fee_scalar)
68        .wrapping_div(U256::from(1_000_000)) // normalize by 1e6
69}
70
71/// Calculate the gas cost of a transaction based on L1 block data posted on L2 post-ecotone.
72/// This is a special case where the gas cost function uses the bedrock gas cost function,
73/// but the data gas is calculated using the ecotone data gas function.
74pub fn calculate_tx_l1_cost_bedrock_empty_scalars(
75    input: &[u8],
76    l1_fee_overhead: U256,
77    base_fee: U256,
78    l1_fee_scalar: U256,
79) -> U256 {
80    if input.is_empty() || input.first() == Some(&0x7F) {
81        return U256::ZERO;
82    }
83
84    let rollup_data_gas_cost = data_gas_regolith(input);
85
86    bedrock_tx_cost(rollup_data_gas_cost, l1_fee_overhead, base_fee, l1_fee_scalar)
87}
88
89/// Calculate the gas cost of a transaction based on L1 block data posted on L2 post-bedrock.
90pub fn calculate_tx_l1_cost_bedrock(
91    input: &[u8],
92    l1_fee_overhead: U256,
93    base_fee: U256,
94    l1_fee_scalar: U256,
95) -> U256 {
96    if input.is_empty() || input.first() == Some(&0x7F) {
97        return U256::ZERO;
98    }
99
100    let rollup_data_gas_cost = data_gas_bedrock(input);
101
102    bedrock_tx_cost(rollup_data_gas_cost, l1_fee_overhead, base_fee, l1_fee_scalar)
103}
104
105/// Calculate the gas cost of a transaction based on L1 block data posted on L2 post-regolith.
106pub fn calculate_tx_l1_cost_regolith(
107    input: &[u8],
108    l1_fee_overhead: U256,
109    base_fee: U256,
110    l1_fee_scalar: U256,
111) -> U256 {
112    if input.is_empty() || input.first() == Some(&0x7F) {
113        return U256::ZERO;
114    }
115
116    let rollup_data_gas_cost = data_gas_regolith(input);
117
118    bedrock_tx_cost(rollup_data_gas_cost, l1_fee_overhead, base_fee, l1_fee_scalar)
119}
120
121/// Calculate the gas cost of a transaction based on L1 block data posted on L2, post-Ecotone.
122///
123/// L1 cost function:
124/// `(calldataGas/16)*(l1BaseFee*16*l1BaseFeeScalar + l1BlobBaseFee*l1BlobBaseFeeScalar)/1e6`
125///
126/// We divide "calldataGas" by 16 to change from units of calldata gas to "estimated # of bytes when
127/// compressed". Known as "compressedTxSize" in the spec.
128///
129/// Function is actually computed as follows for better precision under integer arithmetic:
130/// `calldataGas*(l1BaseFee*16*l1BaseFeeScalar + l1BlobBaseFee*l1BlobBaseFeeScalar)/16e6`
131pub fn calculate_tx_l1_cost_ecotone(
132    input: &[u8],
133    base_fee: U256,
134    base_fee_scalar: U256,
135    blob_base_fee: U256,
136    blob_base_fee_scalar: U256,
137) -> U256 {
138    if input.is_empty() || input.first() == Some(&0x7F) {
139        return U256::ZERO;
140    }
141
142    let rollup_data_gas_cost = data_gas_regolith(input);
143    let l1_fee_scaled = calculate_l1_fee_scaled_ecotone(
144        base_fee,
145        base_fee_scalar,
146        blob_base_fee,
147        blob_base_fee_scalar,
148    );
149
150    l1_fee_scaled
151        .saturating_mul(rollup_data_gas_cost)
152        .wrapping_div(U256::from(1_000_000 * NON_ZERO_BYTE_COST))
153}
154
155/// Calculate the gas cost of a transaction based on L1 block data posted on L2, post-Fjord.
156///
157/// L1 cost function:
158/// `estimatedSize*(baseFeeScalar*l1BaseFee*16 + blobFeeScalar*l1BlobBaseFee)/1e12`
159pub fn calculate_tx_l1_cost_fjord(
160    input: &[u8],
161    base_fee: U256,
162    base_fee_scalar: U256,
163    blob_base_fee: U256,
164    blob_base_fee_scalar: U256,
165) -> U256 {
166    if input.is_empty() || input.first() == Some(&0x7F) {
167        return U256::ZERO;
168    }
169
170    let l1_fee_scaled = calculate_l1_fee_scaled_ecotone(
171        base_fee,
172        base_fee_scalar,
173        blob_base_fee,
174        blob_base_fee_scalar,
175    );
176    let estimated_size = U256::from(tx_estimated_size_fjord(input));
177
178    estimated_size.saturating_mul(l1_fee_scaled).wrapping_div(U256::from(1_000_000_000_000u64))
179}
180
181// l1BaseFee*16*l1BaseFeeScalar + l1BlobBaseFee*l1BlobBaseFeeScalar
182fn calculate_l1_fee_scaled_ecotone(
183    base_fee: U256,
184    base_fee_scalar: U256,
185    blob_base_fee: U256,
186    blob_base_fee_scalar: U256,
187) -> U256 {
188    let calldata_cost_per_byte: U256 =
189        base_fee.saturating_mul(U256::from(NON_ZERO_BYTE_COST)).saturating_mul(base_fee_scalar);
190    let blob_cost_per_byte = blob_base_fee.saturating_mul(blob_base_fee_scalar);
191
192    U256::from(calldata_cost_per_byte).saturating_add(blob_cost_per_byte)
193}
194
195#[cfg(test)]
196mod tests {
197    use super::*;
198    use alloy_primitives::{bytes, hex};
199
200    #[test]
201    fn test_data_gas_bedrock() {
202        // 0xFACADE = 6 nibbles = 3 bytes
203        // 0xFACADE = 1111 1010 . 1100 1010 . 1101 1110
204        let input_1 = bytes!("FACADE");
205
206        // 0xFA00CA00DE = 10 nibbles = 5 bytes
207        // 0xFA00CA00DE = 1111 1010 . 0000 0000 . 1100 1010 . 0000 0000 . 1101 1110
208        let input_2 = bytes!("FA00CA00DE");
209
210        // Pre-regolith (ie bedrock) has an extra 68 non-zero bytes
211        // gas cost = 3 non-zero bytes * NON_ZERO_BYTE_COST + NON_ZERO_BYTE_COST * 68
212        // gas cost = 3 * 16 + 68 * 16 = 1136
213        let bedrock_data_gas = data_gas_bedrock(&input_1);
214        assert_eq!(bedrock_data_gas, U256::from(1136));
215
216        // Pre-regolith (ie bedrock) has an extra 68 non-zero bytes
217        // gas cost = 3 non-zero * NON_ZERO_BYTE_COST + 2 * ZERO_BYTE_COST + NON_ZERO_BYTE_COST * 68
218        // gas cost = 3 * 16 + 2 * 4 + 68 * 16 = 1144
219        let bedrock_data_gas = data_gas_bedrock(&input_2);
220        assert_eq!(bedrock_data_gas, U256::from(1144));
221    }
222
223    #[test]
224    fn test_data_gas_regolith() {
225        // 0xFACADE = 6 nibbles = 3 bytes
226        // 0xFACADE = 1111 1010 . 1100 1010 . 1101 1110
227        let input_1 = bytes!("FACADE");
228
229        // 0xFA00CA00DE = 10 nibbles = 5 bytes
230        // 0xFA00CA00DE = 1111 1010 . 0000 0000 . 1100 1010 . 0000 0000 . 1101 1110
231        let input_2 = bytes!("FA00CA00DE");
232
233        // gas cost = 3 non-zero bytes * NON_ZERO_BYTE_COST
234        // gas cost = 3 * 16 = 48
235        let bedrock_data_gas = data_gas_regolith(&input_1);
236        assert_eq!(bedrock_data_gas, U256::from(48));
237
238        // gas cost = 3 non-zero * NON_ZERO_BYTE_COST + 2 * ZERO_BYTE_COST
239        // gas cost = 3 * 16 + 2 * 4 = 56
240        let bedrock_data_gas = data_gas_regolith(&input_2);
241        assert_eq!(bedrock_data_gas, U256::from(56));
242    }
243
244    #[test]
245    fn test_data_gas_fjord() {
246        // 0xFACADE = 6 nibbles = 3 bytes
247        // 0xFACADE = 1111 1010 . 1100 1010 . 1101 1110
248        let input_1 = bytes!("FACADE");
249
250        // 0xFA00CA00DE = 10 nibbles = 5 bytes
251        // 0xFA00CA00DE = 1111 1010 . 0000 0000 . 1100 1010 . 0000 0000 . 1101 1110
252        let input_2 = bytes!("FA00CA00DE");
253
254        // Fjord has a minimum compressed size of 100 bytes
255        // gas cost = 100 * 16 = 1600
256        let fjord_data_gas = data_gas_fjord(&input_1);
257        assert_eq!(fjord_data_gas, U256::from(1600));
258
259        // Fjord has a minimum compressed size of 100 bytes
260        // gas cost = 100 * 16 = 1600
261        let fjord_data_gas = data_gas_fjord(&input_2);
262        assert_eq!(fjord_data_gas, U256::from(1600));
263    }
264
265    #[test]
266    fn test_calculate_tx_l1_cost_bedrock() {
267        // let mut l1_block_bedrock = get_default_bedrock_l1_info();
268        let base_fee = U256::from(1_000);
269        let l1_fee_overhead = U256::from(1_000);
270        let l1_fee_scalar = U256::from(1_000);
271
272        // calldataGas * (l1BaseFee * 16 * l1BaseFeeScalar + l1BlobBaseFee * l1BlobBaseFeeScalar) /
273        // (16 * 1e6) = (16 * 3 + 16 * 68 + 1000) * 1000 * 1000 / (1_000_000)
274        // = 2136
275        let input = bytes!("FACADE");
276        let gas_cost =
277            calculate_tx_l1_cost_bedrock(&input, l1_fee_overhead, base_fee, l1_fee_scalar);
278        assert_eq!(gas_cost, U256::from(2136));
279
280        // Zero rollup data gas cost should result in zero
281        let input = bytes!("");
282        let gas_cost =
283            calculate_tx_l1_cost_bedrock(&input, l1_fee_overhead, base_fee, l1_fee_scalar);
284        assert_eq!(gas_cost, U256::ZERO);
285
286        // Deposit transactions with the EIP-2718 type of 0x7F should result in zero
287        let input = bytes!("7FFACADE");
288        let gas_cost =
289            calculate_tx_l1_cost_bedrock(&input, l1_fee_overhead, base_fee, l1_fee_scalar);
290        assert_eq!(gas_cost, U256::ZERO);
291    }
292
293    #[test]
294    fn test_calculate_tx_l1_cost_regolith() {
295        // let mut l1_block_bedrock = get_default_bedrock_l1_info();
296        let base_fee = U256::from(1_000);
297        let l1_fee_overhead = U256::from(1_000);
298        let l1_fee_scalar = U256::from(1_000);
299
300        // calldataGas * (l1BaseFee * 16 * l1BaseFeeScalar + l1BlobBaseFee * l1BlobBaseFeeScalar) /
301        // (16 * 1e6) = (16 * 3 + 1000) * 1000 * 1000 / (1_000_000)
302        // = 1048
303        let input = bytes!("FACADE");
304        let gas_cost =
305            calculate_tx_l1_cost_regolith(&input, l1_fee_overhead, base_fee, l1_fee_scalar);
306        assert_eq!(gas_cost, U256::from(1048));
307
308        // Zero rollup data gas cost should result in zero
309        let input = bytes!("");
310        let gas_cost =
311            calculate_tx_l1_cost_regolith(&input, l1_fee_overhead, base_fee, l1_fee_scalar);
312        assert_eq!(gas_cost, U256::ZERO);
313
314        // Deposit transactions with the EIP-2718 type of 0x7F should result in zero
315        let input = bytes!("7FFACADE");
316        let gas_cost =
317            calculate_tx_l1_cost_regolith(&input, l1_fee_overhead, base_fee, l1_fee_scalar);
318        assert_eq!(gas_cost, U256::ZERO);
319    }
320
321    #[test]
322    fn test_calculate_tx_l1_cost_ecotone() {
323        let base_fee = U256::from(1_000);
324        let blob_base_fee = U256::from(1_000);
325        let blob_base_fee_scalar = U256::from(1_000);
326        let base_fee_scalar = U256::from(1_000);
327
328        // calldataGas * (l1BaseFee * 16 * l1BaseFeeScalar + l1BlobBaseFee * l1BlobBaseFeeScalar) /
329        // (16 * 1e6) = (16 * 3) * (1000 * 16 * 1000 + 1000 * 1000) / (16 * 1e6)
330        // = 51
331        let input = bytes!("FACADE");
332        let gas_cost = calculate_tx_l1_cost_ecotone(
333            &input,
334            base_fee,
335            base_fee_scalar,
336            blob_base_fee,
337            blob_base_fee_scalar,
338        );
339        assert_eq!(gas_cost, U256::from(51));
340
341        // Zero rollup data gas cost should result in zero
342        let input = bytes!("");
343        let gas_cost = calculate_tx_l1_cost_ecotone(
344            &input,
345            base_fee,
346            base_fee_scalar,
347            blob_base_fee,
348            blob_base_fee_scalar,
349        );
350        assert_eq!(gas_cost, U256::ZERO);
351
352        // Deposit transactions with the EIP-2718 type of 0x7F should result in zero
353        let input = bytes!("7FFACADE");
354        let gas_cost = calculate_tx_l1_cost_ecotone(
355            &input,
356            base_fee,
357            base_fee_scalar,
358            blob_base_fee,
359            blob_base_fee_scalar,
360        );
361        assert_eq!(gas_cost, U256::ZERO);
362    }
363
364    #[test]
365    fn test_calculate_tx_l1_cost_fjord() {
366        // l1FeeScaled = baseFeeScalar*l1BaseFee*16 + blobFeeScalar*l1BlobBaseFee
367        //             = 1000 * 1000 * 16 + 1000 * 1000
368        //             = 17e6
369        let base_fee = U256::from(1_000);
370        let blob_base_fee = U256::from(1_000);
371        let blob_base_fee_scalar = U256::from(1_000);
372        let base_fee_scalar = U256::from(1_000);
373
374        // fastLzSize = 4
375        // estimatedSize = max(minTransactionSize, intercept + fastlzCoef*fastlzSize)
376        //               = max(100e6, 836500*4 - 42585600)
377        //               = 100e6
378        let input = bytes!("FACADE");
379        // l1Cost = estimatedSize * l1FeeScaled / 1e12
380        //        = 100e6 * 17 / 1e6
381        //        = 1700
382        let gas_cost = calculate_tx_l1_cost_fjord(
383            &input,
384            base_fee,
385            base_fee_scalar,
386            blob_base_fee,
387            blob_base_fee_scalar,
388        );
389        assert_eq!(gas_cost, U256::from(1700));
390
391        // fastLzSize = 202
392        // estimatedSize = max(minTransactionSize, intercept + fastlzCoef*fastlzSize)
393        //               = max(100e6, 836500*202 - 42585600)
394        //               = 126387400
395        let input = bytes!(
396            "02f901550a758302df1483be21b88304743f94f80e51afb613d764fa61751affd3313c190a86bb870151bd62fd12adb8e41ef24f3f000000000000000000000000000000000000000000000000000000000000006e000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e5831000000000000000000000000000000000000000000000000000000000003c1e5000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000148c89ed219d02f1a5be012c689b4f5b731827bebe000000000000000000000000c001a033fd89cb37c31b2cba46b6466e040c61fc9b2a3675a7f5f493ebd5ad77c497f8a07cdf65680e238392693019b4092f610222e71b7cec06449cb922b93b6a12744e"
397        );
398        // l1Cost = estimatedSize * l1FeeScaled / 1e12
399        //        = 126387400 * 17 / 1e6
400        //        = 2148
401        let gas_cost = calculate_tx_l1_cost_fjord(
402            &input,
403            base_fee,
404            base_fee_scalar,
405            blob_base_fee,
406            blob_base_fee_scalar,
407        );
408        assert_eq!(gas_cost, U256::from(2148));
409
410        // Zero rollup data gas cost should result in zero
411        let input = bytes!("");
412        let gas_cost = calculate_tx_l1_cost_fjord(
413            &input,
414            base_fee,
415            base_fee_scalar,
416            blob_base_fee,
417            blob_base_fee_scalar,
418        );
419        assert_eq!(gas_cost, U256::ZERO);
420
421        // Deposit transactions with the EIP-2718 type of 0x7F should result in zero
422        let input = bytes!("7FFACADE");
423        let gas_cost = calculate_tx_l1_cost_fjord(
424            &input,
425            base_fee,
426            base_fee_scalar,
427            blob_base_fee,
428            blob_base_fee_scalar,
429        );
430        assert_eq!(gas_cost, U256::ZERO);
431    }
432
433    #[test]
434    fn calculate_tx_l1_cost_fjord_actual_block() {
435        // L1 block info for OP mainnet fjord block 124665056
436        // <https://optimistic.etherscan.io/block/124665056>
437        let base_fee = U256::from(1055991687);
438        let blob_base_fee = U256::from(1);
439        let blob_base_fee_scalar = U256::from(1014213);
440        let base_fee_scalar = U256::from(5227);
441
442        // second tx in OP mainnet Fjord block 124665056
443        // <https://optimistic.etherscan.io/tx/0x1059e8004daff32caa1f1b1ef97fe3a07a8cf40508f5b835b66d9420d87c4a4a>
444        const TX: &[u8] = &hex!(
445            "02f904940a8303fba78401d6d2798401db2b6d830493e0943e6f4f7866654c18f536170780344aa8772950b680b904246a761202000000000000000000000000087000a300de7200382b55d40045000000e5d60e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003a0000000000000000000000000000000000000000000000000000000000000022482ad56cb0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000dc6ff44d5d932cbd77b52e5612ba0529dc6226f1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b300000000000000000000000021c4928109acb0659a88ae5329b5374a3024694c0000000000000000000000000000000000000000000000049b9ca9a6943400000000000000000000000000000000000000000000000000000000000000000000000000000000000021c4928109acb0659a88ae5329b5374a3024694c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000024b6b55f250000000000000000000000000000000000000000000000049b9ca9a694340000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000415ec214a3950bea839a7e6fbb0ba1540ac2076acd50820e2d5ef83d0902cdffb24a47aff7de5190290769c4f0a9c6fabf63012986a0d590b1b571547a8c7050ea1b00000000000000000000000000000000000000000000000000000000000000c080a06db770e6e25a617fe9652f0958bd9bd6e49281a53036906386ed39ec48eadf63a07f47cf51a4a40b4494cf26efc686709a9b03939e20ee27e59682f5faa536667e"
446        );
447
448        // l1 gas used for tx and l1 fee for tx, from OP mainnet block scanner
449        // https://optimistic.etherscan.io/tx/0x1059e8004daff32caa1f1b1ef97fe3a07a8cf40508f5b835b66d9420d87c4a4a
450        let expected_data_gas = U256::from(4471);
451        let expected_l1_fee = U256::from_be_bytes(hex!(
452            "00000000000000000000000000000000000000000000000000000005bf1ab43d"
453        ));
454
455        // test
456        let data_gas = data_gas_fjord(TX);
457        assert_eq!(data_gas, expected_data_gas);
458        let l1_fee = calculate_tx_l1_cost_fjord(
459            TX,
460            base_fee,
461            base_fee_scalar,
462            blob_base_fee,
463            blob_base_fee_scalar,
464        );
465        assert_eq!(l1_fee, expected_l1_fee)
466    }
467}