Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cast): cast send tries to decode custom errors #8860

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 53 additions & 4 deletions crates/cast/bin/cmd/send.rs
Original file line number Diff line number Diff line change
@@ -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`.
Expand Down Expand Up @@ -171,7 +173,54 @@ 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::<RpcError<TransportErrorKind>>()
{
// 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()
Expand Down
65 changes: 65 additions & 0 deletions crates/cast/tests/cli/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1288,6 +1288,71 @@ casttest!(block_number_hash, |_prj, cmd| {
assert_eq!(s.trim().parse::<u64>().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())))
Expand Down
Loading