abigen: guard short custom error data

This commit is contained in:
Minh Vu 2026-07-09 23:59:17 +02:00
parent 111e7b8b48
commit 6deb0a3b9d
4 changed files with 51 additions and 2 deletions

View file

@ -357,6 +357,7 @@ func BindV2(types []string, abis []string, bytecodes []string, pkg string, libs
"bindtopictype": bindTopicType,
"capitalise": abi.ToCamelCase,
"decapitalise": decapitalise,
"hasErrors": hasErrors,
"ispointertype": isPointerType,
"underlyingbindtype": underlyingBindType,
}
@ -371,3 +372,12 @@ func BindV2(types []string, abis []string, bytecodes []string, pkg string, libs
}
return string(code), nil
}
func hasErrors(contracts map[string]*tmplContractV2) bool {
for _, contract := range contracts {
if len(contract.Errors) > 0 {
return true
}
}
return false
}

View file

@ -4,7 +4,8 @@
package {{.Package}}
import (
"bytes"
"bytes"{{if hasErrors .Contracts}}
"fmt"{{end}}
"math/big"
"errors"
@ -16,7 +17,8 @@ import (
// Reference imports to suppress errors if they are not otherwise used.
var (
_ = bytes.Equal
_ = bytes.Equal{{if hasErrors .Contracts}}
_ = fmt.Sprintf{{end}}
_ = errors.New
_ = big.NewInt
_ = common.Big1
@ -218,6 +220,9 @@ var (
// UnpackError attempts to decode the provided error data using user-defined
// error definitions.
func ({{ decapitalise $contract.Type}} *{{$contract.Type}}) UnpackError(raw []byte) (any, error) {
if len(raw) < 4 {
return nil, fmt.Errorf("insufficient error data: have %d, want at least 4", len(raw))
}
{{- range $k, $v := .Errors}}
if bytes.Equal(raw[:4], {{ decapitalise $contract.Type}}.abi.Errors["{{.Normalized.Name}}"].ID.Bytes()[:4]) {
return {{ decapitalise $contract.Type}}.Unpack{{.Normalized.Name}}Error(raw[4:])

View file

@ -6,6 +6,7 @@ package solc_errors
import (
"bytes"
"errors"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/accounts/abi"
@ -17,6 +18,7 @@ import (
// Reference imports to suppress errors if they are not otherwise used.
var (
_ = bytes.Equal
_ = fmt.Sprintf
_ = errors.New
_ = big.NewInt
_ = common.Big1
@ -103,6 +105,9 @@ func (c *C) TryPackFoo() ([]byte, error) {
// UnpackError attempts to decode the provided error data using user-defined
// error definitions.
func (c *C) UnpackError(raw []byte) (any, error) {
if len(raw) < 4 {
return nil, fmt.Errorf("insufficient error data: have %d, want at least 4", len(raw))
}
if bytes.Equal(raw[:4], c.abi.Errors["BadThing"].ID.Bytes()[:4]) {
return c.UnpackBadThingError(raw[4:])
}
@ -223,6 +228,9 @@ func (c2 *C2) TryPackFoo() ([]byte, error) {
// UnpackError attempts to decode the provided error data using user-defined
// error definitions.
func (c2 *C2) UnpackError(raw []byte) (any, error) {
if len(raw) < 4 {
return nil, fmt.Errorf("insufficient error data: have %d, want at least 4", len(raw))
}
if bytes.Equal(raw[:4], c2.abi.Errors["BadThing"].ID.Bytes()[:4]) {
return c2.UnpackBadThingError(raw[4:])
}

View file

@ -368,6 +368,32 @@ func TestErrors(t *testing.T) {
}
}
func TestUnpackErrorRejectsMalformedData(t *testing.T) {
c := solc_errors.NewC()
selector := solc_errors.CBadThingErrorID()
tests := []struct {
name string
raw []byte
want string
}{
{"nil data", nil, "insufficient error data: have 0, want at least 4"},
{"short data", []byte{1, 2, 3}, "insufficient error data: have 3, want at least 4"},
{"unknown selector", []byte{0, 1, 2, 3}, "Unknown error"},
{"known selector with malformed payload", append(append([]byte{}, selector[:4]...), 0), ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := c.UnpackError(tt.raw)
if err == nil {
t.Fatal("expected an error")
}
if tt.want != "" && err.Error() != tt.want {
t.Fatalf("error: got %q, want %q", err, tt.want)
}
})
}
}
func TestEventUnpackEmptyTopics(t *testing.T) {
c := events.NewC()