From 047db11d3328c8ab2b01a3ecd5944b1ea869b9f1 Mon Sep 17 00:00:00 2001 From: rplusq Date: Thu, 12 Sep 2024 21:10:12 +0100 Subject: [PATCH 1/6] 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 77b6a2cddae5..030b336d6cab 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,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 7291981235a871750df3f847443fd283f84f2423 Mon Sep 17 00:00:00 2001 From: rplusq Date: Mon, 16 Sep 2024 13:21:07 +0100 Subject: [PATCH 2/6] 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 030b336d6cab..f6ac3c5e6402 100644 --- a/crates/cast/bin/cmd/send.rs +++ b/crates/cast/bin/cmd/send.rs @@ -168,8 +168,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 1aa3f5a880523bc4a88a6d74a7fb47e992b59710 Mon Sep 17 00:00:00 2001 From: rplusq Date: Wed, 25 Sep 2024 22:14:49 +0100 Subject: [PATCH 3/6] 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 f3d04b09456b..0d5351ed5e75 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1411,6 +1411,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()))) From 593886f7488b40bf0335573ce5acc53a279f3508 Mon Sep 17 00:00:00 2001 From: rplusq Date: Fri, 29 Nov 2024 14:21:56 +0000 Subject: [PATCH 4/6] chore: update based on removal of "custom error" from the message --- crates/cast/bin/cmd/send.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/cast/bin/cmd/send.rs b/crates/cast/bin/cmd/send.rs index f6ac3c5e6402..3c5a1e59d47c 100644 --- a/crates/cast/bin/cmd/send.rs +++ b/crates/cast/bin/cmd/send.rs @@ -176,8 +176,10 @@ impl SendTxArgs { 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") { + // 1. Return if it's not a revert / custom error + if error_payload.code != 3 || + !error_payload.message.ends_with("execution reverted") + { return Err(report); } // 2. Extract the error data from the ErrorPayload From b87dbaa511aa2fad44c3cd35e7ee1bd6401c565f Mon Sep 17 00:00:00 2001 From: rplusq Date: Fri, 29 Nov 2024 17:32:08 +0000 Subject: [PATCH 5/6] test: fix create with broadcast --- crates/cast/tests/cli/main.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index 0d5351ed5e75..ab8ffd3cd5e4 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1441,6 +1441,7 @@ casttest!(send_custom_error, async |prj, cmd| { "./src/SimpleStorage.sol:SimpleStorage", "--rpc-url", &endpoint, + "--broadcast", "--private-key", "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", ]) @@ -1449,7 +1450,6 @@ casttest!(send_custom_error, async |prj, cmd| { .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 @@ -1465,14 +1465,17 @@ casttest!(send_custom_error, async |prj, cmd| { ]) .assert_failure() .stderr_eq(str![[r#" -Error: -Reverted with custom error: +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" + "#]]); }); From fe50a7e40089bdc11987976f6e8b324902f3830b Mon Sep 17 00:00:00 2001 From: rplusq Date: Fri, 29 Nov 2024 17:52:36 +0000 Subject: [PATCH 6/6] fix: use Error(string) signature to verify custom errors --- crates/cast/bin/cmd/send.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/cast/bin/cmd/send.rs b/crates/cast/bin/cmd/send.rs index 3c5a1e59d47c..73cc0fe5a594 100644 --- a/crates/cast/bin/cmd/send.rs +++ b/crates/cast/bin/cmd/send.rs @@ -176,10 +176,8 @@ impl SendTxArgs { if let Some(RpcError::ErrorResp(error_payload)) = report.downcast_ref::>() { - // 1. Return if it's not a revert / custom error - if error_payload.code != 3 || - !error_payload.message.ends_with("execution reverted") - { + // 1. Return if it's not a revert + if error_payload.code != 3 { return Err(report); } // 2. Extract the error data from the ErrorPayload @@ -199,6 +197,11 @@ impl SendTxArgs { }; 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,