stylus_tools/
error.rs

1// Copyright 2025, Offchain Labs, Inc.
2// For licensing, see https://github.com/OffchainLabs/stylus-sdk-rs/blob/main/licenses/COPYRIGHT.md
3
4use alloy::sol_types::SolInterface;
5
6pub type Result<T, E = Error> = std::result::Result<T, E>;
7
8#[derive(Debug, thiserror::Error)]
9pub enum Error {
10    #[error("from utf8 error: {0}")]
11    FromUtf8(#[from] std::string::FromUtf8Error),
12    #[error("io error: {0}")]
13    Io(#[from] std::io::Error),
14
15    #[error("rpc error: {0}")]
16    Rpc(#[from] alloy::transports::RpcError<alloy::transports::TransportErrorKind>),
17    #[error("cargo metadata error: {0}")]
18    CargoMetadata(#[from] cargo_metadata::Error),
19    #[error("json error: {0}")]
20    Json(#[from] serde_json::Error),
21    #[error("toml serialize error: {0}")]
22    TomlSerialize(#[from] toml::ser::Error),
23    #[error("toml deserialize error: {0}")]
24    TomlDeserialize(#[from] toml::de::Error),
25
26    // TODO: better error formatting
27    #[error("command failed (exit code: {code:?})", code = .0.exit_code)]
28    CommandFailure(crate::core::message::ProcessOutput),
29    #[error("{0}")]
30    Build(#[from] crate::core::build::BuildError),
31    #[error("{0}")]
32    Toolchain(#[from] crate::utils::toolchain::ToolchainError),
33}
34
35#[derive(Debug, thiserror::Error)]
36pub enum CommandError {
37    #[error("io error: {0}")]
38    Io(#[from] std::io::Error),
39    #[error("{0}")]
40    CommandFailure(#[from] CommandFailure),
41}
42
43#[derive(Debug, thiserror::Error)]
44#[error("command failed (exit code: {code:?}", code = .0.exit_code)]
45pub struct CommandFailure(crate::core::message::ProcessOutput);
46
47impl CommandFailure {
48    pub fn check(
49        process_name: impl Into<String>,
50        output: std::process::Output,
51    ) -> Result<String, Self> {
52        let process_output = crate::core::message::ProcessOutput {
53            process_name: process_name.into(),
54            stdout: String::from_utf8_lossy(&output.stdout).to_string(),
55            stderr: String::from_utf8_lossy(&output.stderr).to_string(),
56            exit_code: output.status.code(),
57        };
58        if output.status.success() {
59            Ok(process_output.stdout)
60        } else {
61            Err(CommandFailure(process_output))
62        }
63    }
64}
65
66#[derive(Debug, thiserror::Error)]
67pub enum ContractDecodeError {
68    #[error("failed to send tx: {0:?}")]
69    FailedToSendTx(alloy::contract::Error),
70    #[error("no error payload found in response: {0:?}")]
71    NoErrorPayload(alloy::transports::RpcError<alloy::transports::TransportErrorKind>),
72    #[error("failed to decode error: {0:?}")]
73    FailedToDecode(alloy::rpc::json_rpc::ErrorPayload),
74}
75
76pub fn decode_contract_error<E: SolInterface>(
77    e: alloy::contract::Error,
78) -> Result<E, ContractDecodeError> {
79    let alloy::contract::Error::TransportError(tperr) = e else {
80        return Err(ContractDecodeError::FailedToSendTx(e));
81    };
82    let Some(err_resp) = tperr.as_error_resp() else {
83        return Err(ContractDecodeError::NoErrorPayload(tperr));
84    };
85    let Some(errs) = err_resp.as_decoded_interface_error::<E>() else {
86        return Err(ContractDecodeError::FailedToDecode(err_resp.clone()));
87    };
88    Ok(errs)
89}