1use alloy_evm::precompiles::{DynPrecompile, PrecompileInput};
2use alloy_primitives::{Address, U256};
3use alloy_sol_types::SolInterface;
4use revm::{
5 context_interface::block::Block,
6 precompile::{PrecompileError, PrecompileId, PrecompileOutput, PrecompileResult},
7};
8
9use crate::{
10 interfaces::IArbGasInfo,
11 storage_slot::{
12 derive_subspace_key, gas_constraints_vec_key, map_slot, multi_gas_base_fees_subspace,
13 multi_gas_constraints_vec_key, subspace_slot, vector_element_field, vector_element_key,
14 vector_length_slot, ARBOS_STATE_ADDRESS, L1_PRICING_SUBSPACE, L2_PRICING_SUBSPACE,
15 ROOT_STORAGE_KEY,
16 },
17};
18
19pub const ARBGASINFO_ADDRESS: Address = Address::new([
21 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
22 0x00, 0x00, 0x00, 0x6c,
23]);
24
25const SLOAD_GAS: u64 = 800;
26const COPY_GAS: u64 = 3;
27
28const L1_PAY_REWARDS_TO: u64 = 0;
30const L1_INERTIA: u64 = 2;
31const L1_PER_UNIT_REWARD: u64 = 3;
32const L1_PRICE_PER_UNIT: u64 = 7;
33const L1_LAST_SURPLUS: u64 = 8;
34const L1_PER_BATCH_GAS_COST: u64 = 9;
35const L1_AMORTIZED_COST_CAP_BIPS: u64 = 10;
36const L1_EQUILIBRATION_UNITS: u64 = 1;
37const L1_LAST_UPDATE_TIME: u64 = 4;
38const L1_FUNDS_DUE_FOR_REWARDS: u64 = 5;
39const L1_UNITS_SINCE: u64 = 6;
40const L1_FEES_AVAILABLE: u64 = 11;
41
42const L2_SPEED_LIMIT: u64 = 0;
44const L2_PER_BLOCK_GAS_LIMIT: u64 = 1;
45const L2_BASE_FEE: u64 = 2;
46const L2_MIN_BASE_FEE: u64 = 3;
47const L2_GAS_BACKLOG: u64 = 4;
48const L2_PRICING_INERTIA: u64 = 5;
49const L2_BACKLOG_TOLERANCE: u64 = 6;
50const L2_PER_TX_GAS_LIMIT: u64 = 7;
51
52const TX_DATA_NON_ZERO_GAS: u64 = 16;
53const ASSUMED_SIMPLE_TX_SIZE: u64 = 140;
54const STORAGE_WRITE_COST: u64 = 20_000;
55
56const BATCH_POSTER_TABLE_KEY: &[u8] = &[0];
58const TOTAL_FUNDS_DUE_OFFSET: u64 = 0;
60
61const L1_PRICER_FUNDS_POOL_ADDRESS: Address = Address::new([
63 0xa4, 0xb0, 0x5f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
64 0xff, 0xff, 0xff, 0xff,
65]);
66
67pub fn create_arbgasinfo_precompile() -> DynPrecompile {
68 DynPrecompile::new_stateful(PrecompileId::custom("arbgasinfo"), handler)
69}
70
71fn handler(mut input: PrecompileInput<'_>) -> PrecompileResult {
72 let gas_limit = input.gas;
73 crate::init_precompile_gas(input.data.len());
74
75 let call = match IArbGasInfo::ArbGasInfoCalls::abi_decode(input.data) {
76 Ok(c) => c,
77 Err(_) => return crate::burn_all_revert(gas_limit),
78 };
79
80 use IArbGasInfo::ArbGasInfoCalls as Calls;
81 let result = match call {
82 Calls::getL1BaseFeeEstimate(_) | Calls::getL1GasPriceEstimate(_) => {
83 read_l1_field(&mut input, L1_PRICE_PER_UNIT)
84 }
85 Calls::getMinimumGasPrice(_) => read_l2_field(&mut input, L2_MIN_BASE_FEE),
86 Calls::getPricesInWei(_) | Calls::getPricesInWeiWithAggregator(_) => {
87 handle_prices_in_wei(&mut input)
88 }
89 Calls::getGasAccountingParams(_) => handle_gas_accounting_params(&mut input),
90 Calls::getCurrentTxL1GasFees(_) => {
91 let fee = U256::from(crate::get_current_tx_poster_fee());
92 Ok(PrecompileOutput::new(
93 (SLOAD_GAS + COPY_GAS).min(gas_limit),
94 fee.to_be_bytes::<32>().to_vec().into(),
95 ))
96 }
97 Calls::getPricesInArbGas(_) | Calls::getPricesInArbGasWithAggregator(_) => {
98 handle_prices_in_arbgas(&mut input)
99 }
100 Calls::getL1BaseFeeEstimateInertia(_) => read_l1_field(&mut input, L1_INERTIA),
101 Calls::getGasBacklog(_) => read_l2_field(&mut input, L2_GAS_BACKLOG),
102 Calls::getPricingInertia(_) => read_l2_field(&mut input, L2_PRICING_INERTIA),
103 Calls::getGasBacklogTolerance(_) => read_l2_field(&mut input, L2_BACKLOG_TOLERANCE),
104 Calls::getL1PricingSurplus(_) => handle_l1_pricing_surplus(&mut input),
105 Calls::getPerBatchGasCharge(_) => read_l1_field(&mut input, L1_PER_BATCH_GAS_COST),
106 Calls::getAmortizedCostCapBips(_) => read_l1_field(&mut input, L1_AMORTIZED_COST_CAP_BIPS),
107 Calls::getL1FeesAvailable(_) => {
108 if let Some(r) = crate::check_method_version(gas_limit, 10, 0) {
109 return r;
110 }
111 read_l1_field(&mut input, L1_FEES_AVAILABLE)
112 }
113 Calls::getL1RewardRate(_) => {
114 if let Some(r) = crate::check_method_version(gas_limit, 11, 0) {
115 return r;
116 }
117 read_l1_field(&mut input, L1_PER_UNIT_REWARD)
118 }
119 Calls::getL1RewardRecipient(_) => {
120 if let Some(r) = crate::check_method_version(gas_limit, 11, 0) {
121 return r;
122 }
123 read_l1_field(&mut input, L1_PAY_REWARDS_TO)
124 }
125 Calls::getL1PricingEquilibrationUnits(_) => {
126 if let Some(r) = crate::check_method_version(gas_limit, 20, 0) {
127 return r;
128 }
129 read_l1_field(&mut input, L1_EQUILIBRATION_UNITS)
130 }
131 Calls::getLastL1PricingUpdateTime(_) => {
132 if let Some(r) = crate::check_method_version(gas_limit, 20, 0) {
133 return r;
134 }
135 read_l1_field(&mut input, L1_LAST_UPDATE_TIME)
136 }
137 Calls::getL1PricingFundsDueForRewards(_) => {
138 if let Some(r) = crate::check_method_version(gas_limit, 20, 0) {
139 return r;
140 }
141 read_l1_field(&mut input, L1_FUNDS_DUE_FOR_REWARDS)
142 }
143 Calls::getL1PricingUnitsSinceUpdate(_) => {
144 if let Some(r) = crate::check_method_version(gas_limit, 20, 0) {
145 return r;
146 }
147 read_l1_field(&mut input, L1_UNITS_SINCE)
148 }
149 Calls::getLastL1PricingSurplus(_) => {
150 if let Some(r) = crate::check_method_version(gas_limit, 20, 0) {
151 return r;
152 }
153 read_l1_field(&mut input, L1_LAST_SURPLUS)
154 }
155 Calls::getMaxBlockGasLimit(_) => {
156 if let Some(r) = crate::check_method_version(gas_limit, 50, 0) {
157 return r;
158 }
159 read_l2_field(&mut input, L2_PER_BLOCK_GAS_LIMIT)
160 }
161 Calls::getMaxTxGasLimit(_) => {
162 if let Some(r) = crate::check_method_version(gas_limit, 50, 0) {
163 return r;
164 }
165 read_l2_field(&mut input, L2_PER_TX_GAS_LIMIT)
166 }
167 Calls::getGasPricingConstraints(_) => {
168 if let Some(r) = crate::check_method_version(gas_limit, 50, 0) {
169 return r;
170 }
171 handle_gas_pricing_constraints(&mut input)
172 }
173 Calls::getMultiGasPricingConstraints(_) => {
174 if let Some(r) = crate::check_method_version(gas_limit, 60, 0) {
175 return r;
176 }
177 handle_multi_gas_pricing_constraints(&mut input)
178 }
179 Calls::getMultiGasBaseFee(_) => {
180 if let Some(r) = crate::check_method_version(gas_limit, 60, 0) {
181 return r;
182 }
183 handle_multi_gas_base_fee(&mut input)
184 }
185 };
186 crate::gas_check(gas_limit, result)
187}
188
189fn load_arbos(input: &mut PrecompileInput<'_>) -> Result<(), PrecompileError> {
192 input
193 .internals_mut()
194 .load_account(ARBOS_STATE_ADDRESS)
195 .map_err(|e| PrecompileError::other(format!("load_account: {e:?}")))?;
196 Ok(())
197}
198
199fn sload_field(input: &mut PrecompileInput<'_>, slot: U256) -> Result<U256, PrecompileError> {
200 let val = input
201 .internals_mut()
202 .sload(ARBOS_STATE_ADDRESS, slot)
203 .map_err(|_| PrecompileError::other("sload failed"))?;
204 crate::charge_precompile_gas(SLOAD_GAS);
205 Ok(val.data)
206}
207
208fn read_l1_field(input: &mut PrecompileInput<'_>, offset: u64) -> PrecompileResult {
209 let gas_limit = input.gas;
210 load_arbos(input)?;
211 let field_slot = subspace_slot(L1_PRICING_SUBSPACE, offset);
212 let value = sload_field(input, field_slot)?;
213 Ok(PrecompileOutput::new(
214 (2 * SLOAD_GAS + COPY_GAS).min(gas_limit),
215 value.to_be_bytes::<32>().to_vec().into(),
216 ))
217}
218
219fn read_l2_field(input: &mut PrecompileInput<'_>, offset: u64) -> PrecompileResult {
220 let gas_limit = input.gas;
221 load_arbos(input)?;
222 let field_slot = subspace_slot(L2_PRICING_SUBSPACE, offset);
223 let value = sload_field(input, field_slot)?;
224 Ok(PrecompileOutput::new(
225 (2 * SLOAD_GAS + COPY_GAS).min(gas_limit),
226 value.to_be_bytes::<32>().to_vec().into(),
227 ))
228}
229
230fn handle_l1_pricing_surplus(input: &mut PrecompileInput<'_>) -> PrecompileResult {
234 let gas_limit = input.gas;
235 let arbos_version = crate::get_arbos_version();
236
237 load_arbos(input)?;
238
239 let l1_sub_key = derive_subspace_key(ROOT_STORAGE_KEY, L1_PRICING_SUBSPACE);
241 let bpt_key = derive_subspace_key(l1_sub_key.as_slice(), BATCH_POSTER_TABLE_KEY);
242 let total_funds_due_slot = map_slot(bpt_key.as_slice(), TOTAL_FUNDS_DUE_OFFSET);
243 let total_funds_due = sload_field(input, total_funds_due_slot)?;
244
245 let fdr_slot = subspace_slot(L1_PRICING_SUBSPACE, L1_FUNDS_DUE_FOR_REWARDS);
247 let funds_due_for_rewards = sload_field(input, fdr_slot)?;
248
249 let need_funds = total_funds_due.saturating_add(funds_due_for_rewards);
250
251 let have_funds = if arbos_version >= 10 {
252 let slot = subspace_slot(L1_PRICING_SUBSPACE, L1_FEES_AVAILABLE);
254 sload_field(input, slot)?
255 } else {
256 let account = input
258 .internals_mut()
259 .load_account(L1_PRICER_FUNDS_POOL_ADDRESS)
260 .map_err(|e| PrecompileError::other(format!("load_account: {e:?}")))?;
261 account.data.info.balance
262 };
263
264 let surplus = if have_funds >= need_funds {
266 have_funds - need_funds
267 } else {
268 let deficit = need_funds - have_funds;
270 U256::ZERO.wrapping_sub(deficit)
271 };
272
273 let gas_cost = (4 * SLOAD_GAS + COPY_GAS).min(gas_limit);
274 Ok(PrecompileOutput::new(
275 gas_cost,
276 surplus.to_be_bytes::<32>().to_vec().into(),
277 ))
278}
279
280fn handle_prices_in_wei(input: &mut PrecompileInput<'_>) -> PrecompileResult {
281 let data_len = input.data.len();
282 let gas_limit = input.gas;
283
284 let block_basefee = U256::from(input.internals().block_env().basefee());
288 load_arbos(input)?;
289
290 let l1_price = sload_field(input, subspace_slot(L1_PRICING_SUBSPACE, L1_PRICE_PER_UNIT))?;
291 let l2_min = sload_field(input, subspace_slot(L2_PRICING_SUBSPACE, L2_MIN_BASE_FEE))?;
292 let l2_gas_price = if block_basefee.is_zero() {
293 sload_field(input, subspace_slot(L2_PRICING_SUBSPACE, L2_BASE_FEE))?
294 } else {
295 block_basefee
296 };
297
298 let wei_for_l1_calldata = l1_price.saturating_mul(U256::from(TX_DATA_NON_ZERO_GAS));
299 let per_l2_tx = wei_for_l1_calldata.saturating_mul(U256::from(ASSUMED_SIMPLE_TX_SIZE));
300 let per_arbgas_base = l2_gas_price.min(l2_min);
301 let per_arbgas_congestion = l2_gas_price.saturating_sub(per_arbgas_base);
302 let per_arbgas_total = l2_gas_price;
303 let wei_for_l2_storage = l2_gas_price.saturating_mul(U256::from(STORAGE_WRITE_COST));
304
305 let mut out = Vec::with_capacity(192);
306 out.extend_from_slice(&per_l2_tx.to_be_bytes::<32>());
307 out.extend_from_slice(&wei_for_l1_calldata.to_be_bytes::<32>());
308 out.extend_from_slice(&wei_for_l2_storage.to_be_bytes::<32>());
309 out.extend_from_slice(&per_arbgas_base.to_be_bytes::<32>());
310 out.extend_from_slice(&per_arbgas_congestion.to_be_bytes::<32>());
311 out.extend_from_slice(&per_arbgas_total.to_be_bytes::<32>());
312
313 let arg_words = (data_len as u64).saturating_sub(4).div_ceil(32);
316 let gas_cost = (3 * SLOAD_GAS + (arg_words + 6) * COPY_GAS).min(gas_limit);
317 Ok(PrecompileOutput::new(gas_cost, out.into()))
318}
319
320fn handle_gas_accounting_params(input: &mut PrecompileInput<'_>) -> PrecompileResult {
321 let gas_limit = input.gas;
322 load_arbos(input)?;
323
324 let speed_limit = sload_field(input, subspace_slot(L2_PRICING_SUBSPACE, L2_SPEED_LIMIT))?;
325 let gas_limit_val = sload_field(
326 input,
327 subspace_slot(L2_PRICING_SUBSPACE, L2_PER_BLOCK_GAS_LIMIT),
328 )?;
329
330 let mut out = Vec::with_capacity(96);
331 out.extend_from_slice(&speed_limit.to_be_bytes::<32>());
332 out.extend_from_slice(&gas_limit_val.to_be_bytes::<32>());
333 out.extend_from_slice(&gas_limit_val.to_be_bytes::<32>());
334
335 Ok(PrecompileOutput::new(
336 (3 * SLOAD_GAS + 3 * COPY_GAS).min(gas_limit),
337 out.into(),
338 ))
339}
340
341fn handle_prices_in_arbgas(input: &mut PrecompileInput<'_>) -> PrecompileResult {
342 let data_len = input.data.len();
343 let gas_limit = input.gas;
344
345 let block_basefee = U256::from(input.internals().block_env().basefee());
346 load_arbos(input)?;
347
348 let l1_price = sload_field(input, subspace_slot(L1_PRICING_SUBSPACE, L1_PRICE_PER_UNIT))?;
349 let l2_gas_price = if block_basefee.is_zero() {
350 sload_field(input, subspace_slot(L2_PRICING_SUBSPACE, L2_BASE_FEE))?
351 } else {
352 block_basefee
353 };
354
355 let wei_for_l1_calldata = l1_price.saturating_mul(U256::from(TX_DATA_NON_ZERO_GAS));
356 let wei_per_l2_tx = wei_for_l1_calldata.saturating_mul(U256::from(ASSUMED_SIMPLE_TX_SIZE));
357
358 let (gas_for_l1_calldata, gas_per_l2_tx) = if l2_gas_price > U256::ZERO {
359 (
360 wei_for_l1_calldata / l2_gas_price,
361 wei_per_l2_tx / l2_gas_price,
362 )
363 } else {
364 (U256::ZERO, U256::ZERO)
365 };
366
367 let mut out = Vec::with_capacity(96);
368 out.extend_from_slice(&gas_per_l2_tx.to_be_bytes::<32>());
369 out.extend_from_slice(&gas_for_l1_calldata.to_be_bytes::<32>());
370 out.extend_from_slice(&U256::from(STORAGE_WRITE_COST).to_be_bytes::<32>());
371
372 let arg_words = (data_len as u64).saturating_sub(4).div_ceil(32);
375 let gas_cost = (2 * SLOAD_GAS + (arg_words + 3) * COPY_GAS).min(gas_limit);
376 Ok(PrecompileOutput::new(gas_cost, out.into()))
377}
378
379const CONSTRAINT_TARGET: u64 = 0;
383const CONSTRAINT_ADJ_WINDOW: u64 = 1;
384const CONSTRAINT_BACKLOG: u64 = 2;
385const MULTI_CONSTRAINT_WEIGHTED_BASE: u64 = 4;
386
387const NUM_RESOURCE_KIND: u64 = 8;
388const CURRENT_BLOCK_FEES_OFFSET: u64 = NUM_RESOURCE_KIND;
390
391fn handle_gas_pricing_constraints(input: &mut PrecompileInput<'_>) -> PrecompileResult {
393 let gas_limit = input.gas;
394 load_arbos(input)?;
395
396 let vec_key = gas_constraints_vec_key();
397 let count = sload_field(input, vector_length_slot(&vec_key))?.saturating_to::<u64>();
398 let mut sloads: u64 = 2; let mut out = Vec::with_capacity(64 + count as usize * 96);
402 out.extend_from_slice(&U256::from(32u64).to_be_bytes::<32>());
403 out.extend_from_slice(&U256::from(count).to_be_bytes::<32>());
404
405 for i in 0..count {
406 let target = sload_field(input, vector_element_field(&vec_key, i, CONSTRAINT_TARGET))?;
407 let window = sload_field(
408 input,
409 vector_element_field(&vec_key, i, CONSTRAINT_ADJ_WINDOW),
410 )?;
411 let backlog = sload_field(input, vector_element_field(&vec_key, i, CONSTRAINT_BACKLOG))?;
412
413 out.extend_from_slice(&target.to_be_bytes::<32>());
414 out.extend_from_slice(&window.to_be_bytes::<32>());
415 out.extend_from_slice(&backlog.to_be_bytes::<32>());
416 sloads += 3;
417 }
418
419 let result_words = (out.len() as u64).div_ceil(32);
420 Ok(PrecompileOutput::new(
421 (sloads * SLOAD_GAS + result_words * COPY_GAS).min(gas_limit),
422 out.into(),
423 ))
424}
425
426fn handle_multi_gas_pricing_constraints(input: &mut PrecompileInput<'_>) -> PrecompileResult {
432 let gas_limit = input.gas;
433 load_arbos(input)?;
434
435 let vec_key = multi_gas_constraints_vec_key();
436 let count = sload_field(input, vector_length_slot(&vec_key))?.saturating_to::<u64>();
437 let mut sloads: u64 = 2; struct ConstraintData {
441 target: U256,
442 window: U256,
443 backlog: U256,
444 resources: Vec<(u8, U256)>,
445 }
446 let mut constraints = Vec::with_capacity(count as usize);
447
448 for i in 0..count {
449 let target = sload_field(input, vector_element_field(&vec_key, i, CONSTRAINT_TARGET))?;
450 let window = sload_field(
451 input,
452 vector_element_field(&vec_key, i, CONSTRAINT_ADJ_WINDOW),
453 )?;
454 let backlog = sload_field(input, vector_element_field(&vec_key, i, CONSTRAINT_BACKLOG))?;
455 sloads += 3;
456
457 let elem_key = vector_element_key(&vec_key, i);
458 let mut resources = Vec::new();
459 for kind in 0..NUM_RESOURCE_KIND {
460 let w = sload_field(
461 input,
462 map_slot(elem_key.as_slice(), MULTI_CONSTRAINT_WEIGHTED_BASE + kind),
463 )?;
464 sloads += 1;
465 if w > U256::ZERO {
466 resources.push((kind as u8, w));
467 }
468 }
469 constraints.push(ConstraintData {
470 target,
471 window,
472 backlog,
473 resources,
474 });
475 }
476
477 let n = constraints.len();
479 let mut out = Vec::new();
480
481 out.extend_from_slice(&U256::from(32u64).to_be_bytes::<32>());
483 out.extend_from_slice(&U256::from(n).to_be_bytes::<32>());
485
486 let elem_sizes: Vec<usize> = constraints
488 .iter()
489 .map(|c| 4 * 32 + 32 + c.resources.len() * 64)
490 .collect();
491
492 let mut running_offset = n * 32;
494 for size in &elem_sizes {
495 out.extend_from_slice(&U256::from(running_offset).to_be_bytes::<32>());
496 running_offset += size;
497 }
498
499 for c in &constraints {
501 let m = c.resources.len();
502 out.extend_from_slice(&U256::from(4u64 * 32).to_be_bytes::<32>());
504 out.extend_from_slice(&c.window.to_be_bytes::<32>());
506 out.extend_from_slice(&c.target.to_be_bytes::<32>());
508 out.extend_from_slice(&c.backlog.to_be_bytes::<32>());
510 out.extend_from_slice(&U256::from(m).to_be_bytes::<32>());
512 for &(kind, ref weight) in &c.resources {
514 out.extend_from_slice(&U256::from(kind).to_be_bytes::<32>());
515 out.extend_from_slice(&weight.to_be_bytes::<32>());
516 }
517 }
518
519 let result_words = (out.len() as u64).div_ceil(32);
520 Ok(PrecompileOutput::new(
521 (sloads * SLOAD_GAS + result_words * COPY_GAS).min(gas_limit),
522 out.into(),
523 ))
524}
525
526fn handle_multi_gas_base_fee(input: &mut PrecompileInput<'_>) -> PrecompileResult {
528 let gas_limit = input.gas;
529 load_arbos(input)?;
530
531 let fees_key = multi_gas_base_fees_subspace();
532
533 let mut out = Vec::with_capacity(64 + NUM_RESOURCE_KIND as usize * 32);
534 out.extend_from_slice(&U256::from(32u64).to_be_bytes::<32>());
536 out.extend_from_slice(&U256::from(NUM_RESOURCE_KIND).to_be_bytes::<32>());
537
538 for kind in 0..NUM_RESOURCE_KIND {
539 let slot = map_slot(fees_key.as_slice(), CURRENT_BLOCK_FEES_OFFSET + kind);
540 let fee = sload_field(input, slot)?;
541 out.extend_from_slice(&fee.to_be_bytes::<32>());
542 }
543
544 let result_words = (out.len() as u64).div_ceil(32);
545 Ok(PrecompileOutput::new(
546 ((1 + NUM_RESOURCE_KIND) * SLOAD_GAS + result_words * COPY_GAS).min(gas_limit),
547 out.into(),
548 ))
549}