Skip to content

Commit 5ca7fb8

Browse files
authored
1 parent 6aa88cc commit 5ca7fb8

File tree

2 files changed

+56
-12
lines changed

2 files changed

+56
-12
lines changed

accounts/abi/abi.go

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
247248
var 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.
253275
func 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
}

accounts/abi/abi_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,6 +1173,8 @@ func TestUnpackRevert(t *testing.T) {
11731173
{"", "", errors.New("invalid data for unpacking")},
11741174
{"08c379a1", "", errors.New("invalid data for unpacking")},
11751175
{"08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d72657665727420726561736f6e00000000000000000000000000000000000000", "revert reason", nil},
1176+
{"4e487b710000000000000000000000000000000000000000000000000000000000000000", "generic panic", nil},
1177+
{"4e487b7100000000000000000000000000000000000000000000000000000000000000ff", "unknown panic code: 0xff", nil},
11761178
}
11771179
for index, c := range cases {
11781180
t.Run(fmt.Sprintf("case %d", index), func(t *testing.T) {

0 commit comments

Comments
 (0)