@@ -22,6 +22,7 @@ import (
22
22
"errors"
23
23
"fmt"
24
24
"io"
25
+ "math/big"
25
26
26
27
"github.com/ethereum/go-ethereum/common"
27
28
"github.com/ethereum/go-ethereum/crypto"
@@ -246,24 +247,65 @@ func (abi *ABI) HasReceive() bool {
246
247
// revertSelector is a special function selector for revert reason unpacking.
247
248
var revertSelector = crypto .Keccak256 ([]byte ("Error(string)" ))[:4 ]
248
249
250
+ // panicSelector is a special function selector for panic reason unpacking.
251
+ var panicSelector = crypto .Keccak256 ([]byte ("Panic(uint256)" ))[:4 ]
252
+
253
+ // panicReasons map is for readable panic codes
254
+ // see this linkage for the deails
255
+ // https://docs.soliditylang.org/en/v0.8.21/control-structures.html#panic-via-assert-and-error-via-require
256
+ // the reason string list is copied from ether.js
257
+ // https://github.com/ethers-io/ethers.js/blob/fa3a883ff7c88611ce766f58bdd4b8ac90814470/src.ts/abi/interface.ts#L207-L218
258
+ var panicReasons = map [uint64 ]string {
259
+ 0x00 : "generic panic" ,
260
+ 0x01 : "assert(false)" ,
261
+ 0x11 : "arithmetic underflow or overflow" ,
262
+ 0x12 : "division or modulo by zero" ,
263
+ 0x21 : "enum overflow" ,
264
+ 0x22 : "invalid encoded storage byte array accessed" ,
265
+ 0x31 : "out-of-bounds array access; popping on an empty array" ,
266
+ 0x32 : "out-of-bounds access of an array or bytesN" ,
267
+ 0x41 : "out of memory" ,
268
+ 0x51 : "uninitialized function" ,
269
+ }
270
+
249
271
// UnpackRevert resolves the abi-encoded revert reason. According to the solidity
250
272
// spec https://solidity.readthedocs.io/en/latest/control-structures.html#revert,
251
- // the provided revert reason is abi-encoded as if it were a call to a function
252
- // `Error(string)`. So it's a special tool for it.
273
+ // the provided revert reason is abi-encoded as if it were a call to function
274
+ // `Error(string)` or `Panic(uint256)` . So it's a special tool for it.
253
275
func UnpackRevert (data []byte ) (string , error ) {
254
276
if len (data ) < 4 {
255
277
return "" , errors .New ("invalid data for unpacking" )
256
278
}
257
- if ! bytes .Equal (data [:4 ], revertSelector ) {
279
+ switch {
280
+ case bytes .Equal (data [:4 ], revertSelector ):
281
+ typ , err := NewType ("string" , "" , nil )
282
+ if err != nil {
283
+ return "" , err
284
+ }
285
+ unpacked , err := (Arguments {{Type : typ }}).Unpack (data [4 :])
286
+ if err != nil {
287
+ return "" , err
288
+ }
289
+ return unpacked [0 ].(string ), nil
290
+ case bytes .Equal (data [:4 ], panicSelector ):
291
+ typ , err := NewType ("uint256" , "" , nil )
292
+ if err != nil {
293
+ return "" , err
294
+ }
295
+ unpacked , err := (Arguments {{Type : typ }}).Unpack (data [4 :])
296
+ if err != nil {
297
+ return "" , err
298
+ }
299
+ pCode := unpacked [0 ].(* big.Int )
300
+ // uint64 safety check for future
301
+ // but the code is not bigger than MAX(uint64) now
302
+ if pCode .IsUint64 () {
303
+ if reason , ok := panicReasons [pCode .Uint64 ()]; ok {
304
+ return reason , nil
305
+ }
306
+ }
307
+ return fmt .Sprintf ("unknown panic code: %#x" , pCode ), nil
308
+ default :
258
309
return "" , errors .New ("invalid data for unpacking" )
259
310
}
260
- typ , err := NewType ("string" , "" , nil )
261
- if err != nil {
262
- return "" , err
263
- }
264
- unpacked , err := (Arguments {{Type : typ }}).Unpack (data [4 :])
265
- if err != nil {
266
- return "" , err
267
- }
268
- return unpacked [0 ].(string ), nil
269
311
}
0 commit comments