arb_precompiles/error.rs
1use core::error::Error;
2use std::borrow::Cow;
3
4use alloy_primitives::Bytes;
5use arb_storage_errors::StorageError;
6use revm::precompile::{PrecompileError, PrecompileOutput, PrecompileResult};
7
8/// Errors raised by Arbitrum precompiles.
9///
10/// `Revert` and `OutOfGas` are user-visible: the transaction reverts and the
11/// surrounding block continues. `Fatal` indicates an infrastructure failure
12/// (database error, broken storage invariant) and must abort the block.
13#[derive(thiserror::Error, Debug)]
14pub enum ArbPrecompileError {
15 /// User-visible revert. The block continues; the tx reverts.
16 #[error("revert: {data:?}")]
17 Revert {
18 /// Optional Solidity custom-error selector that prefixes `data`.
19 selector: Option<[u8; 4]>,
20 /// ABI-encoded revert payload returned to the caller.
21 data: Bytes,
22 /// Precompile gas accounted up to the point of the revert.
23 gas_used: u64,
24 },
25
26 /// Out of gas during precompile execution. User-visible.
27 #[error("out of gas")]
28 OutOfGas,
29
30 /// Infrastructure failure. Maps to `PrecompileError::Fatal` so the block
31 /// aborts instead of producing a user-visible revert.
32 #[error("fatal precompile error: {0}")]
33 Fatal(#[source] Box<dyn Error + Send + Sync>),
34}
35
36impl ArbPrecompileError {
37 /// Wraps any [`Error`] as a [`ArbPrecompileError::Fatal`].
38 pub fn fatal<E>(err: E) -> Self
39 where
40 E: Error + Send + Sync + 'static,
41 {
42 Self::Fatal(Box::new(err))
43 }
44
45 /// Builds a [`Revert`](Self::Revert) carrying empty data and the
46 /// current precompile-gas accumulator.
47 pub fn empty_revert(gas_used: u64) -> Self {
48 Self::Revert {
49 selector: None,
50 data: Bytes::new(),
51 gas_used,
52 }
53 }
54
55 /// Converts this error into a [`PrecompileResult`], capped by `gas_limit`.
56 ///
57 /// `Revert` produces a successful `PrecompileOutput::new_reverted` carrying
58 /// the configured selector and payload. `OutOfGas` and `Fatal` become
59 /// `Err`-variant `PrecompileError`s.
60 pub fn into_precompile_result(self, gas_limit: u64) -> PrecompileResult {
61 match self {
62 Self::Revert {
63 selector,
64 data,
65 gas_used,
66 } => {
67 let payload = match selector {
68 Some(sel) => {
69 let mut bytes = Vec::with_capacity(4 + data.len());
70 bytes.extend_from_slice(&sel);
71 bytes.extend_from_slice(&data);
72 Bytes::from(bytes)
73 }
74 None => data,
75 };
76 Ok(PrecompileOutput::new_reverted(
77 gas_used.min(gas_limit),
78 payload,
79 ))
80 }
81 other => Err(other.into()),
82 }
83 }
84}
85
86impl From<StorageError> for ArbPrecompileError {
87 fn from(err: StorageError) -> Self {
88 Self::Fatal(Box::new(err))
89 }
90}
91
92impl From<ArbPrecompileError> for PrecompileError {
93 fn from(err: ArbPrecompileError) -> Self {
94 match err {
95 ArbPrecompileError::Revert { .. } => PrecompileError::Other(Cow::Borrowed("revert")),
96 ArbPrecompileError::OutOfGas => PrecompileError::OutOfGas,
97 ArbPrecompileError::Fatal(source) => PrecompileError::Fatal(source.to_string()),
98 }
99 }
100}