hotshot_contract_adapter/
evm.rs

1use alloy::{
2    sol_types::SolInterface,
3    transports::{RpcError, TransportErrorKind},
4};
5
6pub trait DecodeRevert<T> {
7    fn maybe_decode_revert<E: SolInterface + std::fmt::Debug>(self) -> anyhow::Result<T>;
8}
9
10impl<T> DecodeRevert<T> for Result<T, alloy::contract::Error> {
11    fn maybe_decode_revert<E: SolInterface + std::fmt::Debug>(self) -> anyhow::Result<T> {
12        match self {
13            Ok(ret) => Ok(ret),
14            Err(err) => {
15                let msg = match err.as_decoded_interface_error::<E>() {
16                    Some(e) => format!("{e:?}"),
17                    None => format!("{err:?}"),
18                };
19                Err(anyhow::anyhow!(msg))
20            },
21        }
22    }
23}
24
25impl<T> DecodeRevert<T> for Result<T, RpcError<TransportErrorKind>> {
26    fn maybe_decode_revert<E: SolInterface + std::fmt::Debug>(self) -> anyhow::Result<T> {
27        match self {
28            Ok(ret) => Ok(ret),
29            Err(RpcError::ErrorResp(payload)) => match payload.as_decoded_interface_error::<E>() {
30                Some(e) => Err(anyhow::anyhow!("{e:?}")),
31                None => Err(anyhow::anyhow!("{payload}")),
32            },
33            Err(err) => Err(anyhow::anyhow!("{err:?}")),
34        }
35    }
36}
37
38#[cfg(test)]
39mod test {
40    use alloy::{
41        primitives::{Address, U256},
42        providers::{Provider, ProviderBuilder},
43        rpc::types::{TransactionInput, TransactionRequest},
44        sol_types::SolCall,
45    };
46
47    use super::*;
48    use crate::sol_types::EspToken::{self, transferCall, EspTokenErrors};
49
50    #[tokio::test]
51    async fn test_decode_revert_contract_error() -> anyhow::Result<()> {
52        let provider = ProviderBuilder::new().connect_anvil_with_wallet();
53
54        let token = EspToken::deploy(&provider).await?;
55        let err = token
56            .transfer(Address::random(), U256::MAX)
57            .send()
58            .await
59            .maybe_decode_revert::<EspTokenErrors>()
60            .unwrap_err();
61        assert!(err.to_string().contains("ERC20InsufficientBalance"));
62
63        Ok(())
64    }
65
66    #[tokio::test]
67    async fn test_decode_revert_rpc_error() -> anyhow::Result<()> {
68        let provider = ProviderBuilder::new().connect_anvil_with_wallet();
69
70        let token = EspToken::deploy(&provider).await?;
71        let call = transferCall {
72            to: Address::random(),
73            value: U256::MAX,
74        };
75        let tx = TransactionRequest::default()
76            .to(*token.address())
77            .input(TransactionInput::new(call.abi_encode().into()));
78
79        let err = provider
80            .send_transaction(tx)
81            .await
82            .maybe_decode_revert::<EspTokenErrors>()
83            .unwrap_err();
84        assert!(err.to_string().contains("ERC20InsufficientBalance"));
85
86        Ok(())
87    }
88}