mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-21 22:24:32 +00:00
account/abi: handle solidity panic revert (#27868)
See https://docs.soliditylang.org/en/v0.8.21/control-structures.html#panic-via-assert-and-error-via-require
This commit is contained in:
parent
d2cfa789da
commit
4c7507af85
2 changed files with 87 additions and 43 deletions
|
|
@ -22,6 +22,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||
|
|
@ -246,24 +247,65 @@ func (abi *ABI) HasReceive() bool {
|
|||
// revertSelector is a special function selector for revert reason unpacking.
|
||||
var revertSelector = crypto.Keccak256([]byte("Error(string)"))[:4]
|
||||
|
||||
// panicSelector is a special function selector for panic reason unpacking.
|
||||
var panicSelector = crypto.Keccak256([]byte("Panic(uint256)"))[:4]
|
||||
|
||||
// panicReasons map is for readable panic codes
|
||||
// see this linkage for the deails
|
||||
// https://docs.soliditylang.org/en/v0.8.21/control-structures.html#panic-via-assert-and-error-via-require
|
||||
// the reason string list is copied from ether.js
|
||||
// https://github.com/ethers-io/ethers.js/blob/fa3a883ff7c88611ce766f58bdd4b8ac90814470/src.ts/abi/interface.ts#L207-L218
|
||||
var panicReasons = map[uint64]string{
|
||||
0x00: "generic panic",
|
||||
0x01: "assert(false)",
|
||||
0x11: "arithmetic underflow or overflow",
|
||||
0x12: "division or modulo by zero",
|
||||
0x21: "enum overflow",
|
||||
0x22: "invalid encoded storage byte array accessed",
|
||||
0x31: "out-of-bounds array access; popping on an empty array",
|
||||
0x32: "out-of-bounds access of an array or bytesN",
|
||||
0x41: "out of memory",
|
||||
0x51: "uninitialized function",
|
||||
}
|
||||
|
||||
// UnpackRevert resolves the abi-encoded revert reason. According to the solidity
|
||||
// spec https://solidity.readthedocs.io/en/latest/control-structures.html#revert,
|
||||
// the provided revert reason is abi-encoded as if it were a call to a function
|
||||
// `Error(string)`. So it's a special tool for it.
|
||||
// the provided revert reason is abi-encoded as if it were a call to function
|
||||
// `Error(string)` or `Panic(uint256)`. So it's a special tool for it.
|
||||
func UnpackRevert(data []byte) (string, error) {
|
||||
if len(data) < 4 {
|
||||
return "", errors.New("invalid data for unpacking")
|
||||
}
|
||||
if !bytes.Equal(data[:4], revertSelector) {
|
||||
switch {
|
||||
case bytes.Equal(data[:4], revertSelector):
|
||||
typ, err := NewType("string", "", nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
unpacked, err := (Arguments{{Type: typ}}).Unpack(data[4:])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return unpacked[0].(string), nil
|
||||
case bytes.Equal(data[:4], panicSelector):
|
||||
typ, err := NewType("uint256", "", nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
unpacked, err := (Arguments{{Type: typ}}).Unpack(data[4:])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
pCode := unpacked[0].(*big.Int)
|
||||
// uint64 safety check for future
|
||||
// but the code is not bigger than MAX(uint64) now
|
||||
if pCode.IsUint64() {
|
||||
if reason, ok := panicReasons[pCode.Uint64()]; ok {
|
||||
return reason, nil
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("unknown panic code: %#x", pCode), nil
|
||||
default:
|
||||
return "", errors.New("invalid data for unpacking")
|
||||
}
|
||||
typ, err := NewType("string", "", nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
unpacked, err := (Arguments{{Type: typ}}).Unpack(data[4:])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return unpacked[0].(string), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -992,37 +992,6 @@ func TestABI_MethodById(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestUnpackRevert(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var cases = []struct {
|
||||
input string
|
||||
expect string
|
||||
expectErr error
|
||||
}{
|
||||
{"", "", errors.New("invalid data for unpacking")},
|
||||
{"08c379a1", "", errors.New("invalid data for unpacking")},
|
||||
{"08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d72657665727420726561736f6e00000000000000000000000000000000000000", "revert reason", nil},
|
||||
}
|
||||
for index, c := range cases {
|
||||
t.Run(fmt.Sprintf("case %d", index), func(t *testing.T) {
|
||||
got, err := UnpackRevert(common.Hex2Bytes(c.input))
|
||||
if c.expectErr != nil {
|
||||
if err == nil {
|
||||
t.Fatalf("Expected non-nil error")
|
||||
}
|
||||
if err.Error() != c.expectErr.Error() {
|
||||
t.Fatalf("Expected error mismatch, want %v, got %v", c.expectErr, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if c.expect != got {
|
||||
t.Fatalf("Output mismatch, want %v, got %v", c.expect, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestABI_EventById(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
@ -1193,3 +1162,36 @@ func TestUnnamedEventParam(t *testing.T) {
|
|||
t.Fatalf("Could not find input")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnpackRevert(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var cases = []struct {
|
||||
input string
|
||||
expect string
|
||||
expectErr error
|
||||
}{
|
||||
{"", "", errors.New("invalid data for unpacking")},
|
||||
{"08c379a1", "", errors.New("invalid data for unpacking")},
|
||||
{"08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000d72657665727420726561736f6e00000000000000000000000000000000000000", "revert reason", nil},
|
||||
{"4e487b710000000000000000000000000000000000000000000000000000000000000000", "generic panic", nil},
|
||||
{"4e487b7100000000000000000000000000000000000000000000000000000000000000ff", "unknown panic code: 0xff", nil},
|
||||
}
|
||||
for index, c := range cases {
|
||||
t.Run(fmt.Sprintf("case %d", index), func(t *testing.T) {
|
||||
got, err := UnpackRevert(common.Hex2Bytes(c.input))
|
||||
if c.expectErr != nil {
|
||||
if err == nil {
|
||||
t.Fatalf("Expected non-nil error")
|
||||
}
|
||||
if err.Error() != c.expectErr.Error() {
|
||||
t.Fatalf("Expected error mismatch, want %v, got %v", c.expectErr, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if c.expect != got {
|
||||
t.Fatalf("Output mismatch, want %v, got %v", c.expect, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue