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