1use std::{
8 collections::HashMap,
9 sync::{Arc, LazyLock},
10};
11
12use crate::{tip20::TIP20Error, tip1060_storage_credits::StorageCreditsError};
13use alloy::{
14 primitives::{FixedBytes, Selector, U256},
15 sol_types::{Panic, PanicKind, SolError, SolInterface},
16};
17use alloy_evm::EvmInternalsError;
18use revm::{
19 context::journaled_state::JournalLoadError,
20 precompile::{PrecompileError, PrecompileHalt, PrecompileOutput, PrecompileResult},
21};
22use tempo_contracts::precompiles::{
23 AccountKeychainError, AddrRegistryError, FeeManagerError, NonceError, ReceivePolicyGuardError,
24 RolesAuthError, SignatureVerifierError, StablecoinDEXError, TIP20ChannelReserveError,
25 TIP20FactoryError, TIP403RegistryError, TIP1060StorageCreditsError, TIPFeeAMMError,
26 UnknownFunctionSelector, ValidatorConfigError, ValidatorConfigV2Error,
27};
28
29#[derive(
31 Debug, Clone, PartialEq, Eq, thiserror::Error, derive_more::From, derive_more::TryInto,
32)]
33pub enum TempoPrecompileError {
34 #[error("Stablecoin DEX error: {0:?}")]
36 StablecoinDEX(StablecoinDEXError),
37
38 #[error("TIP20 token error: {0:?}")]
40 TIP20(TIP20Error),
41
42 #[error("TIP20 factory error: {0:?}")]
44 TIP20Factory(TIP20FactoryError),
45
46 #[error("TIP20 channel reserve error: {0:?}")]
48 TIP20ChannelReserveError(TIP20ChannelReserveError),
49
50 #[error("Roles auth error: {0:?}")]
52 RolesAuthError(RolesAuthError),
53
54 #[error("TIP20 registry error: {0:?}")]
56 AddrRegistryError(AddrRegistryError),
57
58 #[error("TIP403 registry error: {0:?}")]
60 TIP403RegistryError(TIP403RegistryError),
61
62 #[error("TIP fee manager error: {0:?}")]
64 FeeManagerError(FeeManagerError),
65
66 #[error("TIP fee AMM error: {0:?}")]
68 TIPFeeAMMError(TIPFeeAMMError),
69
70 #[error("Tempo Transaction nonce error: {0:?}")]
72 NonceError(NonceError),
73
74 #[error("Panic({0:?})")]
76 Panic(PanicKind),
77
78 #[error("Validator config error: {0:?}")]
80 ValidatorConfigError(ValidatorConfigError),
81
82 #[error("Validator config v2 error: {0:?}")]
84 ValidatorConfigV2Error(ValidatorConfigV2Error),
85
86 #[error("Account keychain error: {0:?}")]
88 AccountKeychainError(AccountKeychainError),
89
90 #[error("Signature verifier error: {0:?}")]
92 SignatureVerifierError(SignatureVerifierError),
93
94 #[error("TIP1028 blocked transfers error: {0:?}")]
96 ReceivePolicyGuardError(ReceivePolicyGuardError),
97
98 #[error("TIP1060 storage credits error: {0:?}")]
100 TIP1060StorageCreditsError(TIP1060StorageCreditsError),
101
102 #[error("Gas limit exceeded")]
104 OutOfGas,
105
106 #[error("Unknown function selector: {0:?}")]
108 UnknownFunctionSelector([u8; 4]),
109
110 #[error("Fatal precompile error: {0:?}")]
112 #[from(skip)]
113 Fatal(String),
114}
115
116impl From<EvmInternalsError> for TempoPrecompileError {
117 fn from(value: EvmInternalsError) -> Self {
118 match value {
119 EvmInternalsError::Database(e) => Self::Fatal(e.to_string()),
120 }
121 }
122}
123
124impl From<JournalLoadError<EvmInternalsError>> for TempoPrecompileError {
125 fn from(value: JournalLoadError<EvmInternalsError>) -> Self {
126 match value {
127 JournalLoadError::DBError(e) => Self::from(e),
128 JournalLoadError::ColdLoadSkipped => Self::OutOfGas,
129 }
130 }
131}
132
133impl From<JournalLoadError<revm::context::ErasedError>> for TempoPrecompileError {
134 fn from(value: JournalLoadError<revm::context::ErasedError>) -> Self {
135 match value {
136 JournalLoadError::DBError(e) => Self::Fatal(e.to_string()),
137 JournalLoadError::ColdLoadSkipped => Self::OutOfGas,
138 }
139 }
140}
141
142pub type Result<T> = std::result::Result<T, TempoPrecompileError>;
144
145impl TempoPrecompileError {
146 pub fn selector(&self) -> FixedBytes<4> {
148 match self {
149 Self::StablecoinDEX(e) => e.selector(),
150 Self::TIP20(e) => e.selector(),
151 Self::TIP20ChannelReserveError(e) => e.selector(),
152 Self::NonceError(e) => e.selector(),
153 Self::TIP20Factory(e) => e.selector(),
154 Self::RolesAuthError(e) => e.selector(),
155 Self::AddrRegistryError(e) => e.selector(),
156 Self::TIPFeeAMMError(e) => e.selector(),
157 Self::FeeManagerError(e) => e.selector(),
158 Self::TIP403RegistryError(e) => e.selector(),
159 Self::ValidatorConfigError(e) => e.selector(),
160 Self::ValidatorConfigV2Error(e) => e.selector(),
161 Self::AccountKeychainError(e) => e.selector(),
162 Self::SignatureVerifierError(e) => e.selector(),
163 Self::ReceivePolicyGuardError(e) => e.selector(),
164 Self::TIP1060StorageCreditsError(e) => e.selector(),
165 Self::UnknownFunctionSelector(selector) => *selector,
166 Self::Panic(_) => Panic::SELECTOR,
167 Self::OutOfGas | Self::Fatal(_) => [0, 0, 0, 0],
168 }
169 .into()
170 }
171
172 pub fn is_system_error(&self) -> bool {
175 match self {
176 Self::OutOfGas | Self::Fatal(_) | Self::Panic(_) => true,
177 Self::StablecoinDEX(_)
178 | Self::TIP20(_)
179 | Self::TIP20ChannelReserveError(_)
180 | Self::NonceError(_)
181 | Self::TIP20Factory(_)
182 | Self::RolesAuthError(_)
183 | Self::AddrRegistryError(_)
184 | Self::TIPFeeAMMError(_)
185 | Self::FeeManagerError(_)
186 | Self::TIP403RegistryError(_)
187 | Self::ValidatorConfigError(_)
188 | Self::ValidatorConfigV2Error(_)
189 | Self::AccountKeychainError(_)
190 | Self::SignatureVerifierError(_)
191 | Self::ReceivePolicyGuardError(_)
192 | Self::TIP1060StorageCreditsError(_)
193 | Self::UnknownFunctionSelector(_) => false,
194 }
195 }
196
197 pub fn under_overflow() -> Self {
199 Self::Panic(PanicKind::UnderOverflow)
200 }
201
202 pub fn enum_conversion_error() -> Self {
204 Self::Panic(PanicKind::EnumConversionError)
205 }
206
207 pub fn array_oob() -> Self {
209 Self::Panic(PanicKind::ArrayOutOfBounds)
210 }
211
212 pub fn into_precompile_result(self, gas: u64, reservoir: u64) -> PrecompileResult {
218 let bytes = match self {
219 Self::StablecoinDEX(e) => e.abi_encode().into(),
220 Self::TIP20(e) => e.abi_encode().into(),
221 Self::TIP20Factory(e) => e.abi_encode().into(),
222 Self::TIP20ChannelReserveError(e) => e.abi_encode().into(),
223 Self::RolesAuthError(e) => e.abi_encode().into(),
224 Self::AddrRegistryError(e) => e.abi_encode().into(),
225 Self::TIP403RegistryError(e) => e.abi_encode().into(),
226 Self::FeeManagerError(e) => e.abi_encode().into(),
227 Self::TIPFeeAMMError(e) => e.abi_encode().into(),
228 Self::NonceError(e) => e.abi_encode().into(),
229 Self::Panic(kind) => {
230 let panic = Panic {
231 code: U256::from(kind as u32),
232 };
233
234 panic.abi_encode().into()
235 }
236 Self::ValidatorConfigError(e) => e.abi_encode().into(),
237 Self::ValidatorConfigV2Error(e) => e.abi_encode().into(),
238 Self::AccountKeychainError(e) => e.abi_encode().into(),
239 Self::SignatureVerifierError(e) => e.abi_encode().into(),
240 Self::ReceivePolicyGuardError(e) => e.abi_encode().into(),
241 Self::TIP1060StorageCreditsError(e) => e.abi_encode().into(),
242 Self::OutOfGas => {
243 return Ok(PrecompileOutput::halt(PrecompileHalt::OutOfGas, reservoir));
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::revert(gas, bytes, reservoir))
255 }
256}
257
258pub fn add_errors_to_registry<T: SolInterface>(
260 registry: &mut TempoPrecompileErrorRegistry,
261 converter: impl Fn(T) -> TempoPrecompileError + 'static + Send + Sync,
262) {
263 let converter = Arc::new(converter);
264 for selector in T::selectors() {
265 let converter = Arc::clone(&converter);
266 registry.insert(
267 selector.into(),
268 Box::new(move |data: &[u8]| {
269 T::abi_decode(data)
270 .ok()
271 .map(|error| DecodedTempoPrecompileError {
272 error: converter(error),
273 revert_bytes: data,
274 })
275 }),
276 );
277 }
278}
279
280pub struct DecodedTempoPrecompileError<'a> {
282 pub error: TempoPrecompileError,
283 pub revert_bytes: &'a [u8],
284}
285
286pub type TempoPrecompileErrorRegistry = HashMap<
288 Selector,
289 Box<dyn for<'a> Fn(&'a [u8]) -> Option<DecodedTempoPrecompileError<'a>> + Send + Sync>,
290>;
291
292pub fn error_decoder_registry() -> TempoPrecompileErrorRegistry {
294 let mut registry: TempoPrecompileErrorRegistry = HashMap::new();
295
296 add_errors_to_registry(&mut registry, TempoPrecompileError::StablecoinDEX);
297 add_errors_to_registry(&mut registry, TempoPrecompileError::TIP20);
298 add_errors_to_registry(&mut registry, TempoPrecompileError::TIP20Factory);
299 add_errors_to_registry(
300 &mut registry,
301 TempoPrecompileError::TIP20ChannelReserveError,
302 );
303 add_errors_to_registry(&mut registry, TempoPrecompileError::RolesAuthError);
304 add_errors_to_registry(&mut registry, TempoPrecompileError::AddrRegistryError);
305 add_errors_to_registry(&mut registry, TempoPrecompileError::TIP403RegistryError);
306 add_errors_to_registry(&mut registry, TempoPrecompileError::FeeManagerError);
307 add_errors_to_registry(&mut registry, TempoPrecompileError::TIPFeeAMMError);
308 add_errors_to_registry(&mut registry, TempoPrecompileError::NonceError);
309 add_errors_to_registry(&mut registry, TempoPrecompileError::ValidatorConfigError);
310 add_errors_to_registry(&mut registry, TempoPrecompileError::ValidatorConfigV2Error);
311 add_errors_to_registry(&mut registry, TempoPrecompileError::AccountKeychainError);
312 add_errors_to_registry(&mut registry, TempoPrecompileError::SignatureVerifierError);
313 add_errors_to_registry(&mut registry, TempoPrecompileError::ReceivePolicyGuardError);
314 add_errors_to_registry(
315 &mut registry,
316 TempoPrecompileError::TIP1060StorageCreditsError,
317 );
318
319 registry
320}
321
322pub static ERROR_REGISTRY: LazyLock<TempoPrecompileErrorRegistry> =
324 LazyLock::new(error_decoder_registry);
325
326pub fn decode_error<'a>(data: &'a [u8]) -> Option<DecodedTempoPrecompileError<'a>> {
330 if data.len() < 4 {
331 return None;
332 }
333
334 let selector: [u8; 4] = data[0..4].try_into().ok()?;
335 ERROR_REGISTRY
336 .get(&selector)
337 .and_then(|decoder| decoder(data))
338}
339
340pub trait IntoPrecompileResult<T> {
342 fn into_precompile_result(
344 self,
345 gas: u64,
346 reservoir: u64,
347 encode_ok: impl FnOnce(T) -> alloy::primitives::Bytes,
348 ) -> PrecompileResult;
349}
350
351impl<T> IntoPrecompileResult<T> for Result<T> {
352 fn into_precompile_result(
353 self,
354 gas: u64,
355 reservoir: u64,
356 encode_ok: impl FnOnce(T) -> alloy::primitives::Bytes,
357 ) -> PrecompileResult {
358 match self {
359 Ok(res) => Ok(PrecompileOutput::new(gas, encode_ok(res), reservoir)),
360 Err(err) => err.into_precompile_result(gas, reservoir),
361 }
362 }
363}
364
365impl StorageCreditsError for TempoPrecompileError {
366 fn out_of_gas() -> Self {
367 Self::OutOfGas
368 }
369
370 fn fatal_external() -> Self {
371 Self::Fatal("invalid storage credits state".to_string())
372 }
373}
374
375#[cfg(test)]
376mod tests {
377 use super::*;
378 use tempo_contracts::precompiles::StablecoinDEXError;
379
380 #[test]
381 fn test_add_errors_to_registry_populates_registry() {
382 let mut registry: TempoPrecompileErrorRegistry = HashMap::new();
383
384 assert!(registry.is_empty());
385
386 add_errors_to_registry(&mut registry, TempoPrecompileError::StablecoinDEX);
387
388 assert!(!registry.is_empty());
389
390 let order_not_found_selector = StablecoinDEXError::order_does_not_exist().selector();
391 assert!(
392 registry.contains_key(&order_not_found_selector),
393 "Registry should contain OrderDoesNotExist selector"
394 );
395 }
396
397 #[test]
398 fn test_error_decoder_registry_is_not_empty() {
399 let registry = error_decoder_registry();
400
401 assert!(
402 !registry.is_empty(),
403 "error_decoder_registry should return a populated registry"
404 );
405
406 let dex_selector = StablecoinDEXError::order_does_not_exist().selector();
407 assert!(registry.contains_key(&dex_selector));
408 }
409
410 #[test]
411 fn test_decode_error_returns_some_for_valid_error() {
412 let error = StablecoinDEXError::order_does_not_exist();
413 let encoded = error.abi_encode();
414
415 let result = decode_error(&encoded);
416 assert!(
417 result.is_some(),
418 "decode_error should return Some for valid error"
419 );
420
421 let decoded = result.unwrap();
422 assert!(matches!(
423 decoded.error,
424 TempoPrecompileError::StablecoinDEX(StablecoinDEXError::OrderDoesNotExist(_))
425 ));
426 }
427
428 #[test]
429 fn test_decode_error_data_length_boundary() {
430 let result = decode_error(&[]);
432 assert!(result.is_none(), "Empty data should return None");
433
434 let result = decode_error(&[0x01]);
436 assert!(result.is_none(), "1 byte should return None");
437
438 let result = decode_error(&[0x01, 0x02]);
440 assert!(result.is_none(), "2 bytes should return None");
441
442 let result = decode_error(&[0x01, 0x02, 0x03]);
444 assert!(result.is_none(), "3 bytes should return None");
445
446 let result = decode_error(&[0x00, 0x00, 0x00, 0x00]);
448 assert!(
449 result.is_none(),
450 "Unknown 4-byte selector should return None"
451 );
452
453 let error = StablecoinDEXError::order_does_not_exist();
455 let encoded = error.abi_encode();
456 let result = decode_error(&encoded);
457 assert!(
458 result.is_some(),
459 "Valid error at 4+ bytes should return Some"
460 );
461 }
462
463 #[test]
464 fn test_into_precompile_result_revert() {
465 let error = TempoPrecompileError::StablecoinDEX(StablecoinDEXError::order_does_not_exist());
466 let result = error.into_precompile_result(0, 0);
467
468 let output = result.expect("business-logic revert should be Ok");
469 assert!(output.status.is_revert());
470 }
471
472 #[test]
473 fn test_into_precompile_result_trait_success() {
474 let result: Result<u64> = Ok(42);
475 let precompile_result = result.into_precompile_result(0, 0, |val| {
476 alloy::primitives::Bytes::from(val.to_be_bytes().to_vec())
477 });
478
479 let output = precompile_result.expect("success should be Ok");
480 assert!(output.status.is_success());
481 }
482
483 #[test]
484 fn test_decode_error_with_tip20_error() {
485 let error = TIP20Error::insufficient_allowance();
487 let encoded = error.abi_encode();
488
489 let result = decode_error(&encoded);
490 assert!(result.is_some(), "Should decode TIP20 errors");
491
492 let decoded = result.unwrap();
493 match decoded.error {
495 TempoPrecompileError::TIP20(_) => {}
496 other => panic!("Expected TIP20 error, got {other:?}"),
497 }
498 }
499}