diff --git a/crates/cast/bin/cmd/send.rs b/crates/cast/bin/cmd/send.rs index 77b6a2cddae5..73cc0fe5a594 100644 --- a/crates/cast/bin/cmd/send.rs +++ b/crates/cast/bin/cmd/send.rs @@ -1,19 +1,21 @@ use crate::tx::{self, CastTxBuilder}; +use alloy_json_rpc::RpcError; use alloy_network::{AnyNetwork, EthereumWallet}; use alloy_provider::{Provider, ProviderBuilder}; use alloy_rpc_types::TransactionRequest; use alloy_serde::WithOtherFields; use alloy_signer::Signer; -use alloy_transport::Transport; +use alloy_transport::{Transport, TransportErrorKind}; use cast::Cast; use clap::Parser; -use eyre::Result; +use eyre::{eyre, Result}; use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, utils, }; -use foundry_common::ens::NameOrAddress; +use foundry_common::{ens::NameOrAddress, selectors::pretty_calldata}; use foundry_config::Config; +use serde_json::Value; use std::{path::PathBuf, str::FromStr}; /// CLI arguments for `cast send`. @@ -166,7 +168,59 @@ impl SendTxArgs { tx::validate_from_address(eth.wallet.from, from)?; - let (tx, _) = builder.build(&signer).await?; + let res = builder.build(&signer).await; + + let (tx, _) = match res { + Err(report) => { + // Try to downcast the error to ErrorPayload + if let Some(RpcError::ErrorResp(error_payload)) = + report.downcast_ref::>() + { + // 1. Return if it's not a revert + if error_payload.code != 3 { + return Err(report); + } + // 2. Extract the error data from the ErrorPayload + let error_data = match error_payload.data.clone() { + Some(data) => data, + None => { + return Err(report); + } + }; + + let error_data: Value = match serde_json::from_str(error_data.get()) { + Ok(data) => data, + Err(e) => { + tracing::warn!("Failed to deserialize error data: {e}"); + return Err(eyre!(e)); + } + }; + let error_data_string = error_data.as_str().unwrap_or_default(); + + // Check if it's a revert string (has an Error(string) signature) + if error_data_string.starts_with("0x08c379a0") { + return Err(report); + } + + let pretty_calldata = match pretty_calldata(error_data_string, false).await + { + Ok(pretty_calldata) => pretty_calldata, + Err(e) => { + tracing::warn!("Failed to pretty print calldata: {e}"); + return Err(report); + } + }; + + let detailed_report = report + .wrap_err(format!("Reverted with custom error: {pretty_calldata}")); + + return Err(detailed_report); + } + // If it's not an ErrorPayload, return the original error + return Err(report); + } + Ok(result) => result, + }; let wallet = EthereumWallet::from(signer); let provider = ProviderBuilder::<_, _, AnyNetwork>::default() diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index cd833f7c3ed9..fbdc6a193a59 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1412,6 +1412,74 @@ casttest!(block_number_hash, |_prj, cmd| { assert_eq!(s.trim().parse::().unwrap(), 1, "{s}") }); +// ... existing code ... + +casttest!(send_custom_error, async |prj, cmd| { + // Start anvil + let (_api, handle) = anvil::spawn(NodeConfig::test()).await; + let endpoint = handle.http_endpoint(); + + prj.add_source( + "SimpleStorage", + r#" + contract SimpleStorage { + + error ValueTooHigh(uint256 providedValue, uint256 maxValue); + + function setValueTo101() public { + revert ValueTooHigh({ providedValue: 101, maxValue: 100 }); + } +} + "#, + ) + .unwrap(); + + // Deploy the contract + let output = cmd + .forge_fuse() + .args([ + "create", + "./src/SimpleStorage.sol:SimpleStorage", + "--rpc-url", + &endpoint, + "--broadcast", + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + ]) + .assert_success() + .get_output() + .stdout_lossy(); + + let address = output.split("Deployed to: ").nth(1).unwrap().split('\n').next().unwrap().trim(); + let contract_address = address.trim(); + + // Call the function that always reverts + cmd.cast_fuse() + .args([ + "send", + contract_address, + "setValueTo101()", + "--rpc-url", + &endpoint, + "--private-key", + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + ]) + .assert_failure() + .stderr_eq(str![[r#" +Error: Reverted with custom error: + Possible methods: + - ValueTooHigh(uint256,uint256) + ------------ + [000]: 0000000000000000000000000000000000000000000000000000000000000065 + [020]: 0000000000000000000000000000000000000000000000000000000000000064 + + +Context: +- server returned an error response: error code 3: execution reverted: custom error 0x7a0e1985: ed, data: "0x7a0e198500000000000000000000000000000000000000000000000000000000000000650000000000000000000000000000000000000000000000000000000000000064" + +"#]]); +}); + casttest!(send_eip7702, async |_prj, cmd| { let (_api, handle) = anvil::spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::PragueEOF.into())))