1mod batch_poster;
2
3pub use batch_poster::*;
4
5use alloy_primitives::{Address, U256};
6use revm::Database;
7
8use arb_storage::{
9 Storage, StorageBackedAddress, StorageBackedBigInt, StorageBackedBigUint, StorageBackedInt64,
10 StorageBackedUint64,
11};
12
13const PAY_REWARDS_TO_OFFSET: u64 = 0;
15const EQUILIBRATION_UNITS_OFFSET: u64 = 1;
16const INERTIA_OFFSET: u64 = 2;
17const PER_UNIT_REWARD_OFFSET: u64 = 3;
18const LAST_UPDATE_TIME_OFFSET: u64 = 4;
19const FUNDS_DUE_FOR_REWARDS_OFFSET: u64 = 5;
20const UNITS_SINCE_OFFSET: u64 = 6;
21const PRICE_PER_UNIT_OFFSET: u64 = 7;
22const LAST_SURPLUS_OFFSET: u64 = 8;
23const PER_BATCH_GAS_COST_OFFSET: u64 = 9;
24const AMORTIZED_COST_CAP_BIPS_OFFSET: u64 = 10;
25const L1_FEES_AVAILABLE_OFFSET: u64 = 11;
26const GAS_FLOOR_PER_TOKEN_OFFSET: u64 = 12;
27
28pub const BATCH_POSTER_ADDRESS: Address = Address::new([
30 0xa4, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x65, 0x71, 0x75, 0x65,
31 0x6e, 0x63, 0x65, 0x72,
32]);
33pub const BATCH_POSTER_PAY_TO_ADDRESS: Address = BATCH_POSTER_ADDRESS;
34
35pub const L1_PRICER_FUNDS_POOL_ADDRESS: Address = Address::new([
36 0xa4, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
37 0x00, 0x00, 0x00, 0xf6,
38]);
39
40pub const INITIAL_INERTIA: u64 = 10;
42pub const INITIAL_PER_UNIT_REWARD: u64 = 10;
43pub const INITIAL_EQUILIBRATION_UNITS_V0: u64 = 60 * 16 * 100_000;
44pub const INITIAL_EQUILIBRATION_UNITS_V6: u64 = 16 * 10_000_000;
45pub const INITIAL_PER_BATCH_GAS_COST_V6: i64 = 100_000;
46pub const INITIAL_PER_BATCH_GAS_COST_V12: i64 = 210_000;
47
48pub const TX_DATA_NON_ZERO_GAS_EIP2028: u64 = 16;
50
51pub const ESTIMATION_PADDING_UNITS: u64 = TX_DATA_NON_ZERO_GAS_EIP2028 * 16;
53pub const ESTIMATION_PADDING_BASIS_POINTS: u64 = 100;
54const ONE_IN_BIPS: u64 = 10000;
55
56pub struct L1PricingState<D> {
58 pub backing_storage: Storage<D>,
59 pay_rewards_to: StorageBackedAddress<D>,
60 equilibration_units: StorageBackedBigUint<D>,
61 inertia: StorageBackedUint64<D>,
62 per_unit_reward: StorageBackedUint64<D>,
63 last_update_time: StorageBackedUint64<D>,
64 funds_due_for_rewards: StorageBackedBigInt<D>,
65 units_since_update: StorageBackedUint64<D>,
66 price_per_unit: StorageBackedBigUint<D>,
67 last_surplus: StorageBackedBigInt<D>,
68 per_batch_gas_cost: StorageBackedInt64<D>,
69 amortized_cost_cap_bips: StorageBackedUint64<D>,
70 l1_fees_available: StorageBackedBigUint<D>,
71 gas_floor_per_token: StorageBackedUint64<D>,
72 pub arbos_version: u64,
73}
74
75pub fn initialize_l1_pricing_state<D: Database>(
76 sto: &Storage<D>,
77 rewards_recipient: Address,
78 initial_l1_base_fee: U256,
79) {
80 let state = sto.state_ptr();
81 let base_key = sto.base_key();
82
83 let _ =
84 StorageBackedAddress::new(state, base_key, PAY_REWARDS_TO_OFFSET).set(rewards_recipient);
85 let _ = StorageBackedBigUint::new(state, base_key, EQUILIBRATION_UNITS_OFFSET)
86 .set(U256::from(INITIAL_EQUILIBRATION_UNITS_V6));
87 let _ = StorageBackedUint64::new(state, base_key, INERTIA_OFFSET).set(INITIAL_INERTIA);
88 let _ = StorageBackedUint64::new(state, base_key, PER_UNIT_REWARD_OFFSET)
89 .set(INITIAL_PER_UNIT_REWARD);
90 let _ = StorageBackedUint64::new(state, base_key, LAST_UPDATE_TIME_OFFSET).set(0);
91 let _ = StorageBackedBigInt::new(state, base_key, FUNDS_DUE_FOR_REWARDS_OFFSET).set(U256::ZERO);
92 let _ = StorageBackedUint64::new(state, base_key, UNITS_SINCE_OFFSET).set(0);
93 let _ =
94 StorageBackedBigUint::new(state, base_key, PRICE_PER_UNIT_OFFSET).set(initial_l1_base_fee);
95 let _ = StorageBackedBigInt::new(state, base_key, LAST_SURPLUS_OFFSET).set(U256::ZERO);
96 let _ = StorageBackedInt64::new(state, base_key, PER_BATCH_GAS_COST_OFFSET)
97 .set(INITIAL_PER_BATCH_GAS_COST_V6);
98 let _ = StorageBackedUint64::new(state, base_key, AMORTIZED_COST_CAP_BIPS_OFFSET).set(0);
99 let _ = StorageBackedBigUint::new(state, base_key, L1_FEES_AVAILABLE_OFFSET).set(U256::ZERO);
100 let _ = StorageBackedUint64::new(state, base_key, GAS_FLOOR_PER_TOKEN_OFFSET).set(0);
101
102 initialize_batch_posters_table(sto, BATCH_POSTER_ADDRESS);
103}
104
105pub fn open_l1_pricing_state<D: Database>(
106 sto: Storage<D>,
107 arbos_version: u64,
108) -> L1PricingState<D> {
109 let state = sto.state_ptr();
110 let base_key = sto.base_key();
111
112 L1PricingState {
113 pay_rewards_to: StorageBackedAddress::new(state, base_key, PAY_REWARDS_TO_OFFSET),
114 equilibration_units: StorageBackedBigUint::new(state, base_key, EQUILIBRATION_UNITS_OFFSET),
115 inertia: StorageBackedUint64::new(state, base_key, INERTIA_OFFSET),
116 per_unit_reward: StorageBackedUint64::new(state, base_key, PER_UNIT_REWARD_OFFSET),
117 last_update_time: StorageBackedUint64::new(state, base_key, LAST_UPDATE_TIME_OFFSET),
118 funds_due_for_rewards: StorageBackedBigInt::new(
119 state,
120 base_key,
121 FUNDS_DUE_FOR_REWARDS_OFFSET,
122 ),
123 units_since_update: StorageBackedUint64::new(state, base_key, UNITS_SINCE_OFFSET),
124 price_per_unit: StorageBackedBigUint::new(state, base_key, PRICE_PER_UNIT_OFFSET),
125 last_surplus: StorageBackedBigInt::new(state, base_key, LAST_SURPLUS_OFFSET),
126 per_batch_gas_cost: StorageBackedInt64::new(state, base_key, PER_BATCH_GAS_COST_OFFSET),
127 amortized_cost_cap_bips: StorageBackedUint64::new(
128 state,
129 base_key,
130 AMORTIZED_COST_CAP_BIPS_OFFSET,
131 ),
132 l1_fees_available: StorageBackedBigUint::new(state, base_key, L1_FEES_AVAILABLE_OFFSET),
133 gas_floor_per_token: StorageBackedUint64::new(state, base_key, GAS_FLOOR_PER_TOKEN_OFFSET),
134 backing_storage: sto,
135 arbos_version,
136 }
137}
138
139impl<D: Database> L1PricingState<D> {
140 pub fn open(sto: Storage<D>, arbos_version: u64) -> Self {
141 open_l1_pricing_state(sto, arbos_version)
142 }
143
144 pub fn initialize(sto: &Storage<D>, rewards_recipient: Address, initial_l1_base_fee: U256) {
145 initialize_l1_pricing_state(sto, rewards_recipient, initial_l1_base_fee);
146 }
147
148 pub fn batch_poster_table(&self) -> BatchPostersTable<D> {
149 BatchPostersTable::open(&self.backing_storage)
150 }
151
152 pub fn pay_rewards_to(&self) -> Result<Address, ()> {
155 self.pay_rewards_to.get()
156 }
157
158 pub fn set_pay_rewards_to(&self, addr: Address) -> Result<(), ()> {
159 self.pay_rewards_to.set(addr)
160 }
161
162 pub fn equilibration_units(&self) -> Result<U256, ()> {
163 self.equilibration_units.get()
164 }
165
166 pub fn set_equilibration_units(&self, units: U256) -> Result<(), ()> {
167 self.equilibration_units.set(units)
168 }
169
170 pub fn inertia(&self) -> Result<u64, ()> {
171 self.inertia.get()
172 }
173
174 pub fn set_inertia(&self, val: u64) -> Result<(), ()> {
175 self.inertia.set(val)
176 }
177
178 pub fn per_unit_reward(&self) -> Result<u64, ()> {
179 self.per_unit_reward.get()
180 }
181
182 pub fn set_per_unit_reward(&self, val: u64) -> Result<(), ()> {
183 self.per_unit_reward.set(val)
184 }
185
186 pub fn last_update_time(&self) -> Result<u64, ()> {
187 self.last_update_time.get()
188 }
189
190 pub fn set_last_update_time(&self, time: u64) -> Result<(), ()> {
191 self.last_update_time.set(time)
192 }
193
194 pub fn funds_due_for_rewards(&self) -> Result<U256, ()> {
195 self.funds_due_for_rewards.get_raw()
196 }
197
198 pub fn set_funds_due_for_rewards(&self, val: U256) -> Result<(), ()> {
199 self.funds_due_for_rewards.set(val)
200 }
201
202 pub fn units_since_update(&self) -> Result<u64, ()> {
203 self.units_since_update.get()
204 }
205
206 pub fn set_units_since_update(&self, val: u64) -> Result<(), ()> {
207 self.units_since_update.set(val)
208 }
209
210 pub fn add_to_units_since_update(&self, units: u64) -> Result<(), ()> {
211 let current = self.units_since_update.get().unwrap_or(0);
212 self.units_since_update.set(current.saturating_add(units))
213 }
214
215 pub fn subtract_from_units_since_update(&self, units: u64) -> Result<(), ()> {
216 let current = self.units_since_update.get().unwrap_or(0);
217 self.units_since_update.set(current.saturating_sub(units))
218 }
219
220 pub fn price_per_unit(&self) -> Result<U256, ()> {
221 self.price_per_unit.get()
222 }
223
224 pub fn set_price_per_unit(&self, val: U256) -> Result<(), ()> {
225 self.price_per_unit.set(val)
226 }
227
228 pub fn last_surplus(&self) -> Result<(U256, bool), ()> {
229 self.last_surplus.get_signed()
230 }
231
232 pub fn set_last_surplus(&self, magnitude: U256, negative: bool) -> Result<(), ()> {
233 if self.arbos_version < 7 {
235 return Ok(());
236 }
237 if negative {
238 self.last_surplus.set_negative(magnitude)
239 } else {
240 self.last_surplus.set(magnitude)
241 }
242 }
243
244 pub fn per_batch_gas_cost(&self) -> Result<i64, ()> {
245 self.per_batch_gas_cost.get()
246 }
247
248 pub fn set_per_batch_gas_cost(&self, val: i64) -> Result<(), ()> {
249 self.per_batch_gas_cost.set(val)
250 }
251
252 pub fn amortized_cost_cap_bips(&self) -> Result<u64, ()> {
253 self.amortized_cost_cap_bips.get()
254 }
255
256 pub fn set_amortized_cost_cap_bips(&self, val: u64) -> Result<(), ()> {
257 self.amortized_cost_cap_bips.set(val)
258 }
259
260 pub fn l1_fees_available(&self) -> Result<U256, ()> {
261 self.l1_fees_available.get()
262 }
263
264 pub fn set_l1_fees_available(&self, val: U256) -> Result<(), ()> {
265 self.l1_fees_available.set(val)
266 }
267
268 pub fn add_to_l1_fees_available(&self, amount: U256) -> Result<(), ()> {
269 let current = self.l1_fees_available.get().unwrap_or(U256::ZERO);
270 self.l1_fees_available.set(current.saturating_add(amount))
271 }
272
273 pub fn transfer_from_l1_fees_available(&self, amount: U256) -> Result<U256, ()> {
274 let available = self.l1_fees_available.get().unwrap_or(U256::ZERO);
275 let transfer = amount.min(available);
276 self.l1_fees_available
277 .set(available.saturating_sub(transfer))?;
278 Ok(transfer)
279 }
280
281 pub fn parent_gas_floor_per_token(&self) -> Result<u64, ()> {
282 if self.arbos_version < arb_chainspec::arbos_version::ARBOS_VERSION_50 {
283 return Ok(0);
284 }
285 self.gas_floor_per_token.get()
286 }
287
288 pub fn set_parent_gas_floor_per_token(&self, val: u64) -> Result<(), ()> {
289 if self.arbos_version < arb_chainspec::arbos_version::ARBOS_VERSION_50 {
290 return Err(());
291 }
292 self.gas_floor_per_token.set(val)
293 }
294
295 pub fn get_l1_pricing_surplus(&self) -> Result<(U256, bool), ()> {
298 let l1_fees_available = self.l1_fees_available.get().unwrap_or(U256::ZERO);
299 let bpt = self.batch_poster_table();
300 let total_funds_due = bpt.total_funds_due().unwrap_or(U256::ZERO);
301 let funds_due_for_rewards = self.funds_due_for_rewards().unwrap_or(U256::ZERO);
302
303 let need = total_funds_due.saturating_add(funds_due_for_rewards);
304 if l1_fees_available >= need {
305 Ok((l1_fees_available.saturating_sub(need), false))
306 } else {
307 Ok((need.saturating_sub(l1_fees_available), true))
308 }
309 }
310
311 pub fn get_poster_info(&self, poster: Address) -> Result<(U256, Address), ()> {
312 let bpt = self.batch_poster_table();
313 let state = bpt.open_poster(poster, false)?;
314 let due = state.funds_due()?;
315 let pay_to = state.pay_to()?;
316 Ok((due, pay_to))
317 }
318
319 pub fn poster_data_cost(&self, calldata_units: u64) -> Result<U256, ()> {
320 let price = self.price_per_unit()?;
321 let batch_cost = self.per_batch_gas_cost()?;
322
323 let calldata_cost = price.saturating_mul(U256::from(calldata_units));
324 if batch_cost >= 0 {
325 Ok(calldata_cost.saturating_add(U256::from(batch_cost as u64)))
326 } else {
327 Ok(calldata_cost.saturating_sub(U256::from((-batch_cost) as u64)))
328 }
329 }
330
331 pub fn compute_poster_cost(
335 &self,
336 poster: Address,
337 tx_bytes: &[u8],
338 brotli_compression_level: u64,
339 ) -> Result<(U256, u64), ()> {
340 if poster != BATCH_POSTER_ADDRESS {
341 return Ok((U256::ZERO, 0));
342 }
343 let units = self.get_poster_units_without_cache(tx_bytes, brotli_compression_level);
344 let price = self.price_per_unit()?;
345 Ok((price.saturating_mul(U256::from(units)), units))
346 }
347
348 pub fn poster_data_cost_for_estimation(
353 &self,
354 tx_bytes: &[u8],
355 brotli_compression_level: u64,
356 ) -> Result<(U256, u64), ()> {
357 let raw_units = self.get_poster_units_without_cache(tx_bytes, brotli_compression_level);
358 let padded = (raw_units.saturating_add(ESTIMATION_PADDING_UNITS))
359 .saturating_mul(ONE_IN_BIPS + ESTIMATION_PADDING_BASIS_POINTS)
360 / ONE_IN_BIPS;
361 let price = self.price_per_unit()?;
362 Ok((price.saturating_mul(U256::from(padded)), padded))
363 }
364
365 pub fn get_poster_units_without_cache(
370 &self,
371 tx_bytes: &[u8],
372 brotli_compression_level: u64,
373 ) -> u64 {
374 let l1_bytes = byte_count_after_brotli_level(tx_bytes, brotli_compression_level);
375 TX_DATA_NON_ZERO_GAS_EIP2028.saturating_mul(l1_bytes)
376 }
377
378 pub fn update_for_batch_poster_spending<F>(
380 &self,
381 update_time: u64,
382 current_time: u64,
383 batch_poster: Address,
384 wei_spent: U256,
385 l1_basefee: U256,
386 mut transfer_fn: F,
387 ) -> Result<(), ()>
388 where
389 F: FnMut(Address, Address, U256) -> Result<(), ()>,
390 {
391 if self.arbos_version < 10 {
392 return self._preversion10_update(update_time, current_time, wei_spent, l1_basefee);
393 }
394
395 let bpt = self.batch_poster_table();
396 let poster_state = bpt.open_poster(batch_poster, true)?;
397
398 let funds_due_for_rewards = self.funds_due_for_rewards().unwrap_or(U256::ZERO);
399 let l1_fees_available = self.l1_fees_available.get().unwrap_or(U256::ZERO);
400
401 let mut last_update_time = self.last_update_time().unwrap_or(0);
402 if last_update_time == 0 && update_time > 0 {
403 last_update_time = update_time.saturating_sub(1);
404 }
405
406 if update_time > current_time || update_time < last_update_time {
407 return Err(());
408 }
409
410 let alloc_num = update_time.saturating_sub(last_update_time);
411 let alloc_denom = current_time.saturating_sub(last_update_time);
412 let (alloc_num, alloc_denom) = if alloc_denom == 0 {
413 (1u64, 1u64)
414 } else {
415 (alloc_num, alloc_denom)
416 };
417
418 let units_since = self.units_since_update().unwrap_or(0);
419 let units_allocated = units_since
420 .saturating_mul(alloc_num)
421 .checked_div(alloc_denom)
422 .unwrap_or(0);
423 let _ = self.set_units_since_update(units_since.saturating_sub(units_allocated));
424
425 let mut wei_spent = wei_spent;
426 if self.arbos_version >= 3 {
427 let cap_bips = self.amortized_cost_cap_bips().unwrap_or(0);
428 if cap_bips != 0 {
429 let cap = l1_basefee
430 .saturating_mul(U256::from(units_allocated))
431 .saturating_mul(U256::from(cap_bips))
432 .checked_div(U256::from(10000u64))
433 .unwrap_or(U256::MAX);
434 if cap < wei_spent {
435 wei_spent = cap;
436 }
437 }
438 }
439
440 let due = poster_state.funds_due().unwrap_or(U256::ZERO);
441 let _ = poster_state.set_funds_due(due.saturating_add(wei_spent), &bpt.total_funds_due);
442
443 let per_unit_reward = self.per_unit_reward().unwrap_or(0);
444 let reward_amount = U256::from(units_allocated).saturating_mul(U256::from(per_unit_reward));
445 let _ = self.set_funds_due_for_rewards(funds_due_for_rewards.saturating_add(reward_amount));
446
447 let mut l1_fees = l1_fees_available;
448 let mut payment_for_rewards = reward_amount;
449 if l1_fees < payment_for_rewards {
450 payment_for_rewards = l1_fees;
451 }
452 let _ = self.set_funds_due_for_rewards(
453 self.funds_due_for_rewards()
454 .unwrap_or(U256::ZERO)
455 .saturating_sub(payment_for_rewards),
456 );
457
458 let pay_rewards_to = self.pay_rewards_to().unwrap_or(Address::ZERO);
459 if payment_for_rewards > U256::ZERO {
460 let _ = transfer_fn(
461 L1_PRICER_FUNDS_POOL_ADDRESS,
462 pay_rewards_to,
463 payment_for_rewards,
464 );
465 l1_fees = l1_fees.saturating_sub(payment_for_rewards);
466 let _ = self.set_l1_fees_available(l1_fees);
467 }
468
469 let balance_due = poster_state.funds_due().unwrap_or(U256::ZERO);
470 let mut transfer_amount = balance_due;
471 if l1_fees < transfer_amount {
472 transfer_amount = l1_fees;
473 }
474 if transfer_amount > U256::ZERO {
475 let addr_to_pay = poster_state.pay_to().unwrap_or(batch_poster);
476 let _ = transfer_fn(L1_PRICER_FUNDS_POOL_ADDRESS, addr_to_pay, transfer_amount);
477 l1_fees = l1_fees.saturating_sub(transfer_amount);
478 let _ = self.set_l1_fees_available(l1_fees);
479 let _ = poster_state.set_funds_due(
480 balance_due.saturating_sub(transfer_amount),
481 &bpt.total_funds_due,
482 );
483 }
484
485 let _ = self.set_last_update_time(update_time);
486
487 if units_allocated > 0 {
488 let total_funds_due = bpt.total_funds_due().unwrap_or(U256::ZERO);
489 let fdr = self.funds_due_for_rewards().unwrap_or(U256::ZERO);
490
491 let need_funds = total_funds_due.saturating_add(fdr);
492 let (surplus_mag, surplus_positive) = if l1_fees >= need_funds {
493 (l1_fees.saturating_sub(need_funds), true)
494 } else {
495 (need_funds.saturating_sub(l1_fees), false)
496 };
497
498 let inertia = self.inertia().unwrap_or(INITIAL_INERTIA);
499 let equil_units = self
500 .equilibration_units()
501 .unwrap_or(U256::from(INITIAL_EQUILIBRATION_UNITS_V6));
502 let inertia_units = equil_units
503 .checked_div(U256::from(inertia))
504 .unwrap_or(U256::ZERO);
505 let price = self.price_per_unit().unwrap_or(U256::ZERO);
506
507 let alloc_plus_inert = inertia_units.saturating_add(U256::from(units_allocated));
508 let (old_surplus_mag, old_surplus_neg) = self
509 .last_surplus
510 .get_signed()
511 .unwrap_or((U256::ZERO, false));
512
513 let units_u256 = U256::from(units_allocated);
514
515 let (desired_mag, desired_pos) =
517 signed_div(surplus_mag, !surplus_positive, equil_units);
518
519 let (diff_mag, diff_pos) = signed_sub(
521 surplus_mag,
522 surplus_positive,
523 old_surplus_mag,
524 !old_surplus_neg,
525 );
526 let (actual_mag, actual_pos) = signed_div(diff_mag, diff_pos, units_u256);
527
528 let (change_mag, change_pos) =
530 signed_sub(desired_mag, desired_pos, actual_mag, actual_pos);
531
532 let change_times_units = change_mag.saturating_mul(units_u256);
534 let (price_change, price_change_pos) =
535 signed_div(change_times_units, change_pos, alloc_plus_inert);
536
537 let new_price = if price_change_pos {
538 price.saturating_add(price_change)
539 } else {
540 price.saturating_sub(price_change)
541 };
542
543 let _ = self.set_last_surplus(surplus_mag, !surplus_positive);
544 let _ = self.set_price_per_unit(new_price);
545 }
546
547 Ok(())
548 }
549
550 fn _preversion10_update(
551 &self,
552 _update_time: u64,
553 _current_time: u64,
554 _wei_spent: U256,
555 _l1_basefee: U256,
556 ) -> Result<(), ()> {
557 Ok(())
559 }
560
561 fn _preversion2_update(
562 &self,
563 _update_time: u64,
564 _current_time: u64,
565 _wei_spent: U256,
566 _l1_basefee: U256,
567 ) -> Result<(), ()> {
568 Ok(())
570 }
571}
572
573fn signed_div(mag: U256, positive: bool, divisor: U256) -> (U256, bool) {
578 if divisor.is_zero() {
579 return (U256::ZERO, true);
580 }
581
582 if positive {
583 return (mag / divisor, true);
585 }
586
587 let quotient = mag / divisor;
591 let remainder = mag % divisor;
592 if remainder.is_zero() {
593 if quotient.is_zero() {
594 (U256::ZERO, true) } else {
596 (quotient, false)
597 }
598 } else {
599 (quotient + U256::from(1), false)
601 }
602}
603
604fn signed_sub(a_mag: U256, a_pos: bool, b_mag: U256, b_pos: bool) -> (U256, bool) {
606 let (neg_b_mag, neg_b_pos) = (b_mag, !b_pos);
608 signed_add(a_mag, a_pos, neg_b_mag, neg_b_pos)
609}
610
611fn signed_add(a_mag: U256, a_pos: bool, b_mag: U256, b_pos: bool) -> (U256, bool) {
613 if a_pos == b_pos {
614 (a_mag.saturating_add(b_mag), a_pos)
615 } else if a_mag >= b_mag {
616 (a_mag.saturating_sub(b_mag), a_pos)
617 } else {
618 (b_mag.saturating_sub(a_mag), b_pos)
619 }
620}
621
622pub fn compute_poster_cost_standalone(
627 tx_bytes: &[u8],
628 poster: Address,
629 price_per_unit: U256,
630 brotli_compression_level: u64,
631) -> (U256, u64) {
632 if poster != BATCH_POSTER_ADDRESS {
633 return (U256::ZERO, 0);
634 }
635 let units = poster_units_from_bytes(tx_bytes, brotli_compression_level);
636 (price_per_unit.saturating_mul(U256::from(units)), units)
637}
638
639pub fn poster_units_from_bytes(tx_bytes: &[u8], brotli_compression_level: u64) -> u64 {
641 let l1_bytes = byte_count_after_brotli_level(tx_bytes, brotli_compression_level);
642 TX_DATA_NON_ZERO_GAS_EIP2028.saturating_mul(l1_bytes)
643}
644
645const BROTLI_DEFAULT_WINDOW_SIZE: i32 = 22;
647
648pub fn byte_count_after_brotli_level(data: &[u8], level: u64) -> u64 {
655 let quality = level.min(11) as i32;
656 let params = brotli::enc::BrotliEncoderParams {
657 quality,
658 lgwin: BROTLI_DEFAULT_WINDOW_SIZE,
659 ..Default::default()
660 };
661
662 let mut compressed = Vec::new();
663 let mut input_buffer = data.to_vec();
664 let mut output_buffer = vec![0u8; data.len() + 1024];
665
666 match brotli::BrotliCompressCustomAlloc(
667 &mut std::io::Cursor::new(data),
668 &mut compressed,
669 &mut input_buffer[..],
670 &mut output_buffer[..],
671 ¶ms,
672 brotli::enc::StandardAlloc::default(),
673 ) {
674 Ok(_) => compressed.len() as u64,
675 Err(_) => data.len() as u64,
676 }
677}