1
+ use clap:: Parser ;
2
+ use eyre:: { eyre, Result } ;
3
+ use foundry_cli:: opts:: { CompilerArgs , CoreBuildArgs } ;
4
+ use foundry_common:: compile:: ProjectCompiler ;
5
+ use foundry_compilers:: artifacts:: output_selection:: ContractOutputSelection ;
6
+ use std:: fmt;
7
+
8
+ use alloy_dyn_abi:: ErrorExt ;
9
+ use alloy_json_abi:: Error ;
10
+ use alloy_sol_types:: { Panic , Revert , SolError } ;
11
+
12
+ macro_rules! spaced_print {
13
+ ( $( $arg: tt) * ) => {
14
+ println!( $( $arg) * ) ;
15
+ println!( ) ;
16
+ } ;
17
+ }
18
+
19
+ #[ derive( Debug , Clone ) ]
20
+ enum RevertType {
21
+ Revert ,
22
+ Panic ,
23
+ /// The 4 byte signature of the error
24
+ Custom ( [ u8 ; 4 ] ) ,
25
+ }
26
+
27
+ impl fmt:: Display for RevertType {
28
+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
29
+ match self {
30
+ RevertType :: Revert => write ! ( f, "Revert" ) ,
31
+ RevertType :: Panic => write ! ( f, "Panic" ) ,
32
+ RevertType :: Custom ( selector) => write ! ( f, "Custom(0x{})" , hex:: encode( selector) ) ,
33
+ }
34
+ }
35
+ }
36
+
37
+ impl From < [ u8 ; 4 ] > for RevertType {
38
+ fn from ( selector : [ u8 ; 4 ] ) -> Self {
39
+ match selector {
40
+ Revert :: SELECTOR => RevertType :: Revert ,
41
+ Panic :: SELECTOR => RevertType :: Panic ,
42
+ _ => RevertType :: Custom ( selector) ,
43
+ }
44
+ }
45
+ }
46
+
47
+ /// CLI arguments for `forge inspect`.
48
+ #[ derive( Clone , Debug , Parser ) ]
49
+ pub struct DecodeError {
50
+ /// The hex encoded revert data
51
+ revert_data : String ,
52
+
53
+ /// All build arguments are supported
54
+ #[ command( flatten) ]
55
+ build : CoreBuildArgs ,
56
+ }
57
+
58
+ impl DecodeError {
59
+ pub fn run ( self ) -> Result < ( ) > {
60
+ let DecodeError { revert_data, build } = self ;
61
+
62
+ if revert_data. len ( ) < 8 {
63
+ return Err ( eyre ! ( "Revert data is too short" ) ) ;
64
+ }
65
+
66
+ // convert to bytes and get the selector
67
+ let data_bytes = hex:: decode ( revert_data. trim_start_matches ( "0x" ) ) ?;
68
+ let selector: [ u8 ; 4 ] = data_bytes[ ..4 ] . try_into ( ) ?;
69
+
70
+ trace ! ( target: "forge" , "running forge decode-error on error type {}" , RevertType :: from( selector) ) ;
71
+
72
+ // Make sure were gonna get the abi out
73
+ let mut cos = build. compiler . extra_output ;
74
+ if !cos. iter ( ) . any ( |selected| * selected == ContractOutputSelection :: Abi ) {
75
+ cos. push ( ContractOutputSelection :: Abi ) ;
76
+ }
77
+
78
+ // Build modified Args
79
+ let modified_build_args = CoreBuildArgs {
80
+ compiler : CompilerArgs { extra_output : cos, ..build. compiler } ,
81
+ ..build
82
+ } ;
83
+
84
+ // Build the project
85
+ if let Ok ( project) = modified_build_args. project ( ) {
86
+ let compiler = ProjectCompiler :: new ( ) . quiet ( true ) ;
87
+ let output = compiler. compile ( & project) ?;
88
+
89
+ // search the project for the error
90
+ //
91
+ // we want to search even it matches the builtin errors because there could be a collision
92
+ let found_errs = output
93
+ . artifacts ( )
94
+ . filter_map ( |( name, artifact) | {
95
+ Some ( (
96
+ name,
97
+ artifact. abi . as_ref ( ) ?. errors . iter ( ) . find_map ( |( _, err) | {
98
+ // check if we have an error with a matching selector
99
+ // there can only be one per artifact
100
+ err. iter ( ) . find ( |err| err. selector ( ) == selector)
101
+ } ) ?,
102
+ ) )
103
+ } )
104
+ . collect :: < Vec < _ > > ( ) ;
105
+
106
+ if !found_errs. is_empty ( ) {
107
+ pretty_print_custom_errros ( found_errs, & data_bytes) ;
108
+ }
109
+ } else {
110
+ tracing:: trace!( "No project found" )
111
+ }
112
+
113
+ // try to decode the builtin errors if it matches
114
+ pretty_print_builtin_errors ( selector. into ( ) , & data_bytes) ;
115
+
116
+ Ok ( ( ) )
117
+ }
118
+ }
119
+
120
+ fn pretty_print_custom_errros ( found_errs : Vec < ( String , & Error ) > , data : & [ u8 ] ) {
121
+ let mut failures = Vec :: with_capacity ( found_errs. len ( ) ) ;
122
+ let mut did_succeed = false ;
123
+ for ( artifact, dyn_err) in found_errs {
124
+ match dyn_err. decode_error ( data) {
125
+ Ok ( decoded) => {
126
+ did_succeed = true ;
127
+
128
+ print_line ( ) ;
129
+ println ! ( "Artifact: {}" , artifact) ;
130
+ println ! ( "Error Name: {}" , dyn_err. name) ;
131
+ for ( param, value) in dyn_err. inputs . iter ( ) . zip ( decoded. body . iter ( ) ) {
132
+ println ! ( " {}: {:?}" , param. name, value) ;
133
+ }
134
+ println ! ( "" ) ;
135
+ }
136
+ Err ( e) => {
137
+ tracing:: error!( "Error decoding dyn err: {}" , e) ;
138
+ failures. push ( format ! ( "decoding data for {} failed" , dyn_err. signature( ) ) ) ;
139
+ }
140
+ } ;
141
+ }
142
+
143
+ if !did_succeed {
144
+ for failure in failures {
145
+ tracing:: error!( "{}" , failure) ;
146
+ }
147
+ }
148
+ }
149
+
150
+ fn pretty_print_builtin_errors ( revert_type : RevertType , data : & [ u8 ] ) {
151
+ match revert_type {
152
+ RevertType :: Revert => {
153
+ if let Ok ( revert) = Revert :: abi_decode ( data, true ) {
154
+ print_line ( ) ;
155
+ spaced_print ! ( "{:#?}\n " , revert) ;
156
+ }
157
+ }
158
+ RevertType :: Panic => {
159
+ if let Ok ( panic) = Panic :: abi_decode ( data, true ) {
160
+ print_line ( ) ;
161
+ spaced_print ! ( "{:#?}" , panic) ;
162
+ }
163
+ }
164
+ _ => { }
165
+ }
166
+ }
167
+
168
+ fn print_line ( ) {
169
+ spaced_print ! ( "--------------------------------------------------------" ) ;
170
+ }
0 commit comments