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}