11use alloy_consensus:: { SidecarBuilder , SimpleCoder } ;
2+ use alloy_dyn_abi:: ErrorExt ;
23use alloy_json_abi:: Function ;
34use alloy_network:: {
45 AnyNetwork , TransactionBuilder , TransactionBuilder4844 , TransactionBuilder7702 ,
@@ -8,21 +9,26 @@ use alloy_provider::Provider;
89use alloy_rpc_types:: { AccessList , Authorization , TransactionInput , TransactionRequest } ;
910use alloy_serde:: WithOtherFields ;
1011use alloy_signer:: Signer ;
12+ use alloy_transport:: TransportError ;
13+ use cast:: traces:: identifier:: SignaturesIdentifier ;
1114use eyre:: Result ;
1215use foundry_cli:: {
1316 opts:: { CliAuthorizationList , TransactionOpts } ,
1417 utils:: { self , parse_function_args} ,
1518} ;
16- use foundry_common:: ens:: NameOrAddress ;
19+ use foundry_common:: { ens:: NameOrAddress , fmt :: format_tokens } ;
1720use foundry_config:: { Chain , Config } ;
1821use foundry_wallets:: { WalletOpts , WalletSigner } ;
22+ use itertools:: Itertools ;
23+ use serde_json:: value:: RawValue ;
24+ use std:: fmt:: Write ;
1925
2026/// Different sender kinds used by [`CastTxBuilder`].
2127pub enum SenderKind < ' a > {
2228 /// An address without signer. Used for read-only calls and transactions sent through unlocked
2329 /// accounts.
2430 Address ( Address ) ,
25- /// A refersnce to a signer.
31+ /// A reference to a signer.
2632 Signer ( & ' a WalletSigner ) ,
2733 /// An owned signer.
2834 OwnedSigner ( WalletSigner ) ,
@@ -350,12 +356,36 @@ impl<P: Provider<AnyNetwork>> CastTxBuilder<P, InputState> {
350356 }
351357
352358 if self . tx . gas . is_none ( ) {
353- self . tx . gas = Some ( self . provider . estimate_gas ( & self . tx ) . await ?) ;
359+ self . estimate_gas ( ) . await ?;
354360 }
355361
356362 Ok ( ( self . tx , self . state . func ) )
357363 }
358364
365+ /// Estimate tx gas from provider call. Tries to decode custom error if execution reverted.
366+ async fn estimate_gas ( & mut self ) -> Result < ( ) > {
367+ match self . provider . estimate_gas ( & self . tx ) . await {
368+ Ok ( estimated) => {
369+ self . tx . gas = Some ( estimated) ;
370+ Ok ( ( ) )
371+ }
372+ Err ( err) => {
373+ if let TransportError :: ErrorResp ( payload) = & err {
374+ // If execution reverted with code 3 during provider gas estimation then try
375+ // to decode custom errors and append it to the error message.
376+ if payload. code == 3 {
377+ if let Some ( data) = & payload. data {
378+ if let Ok ( Some ( decoded_error) ) = decode_execution_revert ( data) . await {
379+ eyre:: bail!( "Failed to estimate gas: {}: {}" , err, decoded_error)
380+ }
381+ }
382+ }
383+ }
384+ eyre:: bail!( "Failed to estimate gas: {}" , err)
385+ }
386+ }
387+ }
388+
359389 /// Parses the passed --auth value and sets the authorization list on the transaction.
360390 async fn resolve_auth ( & mut self , sender : SenderKind < ' _ > , tx_nonce : u64 ) -> Result < ( ) > {
361391 let Some ( auth) = self . auth . take ( ) else { return Ok ( ( ) ) } ;
@@ -401,3 +431,25 @@ where
401431 Ok ( self )
402432 }
403433}
434+
435+ /// Helper function that tries to decode custom error name and inputs from error payload data.
436+ async fn decode_execution_revert ( data : & RawValue ) -> Result < Option < String > > {
437+ if let Some ( err_data) = serde_json:: from_str :: < String > ( data. get ( ) ) ?. strip_prefix ( "0x" ) {
438+ let selector = err_data. get ( ..8 ) . unwrap ( ) ;
439+ if let Some ( known_error) = SignaturesIdentifier :: new ( Config :: foundry_cache_dir ( ) , false ) ?
440+ . write ( )
441+ . await
442+ . identify_error ( & hex:: decode ( selector) ?)
443+ . await
444+ {
445+ let mut decoded_error = known_error. name . clone ( ) ;
446+ if !known_error. inputs . is_empty ( ) {
447+ if let Ok ( error) = known_error. decode_error ( & hex:: decode ( err_data) ?) {
448+ write ! ( decoded_error, "({})" , format_tokens( & error. body) . format( ", " ) ) ?;
449+ }
450+ }
451+ return Ok ( Some ( decoded_error) )
452+ }
453+ }
454+ Ok ( None )
455+ }
0 commit comments