1mod error;
8mod interfaces;
9
10mod arbaddresstable;
11mod arbaggregator;
12mod arbbls;
13mod arbdebug;
14mod arbfilteredtxmanager;
15mod arbfunctiontable;
16mod arbgasinfo;
17mod arbinfo;
18mod arbnativetokenmanager;
19mod arbosacts;
20mod arbostest;
21mod arbowner;
22mod arbownerpublic;
23mod arbretryabletx;
24mod arbstatistics;
25pub mod arbsys;
26mod arbwasm;
27mod arbwasmcache;
28mod nodeinterface;
29mod nodeinterface_debug;
30
31use std::sync::Arc;
32
33use alloy_evm::{
34 EvmInternals,
35 precompiles::{DynPrecompile, PrecompileInput, PrecompilesMap},
36};
37use arb_context::ArbPrecompileCtx;
38pub use arbaddresstable::{ARBADDRESSTABLE_ADDRESS, create_arbaddresstable_precompile};
39pub use arbaggregator::{ARBAGGREGATOR_ADDRESS, create_arbaggregator_precompile};
40pub use arbbls::{ARBBLS_ADDRESS, create_arbbls_precompile};
41pub use arbdebug::{ARBDEBUG_ADDRESS, create_arbdebug_precompile};
42pub use arbfilteredtxmanager::{
43 ARBFILTEREDTXMANAGER_ADDRESS, create_arbfilteredtxmanager_precompile,
44};
45pub use arbfunctiontable::{ARBFUNCTIONTABLE_ADDRESS, create_arbfunctiontable_precompile};
46pub use arbgasinfo::{ARBGASINFO_ADDRESS, create_arbgasinfo_precompile};
47pub use arbinfo::{ARBINFO_ADDRESS, create_arbinfo_precompile};
48pub use arbnativetokenmanager::{
49 ARBNATIVETOKENMANAGER_ADDRESS, create_arbnativetokenmanager_precompile,
50};
51pub use arbosacts::{ARBOSACTS_ADDRESS, create_arbosacts_precompile};
52pub use arbostest::{ARBOSTEST_ADDRESS, create_arbostest_precompile};
53pub use arbowner::{ARBOWNER_ADDRESS, create_arbowner_precompile};
54pub use arbownerpublic::{ARBOWNERPUBLIC_ADDRESS, create_arbownerpublic_precompile};
55pub use arbretryabletx::{
56 ARBRETRYABLETX_ADDRESS, create_arbretryabletx_precompile, redeem_scheduled_topic,
57 ticket_created_topic,
58};
59pub use arbstatistics::{ARBSTATISTICS_ADDRESS, create_arbstatistics_precompile};
60pub use arbsys::{ARBSYS_ADDRESS, create_arbsys_precompile};
61pub use arbwasm::{ARBWASM_ADDRESS, create_arbwasm_precompile};
62pub use arbwasmcache::{ARBWASMCACHE_ADDRESS, create_arbwasmcache_precompile};
63pub use error::ArbPrecompileError;
64pub use nodeinterface::{
65 NODE_INTERFACE_ADDRESS, build_fake_tx_bytes, compute_l1_gas_for_estimate,
66 create_nodeinterface_precompile, decode_estimate_args,
67};
68pub use nodeinterface_debug::{
69 NODE_INTERFACE_DEBUG_ADDRESS, create_nodeinterface_debug_precompile,
70};
71use revm::precompile::{PrecompileError, PrecompileId, PrecompileOutput, PrecompileResult};
72
73pub const P256VERIFY_ADDRESS: alloy_primitives::Address =
75 alloy_primitives::address!("0000000000000000000000000000000000000100");
76
77const MODEXP_ADDRESS: alloy_primitives::Address =
79 alloy_primitives::address!("0000000000000000000000000000000000000005");
80
81const BLS12_381_ADDRESSES: [alloy_primitives::Address; 7] = [
83 alloy_primitives::address!("000000000000000000000000000000000000000b"),
84 alloy_primitives::address!("000000000000000000000000000000000000000c"),
85 alloy_primitives::address!("000000000000000000000000000000000000000d"),
86 alloy_primitives::address!("000000000000000000000000000000000000000e"),
87 alloy_primitives::address!("000000000000000000000000000000000000000f"),
88 alloy_primitives::address!("0000000000000000000000000000000000000010"),
89 alloy_primitives::address!("0000000000000000000000000000000000000011"),
90];
91
92fn create_p256verify_precompile() -> DynPrecompile {
93 DynPrecompile::new(PrecompileId::P256Verify, |input: PrecompileInput<'_>| {
94 revm::precompile::secp256r1::p256_verify(input.data, input.gas)
95 })
96}
97
98fn create_p256verify_osaka_precompile() -> DynPrecompile {
99 DynPrecompile::new(PrecompileId::P256Verify, |input: PrecompileInput<'_>| {
100 revm::precompile::secp256r1::p256_verify_osaka(input.data, input.gas)
101 })
102}
103
104fn create_modexp_osaka_precompile() -> DynPrecompile {
105 DynPrecompile::new(PrecompileId::ModExp, |input: PrecompileInput<'_>| {
106 revm::precompile::modexp::osaka_run(input.data, input.gas)
107 })
108}
109
110pub fn charge_precompile_gas(gas_used: &mut u64, gas: u64) {
111 *gas_used = gas_used.saturating_add(gas);
112}
113
114pub(crate) fn without_access_list_effect<R>(
118 internals: &mut EvmInternals<'_>,
119 f: impl FnOnce(&mut EvmInternals<'_>) -> R,
120) -> R {
121 let checkpoint = internals.checkpoint();
122 let result = f(internals);
123 internals.checkpoint_revert(checkpoint);
124 result
125}
126
127pub fn charge_storage_read(gas_used: &mut u64, ctx: &arb_context::ArbPrecompileCtx, gas: u64) {
132 charge_precompile_gas(gas_used, gas);
133 ctx.add_precompile_multi_gas(
134 arb_primitives::multigas::ResourceKind::StorageAccessRead,
135 gas,
136 );
137}
138
139pub fn charge_storage_write(gas_used: &mut u64, ctx: &arb_context::ArbPrecompileCtx, gas: u64) {
142 charge_precompile_gas(gas_used, gas);
143 ctx.add_precompile_multi_gas(
144 arb_primitives::multigas::ResourceKind::StorageAccessWrite,
145 gas,
146 );
147}
148
149pub fn charge_l2_calldata(gas_used: &mut u64, ctx: &arb_context::ArbPrecompileCtx, gas: u64) {
154 charge_precompile_gas(gas_used, gas);
155 ctx.add_precompile_multi_gas(arb_primitives::multigas::ResourceKind::L2Calldata, gas);
156}
157
158pub fn charge_history_growth(gas_used: &mut u64, ctx: &arb_context::ArbPrecompileCtx, gas: u64) {
160 charge_precompile_gas(gas_used, gas);
161 ctx.add_precompile_multi_gas(arb_primitives::multigas::ResourceKind::HistoryGrowth, gas);
162}
163
164pub fn charge_computation(gas_used: &mut u64, ctx: &arb_context::ArbPrecompileCtx, gas: u64) {
169 charge_precompile_gas(gas_used, gas);
170 ctx.add_precompile_multi_gas(arb_primitives::multigas::ResourceKind::Computation, gas);
171}
172
173const PARAMS_WARM_READ_GAS: u64 = 100;
175
176pub fn charge_params_read(gas_used: &mut u64, ctx: &arb_context::ArbPrecompileCtx) {
180 charge_computation(gas_used, ctx, PARAMS_WARM_READ_GAS);
181}
182
183pub fn init_precompile_gas(
188 gas_used: &mut u64,
189 ctx: &arb_context::ArbPrecompileCtx,
190 input_len: usize,
191) {
192 let args_cost = 3u64 * (input_len as u64).saturating_sub(4).div_ceil(32);
193 charge_l2_calldata(gas_used, ctx, args_cost);
194 charge_storage_read(gas_used, ctx, 800);
195}
196
197pub fn init_precompile_gas_pure(
201 gas_used: &mut u64,
202 ctx: &arb_context::ArbPrecompileCtx,
203 input_len: usize,
204) {
205 let args_cost = 3u64 * (input_len as u64).saturating_sub(4).div_ceil(32);
206 charge_l2_calldata(gas_used, ctx, args_cost);
207}
208
209fn check_precompile_version(ctx: &ArbPrecompileCtx, min_version: u64) -> Option<PrecompileResult> {
210 if ctx.block.arbos_version < min_version {
211 Some(Ok(PrecompileOutput::new(0, Default::default())))
212 } else {
213 None
214 }
215}
216
217fn burn_all_revert(gas_limit: u64) -> PrecompileResult {
219 Ok(PrecompileOutput::new_reverted(
220 gas_limit,
221 Default::default(),
222 ))
223}
224
225pub(crate) fn revert_sol_error(
228 gas_used: &mut u64,
229 ctx: &arb_context::ArbPrecompileCtx,
230 payload: Vec<u8>,
231 input_gas: u64,
232) -> PrecompileResult {
233 charge_computation(gas_used, ctx, 3 * (payload.len() as u64).div_ceil(32));
234 if *gas_used > input_gas {
235 return Err(ArbPrecompileError::OutOfGas.into());
236 }
237 Ok(PrecompileOutput::new_reverted(*gas_used, payload.into()))
238}
239
240pub fn reject_nonpayable_value(
244 value: alloy_primitives::U256,
245 data: &[u8],
246 gas_limit: u64,
247 payable: &[[u8; 4]],
248) -> Option<PrecompileResult> {
249 if value.is_zero() {
250 return None;
251 }
252 if payable.contains(&input_selector(data)) {
253 return None;
254 }
255 Some(burn_all_revert(gas_limit))
256}
257
258fn input_selector(data: &[u8]) -> [u8; 4] {
259 data.get(..4)
260 .and_then(|s| s.try_into().ok())
261 .unwrap_or([0u8; 4])
262}
263
264pub fn reject_static_write(
267 is_static: bool,
268 data: &[u8],
269 gas_limit: u64,
270 write: &[[u8; 4]],
271) -> Option<PrecompileResult> {
272 if is_static && write.contains(&input_selector(data)) {
273 return Some(burn_all_revert(gas_limit));
274 }
275 None
276}
277
278pub fn reject_static_unless_read(
282 is_static: bool,
283 data: &[u8],
284 gas_limit: u64,
285 reads: &[[u8; 4]],
286) -> Option<PrecompileResult> {
287 if is_static && !reads.contains(&input_selector(data)) {
288 return Some(burn_all_revert(gas_limit));
289 }
290 None
291}
292
293pub fn reject_delegate_nonpure(
297 is_delegate: bool,
298 data: &[u8],
299 gas_limit: u64,
300 pure: &[[u8; 4]],
301) -> Option<PrecompileResult> {
302 if is_delegate && !pure.contains(&input_selector(data)) {
303 return Some(burn_all_revert(gas_limit));
304 }
305 None
306}
307
308pub fn sol_error_revert(
313 gas_used: &mut u64,
314 ctx: &ArbPrecompileCtx,
315 payload: Vec<u8>,
316 gas_limit: u64,
317) -> PrecompileResult {
318 let result_cost = 3u64 * (payload.len() as u64).div_ceil(32); charge_computation(gas_used, ctx, result_cost);
320 Ok(PrecompileOutput::new_reverted(
321 (*gas_used).min(gas_limit),
322 payload.into(),
323 ))
324}
325
326fn gas_check(
327 ctx: &ArbPrecompileCtx,
328 gas_limit: u64,
329 gas_used: u64,
330 result: PrecompileResult,
331) -> PrecompileResult {
332 if gas_used > gas_limit {
333 return Err(PrecompileError::OutOfGas);
334 }
335 match result {
336 Err(PrecompileError::Other(_)) if ctx.block.arbos_version >= 11 => Ok(
337 PrecompileOutput::new_reverted(gas_used.min(gas_limit), Default::default()),
338 ),
339 other => other,
340 }
341}
342
343fn check_method_version(
347 ctx: &ArbPrecompileCtx,
348 gas_limit: u64,
349 min_version: u64,
350 max_version: u64,
351) -> Option<PrecompileResult> {
352 let v = ctx.block.arbos_version;
353 if v < min_version || (max_version > 0 && v > max_version) {
354 Some(burn_all_revert(gas_limit))
355 } else {
356 None
357 }
358}
359
360const KZG_POINT_EVALUATION_ADDRESS: alloy_primitives::Address =
361 alloy_primitives::address!("000000000000000000000000000000000000000a");
362
363pub fn register_arb_precompiles(map: &mut PrecompilesMap, ctx: Arc<ArbPrecompileCtx>) {
370 let arbos_version = ctx.block.arbos_version;
371 map.extend_precompiles([
372 (ARBSYS_ADDRESS, create_arbsys_precompile(ctx.clone())),
373 (
374 ARBGASINFO_ADDRESS,
375 create_arbgasinfo_precompile(ctx.clone()),
376 ),
377 (ARBINFO_ADDRESS, create_arbinfo_precompile(ctx.clone())),
378 (
379 ARBSTATISTICS_ADDRESS,
380 create_arbstatistics_precompile(ctx.clone()),
381 ),
382 (
383 ARBFUNCTIONTABLE_ADDRESS,
384 create_arbfunctiontable_precompile(ctx.clone()),
385 ),
386 (ARBOSACTS_ADDRESS, create_arbosacts_precompile(ctx.clone())),
387 (ARBOSTEST_ADDRESS, create_arbostest_precompile(ctx.clone())),
388 (
389 ARBOWNERPUBLIC_ADDRESS,
390 create_arbownerpublic_precompile(ctx.clone()),
391 ),
392 (
393 ARBADDRESSTABLE_ADDRESS,
394 create_arbaddresstable_precompile(ctx.clone()),
395 ),
396 (
397 ARBAGGREGATOR_ADDRESS,
398 create_arbaggregator_precompile(ctx.clone()),
399 ),
400 (
401 ARBRETRYABLETX_ADDRESS,
402 create_arbretryabletx_precompile(ctx.clone()),
403 ),
404 (ARBOWNER_ADDRESS, create_arbowner_precompile(ctx.clone())),
405 (ARBBLS_ADDRESS, create_arbbls_precompile()),
406 (ARBDEBUG_ADDRESS, create_arbdebug_precompile(ctx.clone())),
407 (ARBWASM_ADDRESS, create_arbwasm_precompile(ctx.clone())),
408 (
409 ARBWASMCACHE_ADDRESS,
410 create_arbwasmcache_precompile(ctx.clone()),
411 ),
412 (
413 ARBFILTEREDTXMANAGER_ADDRESS,
414 create_arbfilteredtxmanager_precompile(ctx.clone()),
415 ),
416 (
417 ARBNATIVETOKENMANAGER_ADDRESS,
418 create_arbnativetokenmanager_precompile(ctx.clone()),
419 ),
420 ]);
421
422 if arbos_version >= arb_chainspec::arbos_version::ARBOS_VERSION_50 {
423 map.extend_precompiles([(P256VERIFY_ADDRESS, create_p256verify_osaka_precompile())]);
425 } else if arbos_version >= arb_chainspec::arbos_version::ARBOS_VERSION_30 {
426 map.extend_precompiles([(P256VERIFY_ADDRESS, create_p256verify_precompile())]);
428 } else {
429 map.apply_precompile(&KZG_POINT_EVALUATION_ADDRESS, |_| None);
430 map.apply_precompile(&P256VERIFY_ADDRESS, |_| None);
431 }
432
433 if arbos_version >= arb_chainspec::arbos_version::ARBOS_VERSION_50 {
434 map.extend_precompiles([(MODEXP_ADDRESS, create_modexp_osaka_precompile())]);
436 } else {
437 for addr in &BLS12_381_ADDRESSES {
439 map.apply_precompile(addr, |_| None);
440 }
441 }
442}
443
444#[cfg(test)]
445mod recent_wasms_tests {
446 use alloy_primitives::B256;
447 use arb_context::BlockCtx;
448
449 #[test]
450 fn reset_clears_entries_and_sets_capacity() {
451 let block = BlockCtx::default();
452 let h1 = B256::repeat_byte(0xa1);
453 let h2 = B256::repeat_byte(0xa2);
454 block.reset_recent_wasms(8);
455 assert!(!block.insert_recent_wasm(h1));
456 assert!(!block.insert_recent_wasm(h2));
457 assert!(block.insert_recent_wasm(h1));
458 block.reset_recent_wasms(8);
459 assert!(
460 !block.insert_recent_wasm(h1),
461 "reset must wipe prior entries"
462 );
463 }
464
465 #[test]
466 fn capacity_evicts_oldest() {
467 let block = BlockCtx::default();
468 let h1 = B256::repeat_byte(0x01);
469 let h2 = B256::repeat_byte(0x02);
470 let h3 = B256::repeat_byte(0x03);
471 block.reset_recent_wasms(2);
472 assert!(!block.insert_recent_wasm(h1));
473 assert!(!block.insert_recent_wasm(h2));
474 assert!(!block.insert_recent_wasm(h3));
475 assert!(
476 !block.insert_recent_wasm(h1),
477 "h1 should be evicted after h3 push"
478 );
479 }
480
481 #[test]
482 fn zero_capacity_is_no_op_cache() {
483 let block = BlockCtx::default();
484 let h = B256::repeat_byte(0xff);
485 block.reset_recent_wasms(0);
486 assert!(!block.insert_recent_wasm(h));
487 block.reset_recent_wasms(0);
488 assert!(!block.insert_recent_wasm(h));
489 }
490}
491
492#[cfg(test)]
493mod p256_gas_tests {
494 use revm::precompile::secp256r1::{p256_verify, p256_verify_osaka};
496
497 const VALID_INPUT_HEX: &str = "4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e";
499
500 fn input_bytes() -> Vec<u8> {
501 (0..VALID_INPUT_HEX.len() / 2)
502 .map(|i| u8::from_str_radix(&VALID_INPUT_HEX[i * 2..i * 2 + 2], 16).unwrap())
503 .collect()
504 }
505
506 #[test]
507 fn rip7212_charges_3450() {
508 let input = input_bytes();
509 let out = p256_verify(&input, 10_000).expect("ok");
510 assert_eq!(out.gas_used, 3450);
511 }
512
513 #[test]
514 fn osaka_charges_6900() {
515 let input = input_bytes();
516 let out = p256_verify_osaka(&input, 10_000).expect("ok");
517 assert_eq!(out.gas_used, 6900);
518 }
519}