tempo_precompiles/
error.rs

1use std::{
2    collections::HashMap,
3    sync::{Arc, LazyLock},
4};
5
6use crate::tip20::TIP20Error;
7use alloy::{
8    primitives::{Selector, U256},
9    sol_types::{Panic, PanicKind, SolError, SolInterface},
10};
11use revm::precompile::{PrecompileError, PrecompileOutput, PrecompileResult};
12use tempo_contracts::precompiles::{
13    AccountKeychainError, FeeManagerError, NonceError, RolesAuthError, StablecoinExchangeError,
14    TIP20RewardsRegistryError, TIP403RegistryError, TIPAccountRegistrarError, TIPFeeAMMError,
15    UnknownFunctionSelector, ValidatorConfigError,
16};
17
18// TODO: add error type for overflow/underflow
19/// Top-level error type for all Tempo precompile operations
20#[derive(
21    Debug, Clone, PartialEq, Eq, thiserror::Error, derive_more::From, derive_more::TryInto,
22)]
23pub enum TempoPrecompileError {
24    /// Error from stablecoin exchange
25    #[error("Stablecoin exchange error: {0:?}")]
26    StablecoinExchange(StablecoinExchangeError),
27
28    /// Error from TIP20 token
29    #[error("TIP20 token error: {0:?}")]
30    TIP20(TIP20Error),
31
32    /// Error from TIP20RewardsRegistry
33    #[error("TIP20 rewards registry error: {0:?}")]
34    TIP20RewardsRegistry(TIP20RewardsRegistryError),
35
36    /// Error from roles auth
37    #[error("Roles auth error: {0:?}")]
38    RolesAuthError(RolesAuthError),
39
40    /// Error from 403 registry
41    #[error("TIP403 registry error: {0:?}")]
42    TIP403RegistryError(TIP403RegistryError),
43
44    /// Error from TIP  fee manager
45    #[error("TIP fee manager error: {0:?}")]
46    FeeManagerError(FeeManagerError),
47
48    /// Error from TIP fee AMM
49    #[error("TIP fee AMM error: {0:?}")]
50    TIPFeeAMMError(TIPFeeAMMError),
51
52    /// Error from TIP account registrar
53    #[error("TIP account registrar error: {0:?}")]
54    TIPAccountRegistrarError(TIPAccountRegistrarError),
55
56    /// Error from Tempo Transaction nonce manager
57    #[error("Tempo Transaction nonce error: {0:?}")]
58    NonceError(NonceError),
59
60    #[error("Panic({0:?})")]
61    Panic(PanicKind),
62
63    /// Error from validator config
64    #[error("Validator config error: {0:?}")]
65    ValidatorConfigError(ValidatorConfigError),
66
67    /// Error from account keychain precompile
68    #[error("Account keychain error: {0:?}")]
69    AccountKeychainError(AccountKeychainError),
70
71    #[error("Gas limit exceeded")]
72    OutOfGas,
73
74    #[error("Unknown function selector: {0:?}")]
75    UnknownFunctionSelector([u8; 4]),
76
77    #[error("Fatal precompile error: {0:?}")]
78    #[from(skip)]
79    Fatal(String),
80}
81
82/// Result type alias for Tempo precompile operations
83pub type Result<T> = std::result::Result<T, TempoPrecompileError>;
84
85impl TempoPrecompileError {
86    pub fn under_overflow() -> Self {
87        Self::Panic(PanicKind::UnderOverflow)
88    }
89}
90
91pub fn add_errors_to_registry<T: SolInterface>(
92    registry: &mut TempoPrecompileErrorRegistry,
93    converter: impl Fn(T) -> TempoPrecompileError + 'static + Send + Sync,
94) {
95    let converter = Arc::new(converter);
96    for selector in T::selectors() {
97        let converter = Arc::clone(&converter);
98        registry.insert(
99            selector.into(),
100            Box::new(move |data: &[u8]| {
101                T::abi_decode(data)
102                    .ok()
103                    .map(|error| DecodedTempoPrecompileError {
104                        error: converter(error),
105                        revert_bytes: data,
106                    })
107            }),
108        );
109    }
110}
111
112pub struct DecodedTempoPrecompileError<'a> {
113    pub error: TempoPrecompileError,
114    pub revert_bytes: &'a [u8],
115}
116
117pub type TempoPrecompileErrorRegistry = HashMap<
118    Selector,
119    Box<dyn for<'a> Fn(&'a [u8]) -> Option<DecodedTempoPrecompileError<'a>> + Send + Sync>,
120>;
121
122/// Returns a HashMap mapping error selectors to their decoder functions
123/// The decoder returns a `TempoPrecompileError` variant for the decoded error
124pub fn error_decoder_registry() -> TempoPrecompileErrorRegistry {
125    let mut registry: TempoPrecompileErrorRegistry = HashMap::new();
126
127    add_errors_to_registry(&mut registry, TempoPrecompileError::StablecoinExchange);
128    add_errors_to_registry(&mut registry, TempoPrecompileError::TIP20);
129    add_errors_to_registry(&mut registry, TempoPrecompileError::TIP20RewardsRegistry);
130    add_errors_to_registry(&mut registry, TempoPrecompileError::RolesAuthError);
131    add_errors_to_registry(&mut registry, TempoPrecompileError::TIP403RegistryError);
132    add_errors_to_registry(&mut registry, TempoPrecompileError::FeeManagerError);
133    add_errors_to_registry(&mut registry, TempoPrecompileError::TIPFeeAMMError);
134    add_errors_to_registry(
135        &mut registry,
136        TempoPrecompileError::TIPAccountRegistrarError,
137    );
138    add_errors_to_registry(&mut registry, TempoPrecompileError::NonceError);
139    add_errors_to_registry(&mut registry, TempoPrecompileError::ValidatorConfigError);
140    add_errors_to_registry(&mut registry, TempoPrecompileError::AccountKeychainError);
141
142    registry
143}
144
145pub static ERROR_REGISTRY: LazyLock<TempoPrecompileErrorRegistry> =
146    LazyLock::new(error_decoder_registry);
147
148/// Decode an error from raw bytes using the selector
149pub fn decode_error<'a>(data: &'a [u8]) -> Option<DecodedTempoPrecompileError<'a>> {
150    if data.len() < 4 {
151        return None;
152    }
153
154    let selector: [u8; 4] = data[0..4].try_into().ok()?;
155    ERROR_REGISTRY
156        .get(&selector)
157        .and_then(|decoder| decoder(data))
158}
159
160/// Extension trait to convert `Result<T, TempoPrecompileError` into `PrecompileResult`
161pub trait IntoPrecompileResult<T> {
162    fn into_precompile_result(
163        self,
164        gas: u64,
165        encode_ok: impl FnOnce(T) -> alloy::primitives::Bytes,
166    ) -> PrecompileResult;
167}
168
169impl<T> IntoPrecompileResult<T> for Result<T> {
170    fn into_precompile_result(
171        self,
172        gas: u64,
173        encode_ok: impl FnOnce(T) -> alloy::primitives::Bytes,
174    ) -> PrecompileResult {
175        use TempoPrecompileError as TPErr;
176
177        match self {
178            Ok(res) => Ok(PrecompileOutput::new(gas, encode_ok(res))),
179            Err(err) => {
180                let bytes = match err {
181                    TPErr::StablecoinExchange(e) => e.abi_encode().into(),
182                    TPErr::TIP20(e) => e.abi_encode().into(),
183                    TPErr::TIP20RewardsRegistry(e) => e.abi_encode().into(),
184                    TPErr::RolesAuthError(e) => e.abi_encode().into(),
185                    TPErr::TIP403RegistryError(e) => e.abi_encode().into(),
186                    TPErr::TIPAccountRegistrarError(e) => e.abi_encode().into(),
187                    TPErr::FeeManagerError(e) => e.abi_encode().into(),
188                    TPErr::TIPFeeAMMError(e) => e.abi_encode().into(),
189                    TPErr::NonceError(e) => e.abi_encode().into(),
190                    TPErr::Panic(kind) => {
191                        let panic = Panic {
192                            code: U256::from(kind as u32),
193                        };
194
195                        panic.abi_encode().into()
196                    }
197                    TPErr::ValidatorConfigError(e) => e.abi_encode().into(),
198                    TPErr::AccountKeychainError(e) => e.abi_encode().into(),
199                    TPErr::OutOfGas => {
200                        return Err(PrecompileError::OutOfGas);
201                    }
202                    TPErr::UnknownFunctionSelector(selector) => UnknownFunctionSelector {
203                        selector: selector.into(),
204                    }
205                    .abi_encode()
206                    .into(),
207                    TPErr::Fatal(msg) => {
208                        return Err(PrecompileError::Fatal(msg));
209                    }
210                };
211                Ok(PrecompileOutput::new_reverted(gas, bytes))
212            }
213        }
214    }
215}
216
217impl<T> IntoPrecompileResult<T> for TempoPrecompileError {
218    fn into_precompile_result(
219        self,
220        gas: u64,
221        _encode_ok: impl FnOnce(T) -> alloy::primitives::Bytes,
222    ) -> PrecompileResult {
223        let bytes = match self {
224            Self::StablecoinExchange(e) => e.abi_encode().into(),
225            Self::TIP20(e) => e.abi_encode().into(),
226            Self::TIP20RewardsRegistry(e) => e.abi_encode().into(),
227            Self::RolesAuthError(e) => e.abi_encode().into(),
228            Self::TIP403RegistryError(e) => e.abi_encode().into(),
229            Self::TIPAccountRegistrarError(e) => e.abi_encode().into(),
230            Self::FeeManagerError(e) => e.abi_encode().into(),
231            Self::TIPFeeAMMError(e) => e.abi_encode().into(),
232            Self::NonceError(e) => e.abi_encode().into(),
233            Self::AccountKeychainError(e) => e.abi_encode().into(),
234            Self::Panic(kind) => {
235                let panic = Panic {
236                    code: U256::from(kind as u32),
237                };
238
239                panic.abi_encode().into()
240            }
241            Self::ValidatorConfigError(e) => e.abi_encode().into(),
242            Self::OutOfGas => {
243                return Err(PrecompileError::OutOfGas);
244            }
245            Self::UnknownFunctionSelector(selector) => UnknownFunctionSelector {
246                selector: selector.into(),
247            }
248            .abi_encode()
249            .into(),
250            Self::Fatal(msg) => {
251                return Err(PrecompileError::Fatal(msg));
252            }
253        };
254        Ok(PrecompileOutput::new_reverted(gas, bytes))
255    }
256}