@@ -22,6 +22,7 @@ import (
2222 "errors"
2323 "fmt"
2424 "io"
25+ "math/big"
2526
2627 "github.com/ethereum/go-ethereum/common"
2728 "github.com/ethereum/go-ethereum/crypto"
@@ -246,24 +247,65 @@ func (abi *ABI) HasReceive() bool {
246247// revertSelector is a special function selector for revert reason unpacking.
247248var revertSelector = crypto .Keccak256 ([]byte ("Error(string)" ))[:4 ]
248249
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+
249271// UnpackRevert resolves the abi-encoded revert reason. According to the solidity
250272// 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.
253275func UnpackRevert (data []byte ) (string , error ) {
254276 if len (data ) < 4 {
255277 return "" , errors .New ("invalid data for unpacking" )
256278 }
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 :
258309 return "" , errors .New ("invalid data for unpacking" )
259310 }
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
269311}
0 commit comments