@@ -7,6 +7,24 @@ use fvm_integration_tests::{tester, testkit};
7
7
use fvm_ipld_encoding:: BytesDe ;
8
8
use fvm_shared:: address:: Address ;
9
9
10
+ // Eth ABI (solidity) panic codes.
11
+ const PANIC_ERROR_CODES : [ ( u64 , & str ) ; 10 ] = [
12
+ ( 0x00 , "Panic()" ) ,
13
+ ( 0x01 , "Assert()" ) ,
14
+ ( 0x11 , "ArithmeticOverflow()" ) ,
15
+ ( 0x12 , "DivideByZero()" ) ,
16
+ ( 0x21 , "InvalidEnumVariant()" ) ,
17
+ ( 0x22 , "InvalidStorageArray()" ) ,
18
+ ( 0x31 , "PopEmptyArray()" ) ,
19
+ ( 0x32 , "ArrayIndexOutOfBounds()" ) ,
20
+ ( 0x41 , "OutOfMemory()" ) ,
21
+ ( 0x51 , "CalledUninitializedFunction()" ) ,
22
+ ] ;
23
+
24
+ // Function Selectors
25
+ const ERROR_FUNCTION_SELECTOR : & [ u8 ] = b"\x08 \xc3 \x79 \xa0 " ; // Error(string)
26
+ const PANIC_FUNCTION_SELECTOR : & [ u8 ] = b"\x4e \x48 \x7b \x71 " ; // Panic(uint256)
27
+
10
28
fn handle_result ( tester : & tester:: BasicTester , name : & str , res : & ApplyRet ) -> anyhow:: Result < ( ) > {
11
29
let ( trace, events) = tester
12
30
. options
@@ -52,6 +70,10 @@ fn handle_result(tester: &tester::BasicTester, name: &str, res: &ApplyRet) -> an
52
70
if res. msg_receipt . exit_code . is_success ( ) {
53
71
Ok ( ( ) )
54
72
} else {
73
+ if res. msg_receipt . exit_code == 33 . into ( ) {
74
+ let BytesDe ( returnval) = res. msg_receipt . return_data . deserialize ( ) . unwrap ( ) ;
75
+ println ! ( "Revert Reason: {}" , parse_eth_revert( & returnval) . unwrap( ) ) ;
76
+ }
55
77
Err ( anyhow ! ( "{name} failed" ) )
56
78
}
57
79
}
@@ -85,3 +107,140 @@ pub fn run(
85
107
86
108
handle_result ( tester, "contract invocation" , & invoke_res)
87
109
}
110
+
111
+ // Parses the error message from a revert reason of type Error(string) or Panic(uint256)
112
+ // See https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require
113
+ pub fn parse_eth_revert ( returnval : & Vec < u8 > ) -> anyhow:: Result < String > {
114
+ if returnval. is_empty ( ) {
115
+ return Err ( anyhow ! ( "invalid return value" ) ) ;
116
+ }
117
+ if returnval. len ( ) < 4 + 32 {
118
+ return Ok ( hex:: encode ( returnval) ) ;
119
+ }
120
+ match & returnval[ 0 ..4 ] {
121
+ PANIC_FUNCTION_SELECTOR => {
122
+ let cbytes = & returnval[ 4 ..] ;
123
+ match bytes_to_u64 ( & cbytes[ ..32 ] ) {
124
+ Ok ( panic_code) => {
125
+ let error = panic_error_codes ( panic_code) ;
126
+ match error {
127
+ Some ( s) => return Ok ( format ! ( "Panic Code: {}, msg: {}" , s. 0 , s. 1 ) ) ,
128
+ None => return Err ( anyhow ! ( "Returned with panic code({})" , panic_code) ) ,
129
+ }
130
+ }
131
+ Err ( _) => {
132
+ return Ok ( hex:: encode ( returnval) ) ;
133
+ }
134
+ }
135
+ }
136
+ ERROR_FUNCTION_SELECTOR => {
137
+ let cbytes = & returnval[ 4 ..] ;
138
+ let cbytes_len = cbytes. len ( ) as u64 ;
139
+ if let Ok ( offset) = bytes_to_u64 ( & cbytes[ 0 ..32 ] ) {
140
+ if cbytes_len >= offset + 32 {
141
+ if let Ok ( length) = bytes_to_u64 ( & cbytes[ offset as usize ..offset as usize + 32 ] )
142
+ {
143
+ if cbytes_len >= offset + 32 + length {
144
+ let msg = String :: from_utf8_lossy (
145
+ & cbytes
146
+ [ offset as usize + 32 ..offset as usize + 32 + length as usize ] ,
147
+ ) ;
148
+ return Ok ( msg. to_string ( ) ) ;
149
+ }
150
+ }
151
+ }
152
+ }
153
+ }
154
+ _ => return Ok ( hex:: encode ( returnval) ) ,
155
+ } ;
156
+ Ok ( hex:: encode ( returnval) )
157
+ }
158
+
159
+ // Converts a byte slice to a u64
160
+ fn bytes_to_u64 ( bytes : & [ u8 ] ) -> Result < u64 , anyhow:: Error > {
161
+ if bytes. len ( ) != 32 {
162
+ return Err ( anyhow:: anyhow!( "Invalid byte slice length" ) ) ;
163
+ }
164
+ let mut buf = [ 0u8 ; 8 ] ;
165
+ buf. copy_from_slice ( & bytes[ 24 ..32 ] ) ;
166
+ Ok ( u64:: from_be_bytes ( buf) )
167
+ }
168
+
169
+ // Returns the panic code and message for a given panic code
170
+ fn panic_error_codes ( code : u64 ) -> Option < & ' static ( u64 , & ' static str ) > {
171
+ PANIC_ERROR_CODES . iter ( ) . find ( |( c, _) | * c == code)
172
+ }
173
+
174
+ //////////////////////
175
+ /////// Tests ///////
176
+ /////////////////////
177
+
178
+ #[ cfg( test) ]
179
+ mod tests {
180
+ use super :: * ;
181
+
182
+ #[ test]
183
+ fn test_parse_eth_revert_empty_returnval ( ) {
184
+ let returnval = vec ! [ ] ;
185
+ let result = parse_eth_revert ( & returnval) ;
186
+ assert ! ( result. is_err( ) ) ;
187
+ assert_eq ! ( result. unwrap_err( ) . to_string( ) , "invalid return value" ) ;
188
+ }
189
+
190
+ #[ test]
191
+ fn test_parse_eth_revert_short_returnval ( ) {
192
+ let returnval = vec ! [ 0x01 , 0x02 , 0x03 ] ;
193
+ let result = parse_eth_revert ( & returnval) ;
194
+ assert ! ( result. is_ok( ) ) ;
195
+ assert_eq ! ( result. unwrap( ) , "010203" ) ;
196
+ }
197
+
198
+ #[ test]
199
+ fn test_parse_eth_revert_panic_function_selector ( ) {
200
+ let returnval = vec ! [
201
+ 0x4e , 0x48 , 0x7b , 0x71 , // function selector for "Panic(uint256)"
202
+ 0x00 , 0x00 , 0x00 , 0x00 ,
203
+ ] ;
204
+ let result = parse_eth_revert ( & returnval) ;
205
+ assert ! ( result. is_ok( ) ) ;
206
+ assert_eq ! ( result. unwrap( ) , "4e487b7100000000" ) ;
207
+ }
208
+
209
+ #[ test]
210
+ fn test_parse_eth_revert_panic_function_selector_with_message ( ) {
211
+ // assert error from simplecoin contract
212
+ let returnval =
213
+ hex:: decode ( "4e487b710000000000000000000000000000000000000000000000000000000000000001" )
214
+ . unwrap ( ) ;
215
+ let result = parse_eth_revert ( & returnval) ;
216
+ assert ! ( result. is_ok( ) ) ;
217
+ assert_eq ! ( result. unwrap( ) , "Panic Code: 1, msg: Assert()" ) ;
218
+ }
219
+
220
+ #[ test]
221
+ fn test_parse_eth_revert_error_function_selector ( ) {
222
+ // "Less Than ten" error from simplecoin contract
223
+ let returnval = hex:: decode ( "08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d4c657373205468616e2074656e00000000000000000000000000000000000000" ) . unwrap ( ) ;
224
+ let result = parse_eth_revert ( & returnval) ;
225
+ assert ! ( result. is_ok( ) ) ;
226
+ assert_eq ! ( result. unwrap( ) , "Less Than ten" ) ;
227
+ }
228
+
229
+ #[ test]
230
+ fn test_parse_eth_revert_error_function_selector_invalid_data ( ) {
231
+ // invalid data for error function selector
232
+ let returnval = hex:: decode ( "08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000" ) . unwrap ( ) ;
233
+ let result = parse_eth_revert ( & returnval) ;
234
+ assert ! ( result. is_ok( ) ) ;
235
+ assert_eq ! ( result. unwrap( ) , hex:: encode( & returnval) ) ;
236
+ }
237
+
238
+ #[ test]
239
+ fn test_parse_eth_revert_custom_error ( ) {
240
+ // any other data like custom error, etc. "lessThanFive" custom error of simplecoin contract in this case.
241
+ let returnval = hex:: decode ( "4426661100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000e4c657373207468616e2066697665000000000000000000000000000000000000" ) . unwrap ( ) ;
242
+ let result = parse_eth_revert ( & returnval) ;
243
+ assert ! ( result. is_ok( ) ) ;
244
+ assert_eq ! ( result. unwrap( ) , hex:: encode( & returnval) ) ;
245
+ }
246
+ }
0 commit comments