From 5a131bbac7433438c6aef92278d4f88c6a46bc5e Mon Sep 17 00:00:00 2001 From: rplusq Date: Thu, 12 Sep 2024 21:10:12 +0100 Subject: [PATCH 1/3] feat(cast): cast send tries to decode custom errors Co-authored-by: Howard --- crates/cast/bin/cmd/send.rs | 59 ++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/crates/cast/bin/cmd/send.rs b/crates/cast/bin/cmd/send.rs index cf3582fe03a6..840fb06595e7 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::{cli_warn, ens::NameOrAddress}; +use foundry_common::{cli_warn, ens::NameOrAddress, selectors::pretty_calldata}; use foundry_config::Config; +use serde_json::Value; use std::{path::PathBuf, str::FromStr}; /// CLI arguments for `cast send`. @@ -171,7 +173,56 @@ impl SendTxArgs { tx::validate_from_address(eth.wallet.from, from)?; - let (tx, _) = builder.build(&signer).await?; + println!("Sending transaction"); + + 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 custom error + if !error_payload.message.contains("execution reverted: custom error") { + 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(); + + 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() From a0194aac73bb24acf28f9d8f63bd6640f550284e Mon Sep 17 00:00:00 2001 From: rplusq Date: Mon, 16 Sep 2024 13:21:07 +0100 Subject: [PATCH 2/3] chore: remove println --- crates/cast/bin/cmd/send.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/cast/bin/cmd/send.rs b/crates/cast/bin/cmd/send.rs index 840fb06595e7..beb7385d8bb4 100644 --- a/crates/cast/bin/cmd/send.rs +++ b/crates/cast/bin/cmd/send.rs @@ -173,8 +173,6 @@ impl SendTxArgs { tx::validate_from_address(eth.wallet.from, from)?; - println!("Sending transaction"); - let res = builder.build(&signer).await; let (tx, _) = match res { From c6acc83e8c224d2e64e276ba20d572041151d8c7 Mon Sep 17 00:00:00 2001 From: rplusq Date: Wed, 25 Sep 2024 22:14:49 +0100 Subject: [PATCH 3/3] test(cast): cast send custom error --- crates/cast/tests/cli/main.rs | 65 +++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index df566d1144c0..9f198a5393db 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1288,6 +1288,71 @@ 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, + "--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 +... +"#]]); +}); + casttest!(send_eip7702, async |_prj, cmd| { let (_api, handle) = anvil::spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::PragueEOF.into())))