1mod batch_poster;
2mod error;
3
4use alloy_primitives::{Address, U256};
5use arb_storage::{
6 Storage, StorageBackedAddress, StorageBackedBigInt, StorageBackedBigUint, StorageBackedInt64,
7 StorageBackedUint64, StorageBackend, SystemStateBackend,
8};
9pub use arbos_types::BATCH_POSTER_ADDRESS;
10pub use batch_poster::*;
11pub use error::L1PricingError;
12
13use crate::util::BalanceError;
14
15pub const PAY_REWARDS_TO_OFFSET: u64 = 0;
17pub const EQUILIBRATION_UNITS_OFFSET: u64 = 1;
18pub const INERTIA_OFFSET: u64 = 2;
19pub const PER_UNIT_REWARD_OFFSET: u64 = 3;
20pub const LAST_UPDATE_TIME_OFFSET: u64 = 4;
21pub const FUNDS_DUE_FOR_REWARDS_OFFSET: u64 = 5;
22pub const UNITS_SINCE_OFFSET: u64 = 6;
23pub const PRICE_PER_UNIT_OFFSET: u64 = 7;
24pub const LAST_SURPLUS_OFFSET: u64 = 8;
25pub const PER_BATCH_GAS_COST_OFFSET: u64 = 9;
26pub const AMORTIZED_COST_CAP_BIPS_OFFSET: u64 = 10;
27pub const L1_FEES_AVAILABLE_OFFSET: u64 = 11;
28pub const GAS_FLOOR_PER_TOKEN_OFFSET: u64 = 12;
29
30pub const BATCH_POSTER_PAY_TO_ADDRESS: Address = BATCH_POSTER_ADDRESS;
32
33pub const L1_PRICER_FUNDS_POOL_ADDRESS: Address = Address::new([
34 0xa4, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
35 0x00, 0x00, 0x00, 0xf6,
36]);
37
38pub const INITIAL_INERTIA: u64 = 10;
40pub const INITIAL_PER_UNIT_REWARD: u64 = 10;
41pub const INITIAL_EQUILIBRATION_UNITS_V0: u64 = 60 * 16 * 100_000;
42pub const INITIAL_EQUILIBRATION_UNITS_V6: u64 = 16 * 10_000_000;
43pub const INITIAL_PER_BATCH_GAS_COST_V6: i64 = 100_000;
44pub const INITIAL_PER_BATCH_GAS_COST_V12: i64 = 210_000;
45
46pub const TX_DATA_NON_ZERO_GAS_EIP2028: u64 = 16;
48
49pub const ESTIMATION_PADDING_UNITS: u64 = TX_DATA_NON_ZERO_GAS_EIP2028 * 16;
51pub const ESTIMATION_PADDING_BASIS_POINTS: u64 = 100;
52const ONE_IN_BIPS: u64 = 10000;
53
54pub struct L1PricingState<'a, D> {
56 pub backing_storage: Storage<'a, D>,
57 pay_rewards_to: StorageBackedAddress,
58 equilibration_units: StorageBackedBigUint,
59 inertia: StorageBackedUint64,
60 per_unit_reward: StorageBackedUint64,
61 last_update_time: StorageBackedUint64,
62 funds_due_for_rewards: StorageBackedBigInt,
63 units_since_update: StorageBackedUint64,
64 price_per_unit: StorageBackedBigUint,
65 last_surplus: StorageBackedBigInt,
66 per_batch_gas_cost: StorageBackedInt64,
67 amortized_cost_cap_bips: StorageBackedUint64,
68 l1_fees_available: StorageBackedBigUint,
69 gas_floor_per_token: StorageBackedUint64,
70 pub arbos_version: u64,
71}
72
73pub fn initialize_l1_pricing_state<D: revm::Database, B: StorageBackend>(
74 sto: &Storage<'_, D>,
75 backend: &mut B,
76 rewards_recipient: Address,
77 initial_l1_base_fee: U256,
78) -> Result<(), L1PricingError> {
79 let base_key = sto.base_key();
80
81 StorageBackedAddress::new(base_key, PAY_REWARDS_TO_OFFSET).set(backend, rewards_recipient)?;
82 StorageBackedBigUint::new(base_key, EQUILIBRATION_UNITS_OFFSET)
83 .set(backend, U256::from(INITIAL_EQUILIBRATION_UNITS_V0))?;
84 StorageBackedUint64::new(base_key, INERTIA_OFFSET).set(backend, INITIAL_INERTIA)?;
85 StorageBackedUint64::new(base_key, PER_UNIT_REWARD_OFFSET)
86 .set(backend, INITIAL_PER_UNIT_REWARD)?;
87 StorageBackedUint64::new(base_key, LAST_UPDATE_TIME_OFFSET).set(backend, 0)?;
88 StorageBackedBigInt::new(base_key, FUNDS_DUE_FOR_REWARDS_OFFSET).set(backend, U256::ZERO)?;
89 StorageBackedUint64::new(base_key, UNITS_SINCE_OFFSET).set(backend, 0)?;
90 StorageBackedBigUint::new(base_key, PRICE_PER_UNIT_OFFSET).set(backend, initial_l1_base_fee)?;
91
92 initialize_batch_posters_table(sto, backend, BATCH_POSTER_ADDRESS)?;
93 Ok(())
94}
95
96pub fn open_l1_pricing_state<D>(sto: Storage<'_, D>, arbos_version: u64) -> L1PricingState<'_, D> {
97 let base_key = sto.base_key();
98
99 L1PricingState {
100 pay_rewards_to: StorageBackedAddress::new(base_key, PAY_REWARDS_TO_OFFSET),
101 equilibration_units: StorageBackedBigUint::new(base_key, EQUILIBRATION_UNITS_OFFSET),
102 inertia: StorageBackedUint64::new(base_key, INERTIA_OFFSET),
103 per_unit_reward: StorageBackedUint64::new(base_key, PER_UNIT_REWARD_OFFSET),
104 last_update_time: StorageBackedUint64::new(base_key, LAST_UPDATE_TIME_OFFSET),
105 funds_due_for_rewards: StorageBackedBigInt::new(base_key, FUNDS_DUE_FOR_REWARDS_OFFSET),
106 units_since_update: StorageBackedUint64::new(base_key, UNITS_SINCE_OFFSET),
107 price_per_unit: StorageBackedBigUint::new(base_key, PRICE_PER_UNIT_OFFSET),
108 last_surplus: StorageBackedBigInt::new(base_key, LAST_SURPLUS_OFFSET),
109 per_batch_gas_cost: StorageBackedInt64::new(base_key, PER_BATCH_GAS_COST_OFFSET),
110 amortized_cost_cap_bips: StorageBackedUint64::new(base_key, AMORTIZED_COST_CAP_BIPS_OFFSET),
111 l1_fees_available: StorageBackedBigUint::new(base_key, L1_FEES_AVAILABLE_OFFSET),
112 gas_floor_per_token: StorageBackedUint64::new(base_key, GAS_FLOOR_PER_TOKEN_OFFSET),
113 backing_storage: sto,
114 arbos_version,
115 }
116}
117
118impl<'a, D> L1PricingState<'a, D> {
119 pub fn open(sto: Storage<'a, D>, arbos_version: u64) -> Self {
120 open_l1_pricing_state(sto, arbos_version)
121 }
122
123 pub fn batch_poster_table(&self) -> BatchPostersTable<'a, D> {
124 BatchPostersTable::open(&self.backing_storage)
125 }
126
127 pub fn pay_rewards_to<B: SystemStateBackend>(
130 &self,
131 backend: &mut B,
132 ) -> Result<Address, L1PricingError> {
133 Ok(self.pay_rewards_to.get(backend)?)
134 }
135
136 pub fn set_pay_rewards_to<B: StorageBackend>(
137 &self,
138 backend: &mut B,
139 addr: Address,
140 ) -> Result<(), L1PricingError> {
141 Ok(self.pay_rewards_to.set(backend, addr)?)
142 }
143
144 pub fn equilibration_units<B: SystemStateBackend>(
145 &self,
146 backend: &mut B,
147 ) -> Result<U256, L1PricingError> {
148 Ok(self.equilibration_units.get(backend)?)
149 }
150
151 pub fn set_equilibration_units<B: StorageBackend>(
152 &self,
153 backend: &mut B,
154 units: U256,
155 ) -> Result<(), L1PricingError> {
156 Ok(self.equilibration_units.set(backend, units)?)
157 }
158
159 pub fn inertia<B: SystemStateBackend>(&self, backend: &mut B) -> Result<u64, L1PricingError> {
160 Ok(self.inertia.get(backend)?)
161 }
162
163 pub fn set_inertia<B: StorageBackend>(
164 &self,
165 backend: &mut B,
166 val: u64,
167 ) -> Result<(), L1PricingError> {
168 Ok(self.inertia.set(backend, val)?)
169 }
170
171 pub fn per_unit_reward<B: SystemStateBackend>(
172 &self,
173 backend: &mut B,
174 ) -> Result<u64, L1PricingError> {
175 Ok(self.per_unit_reward.get(backend)?)
176 }
177
178 pub fn set_per_unit_reward<B: StorageBackend>(
179 &self,
180 backend: &mut B,
181 val: u64,
182 ) -> Result<(), L1PricingError> {
183 Ok(self.per_unit_reward.set(backend, val)?)
184 }
185
186 pub fn last_update_time<B: SystemStateBackend>(
187 &self,
188 backend: &mut B,
189 ) -> Result<u64, L1PricingError> {
190 Ok(self.last_update_time.get(backend)?)
191 }
192
193 pub fn set_last_update_time<B: StorageBackend>(
194 &self,
195 backend: &mut B,
196 time: u64,
197 ) -> Result<(), L1PricingError> {
198 Ok(self.last_update_time.set(backend, time)?)
199 }
200
201 pub fn funds_due_for_rewards<B: SystemStateBackend>(
202 &self,
203 backend: &mut B,
204 ) -> Result<U256, L1PricingError> {
205 Ok(self.funds_due_for_rewards.get_raw(backend)?)
206 }
207
208 pub fn set_funds_due_for_rewards<B: StorageBackend>(
209 &self,
210 backend: &mut B,
211 val: U256,
212 ) -> Result<(), L1PricingError> {
213 Ok(self.funds_due_for_rewards.set(backend, val)?)
214 }
215
216 pub fn units_since_update<B: SystemStateBackend>(
217 &self,
218 backend: &mut B,
219 ) -> Result<u64, L1PricingError> {
220 Ok(self.units_since_update.get(backend)?)
221 }
222
223 pub fn set_units_since_update<B: StorageBackend>(
224 &self,
225 backend: &mut B,
226 val: u64,
227 ) -> Result<(), L1PricingError> {
228 Ok(self.units_since_update.set(backend, val)?)
229 }
230
231 pub fn add_to_units_since_update<B: StorageBackend>(
232 &self,
233 backend: &mut B,
234 units: u64,
235 ) -> Result<(), L1PricingError> {
236 let current = self.units_since_update.get(backend)?;
237 Ok(self
238 .units_since_update
239 .set(backend, current.saturating_add(units))?)
240 }
241
242 pub fn subtract_from_units_since_update<B: StorageBackend>(
243 &self,
244 backend: &mut B,
245 units: u64,
246 ) -> Result<(), L1PricingError> {
247 let current = self.units_since_update.get(backend)?;
248 Ok(self
249 .units_since_update
250 .set(backend, current.saturating_sub(units))?)
251 }
252
253 pub fn price_per_unit<B: SystemStateBackend>(
254 &self,
255 backend: &mut B,
256 ) -> Result<U256, L1PricingError> {
257 Ok(self.price_per_unit.get(backend)?)
258 }
259
260 pub fn set_price_per_unit<B: StorageBackend>(
261 &self,
262 backend: &mut B,
263 val: U256,
264 ) -> Result<(), L1PricingError> {
265 Ok(self.price_per_unit.set(backend, val)?)
266 }
267
268 pub fn last_surplus<B: SystemStateBackend>(
269 &self,
270 backend: &mut B,
271 ) -> Result<(U256, bool), L1PricingError> {
272 Ok(self.last_surplus.get_signed(backend)?)
273 }
274
275 pub fn set_last_surplus<B: StorageBackend>(
276 &self,
277 backend: &mut B,
278 magnitude: U256,
279 negative: bool,
280 ) -> Result<(), L1PricingError> {
281 if self.arbos_version < 7 {
282 return Ok(());
283 }
284 if negative {
285 Ok(self.last_surplus.set_negative(backend, magnitude)?)
286 } else {
287 Ok(self.last_surplus.set(backend, magnitude)?)
288 }
289 }
290
291 pub fn per_batch_gas_cost<B: SystemStateBackend>(
292 &self,
293 backend: &mut B,
294 ) -> Result<i64, L1PricingError> {
295 Ok(self.per_batch_gas_cost.get(backend)?)
296 }
297
298 pub fn set_per_batch_gas_cost<B: StorageBackend>(
299 &self,
300 backend: &mut B,
301 val: i64,
302 ) -> Result<(), L1PricingError> {
303 Ok(self.per_batch_gas_cost.set(backend, val)?)
304 }
305
306 pub fn amortized_cost_cap_bips<B: SystemStateBackend>(
307 &self,
308 backend: &mut B,
309 ) -> Result<u64, L1PricingError> {
310 Ok(self.amortized_cost_cap_bips.get(backend)?)
311 }
312
313 pub fn set_amortized_cost_cap_bips<B: StorageBackend>(
314 &self,
315 backend: &mut B,
316 val: u64,
317 ) -> Result<(), L1PricingError> {
318 Ok(self.amortized_cost_cap_bips.set(backend, val)?)
319 }
320
321 pub fn l1_fees_available<B: SystemStateBackend>(
322 &self,
323 backend: &mut B,
324 ) -> Result<U256, L1PricingError> {
325 Ok(self.l1_fees_available.get(backend)?)
326 }
327
328 pub fn set_l1_fees_available<B: StorageBackend>(
329 &self,
330 backend: &mut B,
331 val: U256,
332 ) -> Result<(), L1PricingError> {
333 Ok(self.l1_fees_available.set(backend, val)?)
334 }
335
336 pub fn add_to_l1_fees_available<B: StorageBackend>(
337 &self,
338 backend: &mut B,
339 amount: U256,
340 ) -> Result<(), L1PricingError> {
341 let current = self.l1_fees_available.get(backend)?;
342 Ok(self
343 .l1_fees_available
344 .set(backend, current.saturating_add(amount))?)
345 }
346
347 pub fn transfer_from_l1_fees_available<B: StorageBackend>(
348 &self,
349 backend: &mut B,
350 amount: U256,
351 ) -> Result<U256, L1PricingError> {
352 let available = self.l1_fees_available.get(backend)?;
353 let transfer = amount.min(available);
354 self.l1_fees_available
355 .set(backend, available.saturating_sub(transfer))?;
356 Ok(transfer)
357 }
358
359 pub fn parent_gas_floor_per_token<B: SystemStateBackend>(
360 &self,
361 backend: &mut B,
362 ) -> Result<u64, L1PricingError> {
363 if self.arbos_version < arb_chainspec::arbos_version::ARBOS_VERSION_50 {
364 return Ok(0);
365 }
366 Ok(self.gas_floor_per_token.get(backend)?)
367 }
368
369 pub fn set_parent_gas_floor_per_token<B: StorageBackend>(
370 &self,
371 backend: &mut B,
372 val: u64,
373 ) -> Result<(), L1PricingError> {
374 if self.arbos_version < arb_chainspec::arbos_version::ARBOS_VERSION_50 {
375 return Err(L1PricingError::ParentGasFloorUnsupportedVersion);
376 }
377 Ok(self.gas_floor_per_token.set(backend, val)?)
378 }
379
380 pub fn get_l1_pricing_surplus<B: SystemStateBackend>(
383 &self,
384 backend: &mut B,
385 ) -> Result<(U256, bool), L1PricingError> {
386 let l1_fees_available = self.l1_fees_available.get(backend)?;
387 let bpt = self.batch_poster_table();
388 let total_funds_due = bpt.total_funds_due(backend)?;
389 let funds_due_for_rewards = self.funds_due_for_rewards(backend)?;
390
391 let need = total_funds_due.saturating_add(funds_due_for_rewards);
392 if l1_fees_available >= need {
393 Ok((l1_fees_available.saturating_sub(need), false))
394 } else {
395 Ok((need.saturating_sub(l1_fees_available), true))
396 }
397 }
398
399 pub fn poster_data_cost<B: SystemStateBackend>(
400 &self,
401 backend: &mut B,
402 calldata_units: u64,
403 ) -> Result<U256, L1PricingError> {
404 let price = self.price_per_unit(backend)?;
405 let batch_cost = self.per_batch_gas_cost(backend)?;
406
407 let calldata_cost = price.saturating_mul(U256::from(calldata_units));
408 if batch_cost >= 0 {
409 Ok(calldata_cost.saturating_add(U256::from(batch_cost as u64)))
410 } else {
411 Ok(calldata_cost.saturating_sub(U256::from((-batch_cost) as u64)))
412 }
413 }
414
415 pub fn compute_poster_cost<B: SystemStateBackend>(
417 &self,
418 backend: &mut B,
419 poster: Address,
420 tx_bytes: &[u8],
421 brotli_compression_level: u64,
422 ) -> Result<(U256, u64), L1PricingError> {
423 if poster != BATCH_POSTER_ADDRESS {
424 return Ok((U256::ZERO, 0));
425 }
426 let units = self.get_poster_units_without_cache(tx_bytes, brotli_compression_level);
427 let price = self.price_per_unit(backend)?;
428 Ok((price.saturating_mul(U256::from(units)), units))
429 }
430
431 pub fn poster_data_cost_for_estimation<B: SystemStateBackend>(
433 &self,
434 backend: &mut B,
435 tx_bytes: &[u8],
436 brotli_compression_level: u64,
437 ) -> Result<(U256, u64), L1PricingError> {
438 let raw_units = self.get_poster_units_without_cache(tx_bytes, brotli_compression_level);
439 let padded = (raw_units.saturating_add(ESTIMATION_PADDING_UNITS))
440 .saturating_mul(ONE_IN_BIPS + ESTIMATION_PADDING_BASIS_POINTS)
441 / ONE_IN_BIPS;
442 let price = self.price_per_unit(backend)?;
443 Ok((price.saturating_mul(U256::from(padded)), padded))
444 }
445
446 pub fn get_poster_units_without_cache(
448 &self,
449 tx_bytes: &[u8],
450 brotli_compression_level: u64,
451 ) -> u64 {
452 let l1_bytes = byte_count_after_brotli_level(tx_bytes, brotli_compression_level);
453 TX_DATA_NON_ZERO_GAS_EIP2028.saturating_mul(l1_bytes)
454 }
455
456 fn _preversion10_update(
457 &self,
458 _update_time: u64,
459 _current_time: u64,
460 _wei_spent: U256,
461 _l1_basefee: U256,
462 ) -> Result<(), L1PricingError> {
463 Ok(())
464 }
465
466 fn _preversion2_update(
467 &self,
468 _update_time: u64,
469 _current_time: u64,
470 _wei_spent: U256,
471 _l1_basefee: U256,
472 ) -> Result<(), L1PricingError> {
473 Ok(())
474 }
475}
476
477impl<D: revm::Database> L1PricingState<'_, D> {
478 pub fn initialize<B: StorageBackend>(
479 sto: &Storage<'_, D>,
480 backend: &mut B,
481 rewards_recipient: Address,
482 initial_l1_base_fee: U256,
483 ) -> Result<(), L1PricingError> {
484 initialize_l1_pricing_state(sto, backend, rewards_recipient, initial_l1_base_fee)
485 }
486
487 pub fn get_poster_info<B: StorageBackend>(
488 &self,
489 backend: &mut B,
490 poster: Address,
491 ) -> Result<(U256, Address), L1PricingError> {
492 let bpt = self.batch_poster_table();
493 let state = bpt.open_poster(backend, poster, false)?;
494 let due = state.funds_due(backend)?;
495 let pay_to = state.pay_to(backend)?;
496 Ok((due, pay_to))
497 }
498
499 pub fn update_for_batch_poster_spending<F, B>(
501 &self,
502 backend: &mut B,
503 update_time: u64,
504 current_time: u64,
505 batch_poster: Address,
506 wei_spent: U256,
507 l1_basefee: U256,
508 mut transfer_fn: F,
509 ) -> Result<(), L1PricingError>
510 where
511 F: FnMut(Address, Address, U256) -> Result<(), BalanceError>,
512 B: StorageBackend,
513 {
514 if self.arbos_version < 10 {
515 return self._preversion10_update(update_time, current_time, wei_spent, l1_basefee);
516 }
517
518 let bpt = self.batch_poster_table();
519 let poster_state = bpt.open_poster(backend, batch_poster, true)?;
520
521 let funds_due_for_rewards = self.funds_due_for_rewards(backend)?;
522 let l1_fees_available = self.l1_fees_available.get(backend)?;
523
524 let mut last_update_time = self.last_update_time(backend)?;
525 if last_update_time == 0 && update_time > 0 {
526 last_update_time = update_time.saturating_sub(1);
527 }
528
529 if update_time > current_time || update_time < last_update_time {
530 return Err(L1PricingError::InvalidUpdateTime);
531 }
532
533 let alloc_num = update_time.saturating_sub(last_update_time);
534 let alloc_denom = current_time.saturating_sub(last_update_time);
535 let (alloc_num, alloc_denom) = if alloc_denom == 0 {
536 (1u64, 1u64)
537 } else {
538 (alloc_num, alloc_denom)
539 };
540
541 let units_since = self.units_since_update(backend)?;
542 let units_allocated = units_since
543 .saturating_mul(alloc_num)
544 .checked_div(alloc_denom)
545 .unwrap_or(0);
546 self.set_units_since_update(backend, units_since.saturating_sub(units_allocated))?;
547
548 let mut wei_spent = wei_spent;
549 if self.arbos_version >= 3 {
550 let cap_bips = self.amortized_cost_cap_bips(backend)?;
551 if cap_bips != 0 {
552 let cap = l1_basefee
553 .saturating_mul(U256::from(units_allocated))
554 .saturating_mul(U256::from(cap_bips))
555 .checked_div(U256::from(10000u64))
556 .unwrap_or(U256::MAX);
557 if cap < wei_spent {
558 wei_spent = cap;
559 }
560 }
561 }
562
563 let due = poster_state.funds_due(backend)?;
564 let _ = poster_state.set_funds_due(
565 backend,
566 due.saturating_add(wei_spent),
567 &bpt.total_funds_due,
568 );
569
570 let per_unit_reward = self.per_unit_reward(backend)?;
571 let reward_amount = U256::from(units_allocated).saturating_mul(U256::from(per_unit_reward));
572 self.set_funds_due_for_rewards(
573 backend,
574 funds_due_for_rewards.saturating_add(reward_amount),
575 )?;
576
577 let mut l1_fees = l1_fees_available;
578 let mut payment_for_rewards = reward_amount;
579 if l1_fees < payment_for_rewards {
580 payment_for_rewards = l1_fees;
581 }
582 let fdr_after = self
583 .funds_due_for_rewards(backend)?
584 .saturating_sub(payment_for_rewards);
585 self.set_funds_due_for_rewards(backend, fdr_after)?;
586
587 let pay_rewards_to = self.pay_rewards_to(backend)?;
588 if payment_for_rewards > U256::ZERO {
589 let _ = transfer_fn(
593 L1_PRICER_FUNDS_POOL_ADDRESS,
594 pay_rewards_to,
595 payment_for_rewards,
596 );
597 l1_fees = l1_fees.saturating_sub(payment_for_rewards);
598 self.set_l1_fees_available(backend, l1_fees)?;
599 }
600
601 let balance_due = poster_state.funds_due(backend)?;
602 let mut transfer_amount = balance_due;
603 if l1_fees < transfer_amount {
604 transfer_amount = l1_fees;
605 }
606 if transfer_amount > U256::ZERO {
607 let addr_to_pay = poster_state.pay_to(backend)?;
608 let _ = transfer_fn(L1_PRICER_FUNDS_POOL_ADDRESS, addr_to_pay, transfer_amount);
612 l1_fees = l1_fees.saturating_sub(transfer_amount);
613 self.set_l1_fees_available(backend, l1_fees)?;
614 let _ = poster_state.set_funds_due(
615 backend,
616 balance_due.saturating_sub(transfer_amount),
617 &bpt.total_funds_due,
618 );
619 }
620
621 self.set_last_update_time(backend, update_time)?;
622
623 if units_allocated > 0 {
624 let total_funds_due = bpt.total_funds_due(backend)?;
625 let fdr = self.funds_due_for_rewards(backend)?;
626
627 let need_funds = total_funds_due.saturating_add(fdr);
628 let (surplus_mag, surplus_positive) = if l1_fees >= need_funds {
629 (l1_fees.saturating_sub(need_funds), true)
630 } else {
631 (need_funds.saturating_sub(l1_fees), false)
632 };
633
634 let inertia = self.inertia(backend)?;
635 let equil_units = self.equilibration_units(backend)?;
636 let inertia_units = equil_units
637 .checked_div(U256::from(inertia))
638 .unwrap_or(U256::ZERO);
639 let price = self.price_per_unit(backend)?;
640
641 let alloc_plus_inert = inertia_units.saturating_add(U256::from(units_allocated));
642 let (old_surplus_mag, old_surplus_neg) = self.last_surplus.get_signed(backend)?;
643
644 let units_u256 = U256::from(units_allocated);
645
646 let (desired_mag, desired_pos) =
647 signed_div(surplus_mag, !surplus_positive, equil_units);
648
649 let (diff_mag, diff_pos) = signed_sub(
650 surplus_mag,
651 surplus_positive,
652 old_surplus_mag,
653 !old_surplus_neg,
654 );
655 let (actual_mag, actual_pos) = signed_div(diff_mag, diff_pos, units_u256);
656
657 let (change_mag, change_pos) =
658 signed_sub(desired_mag, desired_pos, actual_mag, actual_pos);
659
660 let change_times_units = change_mag.saturating_mul(units_u256);
661 let (price_change, price_change_pos) =
662 signed_div(change_times_units, change_pos, alloc_plus_inert);
663
664 let new_price = if price_change_pos {
665 price.saturating_add(price_change)
666 } else {
667 price.saturating_sub(price_change)
668 };
669
670 self.set_last_surplus(backend, surplus_mag, !surplus_positive)?;
671 self.set_price_per_unit(backend, new_price)?;
672 }
673
674 Ok(())
675 }
676}
677
678fn signed_div(mag: U256, positive: bool, divisor: U256) -> (U256, bool) {
683 if divisor.is_zero() {
684 return (U256::ZERO, true);
685 }
686
687 if positive {
688 return (mag / divisor, true);
690 }
691
692 let quotient = mag / divisor;
696 let remainder = mag % divisor;
697 if remainder.is_zero() {
698 if quotient.is_zero() {
699 (U256::ZERO, true) } else {
701 (quotient, false)
702 }
703 } else {
704 (quotient + U256::from(1), false)
706 }
707}
708
709fn signed_sub(a_mag: U256, a_pos: bool, b_mag: U256, b_pos: bool) -> (U256, bool) {
711 let (neg_b_mag, neg_b_pos) = (b_mag, !b_pos);
713 signed_add(a_mag, a_pos, neg_b_mag, neg_b_pos)
714}
715
716fn signed_add(a_mag: U256, a_pos: bool, b_mag: U256, b_pos: bool) -> (U256, bool) {
718 if a_pos == b_pos {
719 (a_mag.saturating_add(b_mag), a_pos)
720 } else if a_mag >= b_mag {
721 (a_mag.saturating_sub(b_mag), a_pos)
722 } else {
723 (b_mag.saturating_sub(a_mag), b_pos)
724 }
725}
726
727pub fn compute_poster_cost_standalone(
732 tx_bytes: &[u8],
733 poster: Address,
734 price_per_unit: U256,
735 brotli_compression_level: u64,
736) -> (U256, u64) {
737 if poster != BATCH_POSTER_ADDRESS {
738 return (U256::ZERO, 0);
739 }
740 let units = poster_units_from_bytes(tx_bytes, brotli_compression_level);
741 (price_per_unit.saturating_mul(U256::from(units)), units)
742}
743
744pub fn poster_units_from_bytes(tx_bytes: &[u8], brotli_compression_level: u64) -> u64 {
746 let l1_bytes = byte_count_after_brotli_level(tx_bytes, brotli_compression_level);
747 TX_DATA_NON_ZERO_GAS_EIP2028.saturating_mul(l1_bytes)
748}
749
750const BROTLI_DEFAULT_WINDOW_SIZE: i32 = 22;
752
753pub fn byte_count_after_brotli_level(data: &[u8], level: u64) -> u64 {
755 use std::{ffi::c_int, os::raw::c_void, ptr};
756
757 type BrotliBool = c_int;
758 const BROTLI_PARAM_QUALITY: u32 = 1;
759 const BROTLI_PARAM_LGWIN: u32 = 2;
760 const BROTLI_OPERATION_FINISH: u32 = 2;
761
762 unsafe extern "C" {
763 fn BrotliEncoderCreateInstance(
764 alloc: Option<extern "C" fn(*mut c_void, usize) -> *mut c_void>,
765 free: Option<extern "C" fn(*mut c_void, *mut c_void)>,
766 opaque: *mut c_void,
767 ) -> *mut c_void;
768 fn BrotliEncoderSetParameter(state: *mut c_void, param: u32, value: u32) -> BrotliBool;
769 fn BrotliEncoderCompressStream(
770 state: *mut c_void,
771 op: u32,
772 available_in: *mut usize,
773 next_in: *mut *const u8,
774 available_out: *mut usize,
775 next_out: *mut *mut u8,
776 total_out: *mut usize,
777 ) -> BrotliBool;
778 fn BrotliEncoderIsFinished(state: *const c_void) -> BrotliBool;
779 fn BrotliEncoderDestroyInstance(state: *mut c_void);
780 fn BrotliEncoderMaxCompressedSize(input_size: usize) -> usize;
781 }
782
783 unsafe {
788 let state = BrotliEncoderCreateInstance(None, None, ptr::null_mut());
789 if state.is_null() {
790 return data.len() as u64;
791 }
792
793 BrotliEncoderSetParameter(state, BROTLI_PARAM_QUALITY, level.min(11) as u32);
794 BrotliEncoderSetParameter(state, BROTLI_PARAM_LGWIN, BROTLI_DEFAULT_WINDOW_SIZE as u32);
795
796 let max_size = BrotliEncoderMaxCompressedSize(data.len());
797 let max_size = max_size.max(data.len() + (data.len() >> 10) * 8 + 64);
798 let mut output = vec![0u8; max_size];
799
800 let mut in_len = data.len();
801 let mut in_ptr = data.as_ptr();
802 let mut out_left = output.len();
803 let mut out_ptr = output.as_mut_ptr();
804 let mut out_len = 0usize;
805
806 let ok = BrotliEncoderCompressStream(
807 state,
808 BROTLI_OPERATION_FINISH,
809 &mut in_len,
810 &mut in_ptr,
811 &mut out_left,
812 &mut out_ptr,
813 &mut out_len,
814 );
815 let finished = BrotliEncoderIsFinished(state);
816 BrotliEncoderDestroyInstance(state);
817
818 if ok != 0 && finished != 0 {
819 out_len as u64
820 } else {
821 data.len() as u64
822 }
823 }
824}