Skip to content

Commit

Permalink
[ethereum#20266] Fix bugs decoding integers and fixed bytes in indexe…
Browse files Browse the repository at this point in the history
…d event fields (ethereum#20269)

* fix parseTopics() and add tests

* remove printf

* add ParseTopicsIntoMap() tests

* fix FixedBytesTy

* fix int and fixed bytes

* golint topics_test.go
  • Loading branch information
gzliudan committed Jan 7, 2025
1 parent 4a4f8b6 commit f18c320
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 28 deletions.
16 changes: 13 additions & 3 deletions accounts/abi/bind/topics.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,13 @@ func parseTopics(out interface{}, fields abi.Arguments, topics []common.Hash) er

case reflectBigInt:
num := new(big.Int).SetBytes(topics[0][:])
if arg.Type.T == abi.IntTy {
if num.Cmp(abi.MaxInt256) > 0 {
num.Add(abi.MaxUint256, big.NewInt(0).Neg(num))
num.Add(num, big.NewInt(1))
num.Neg(num)
}
}
field.Set(reflect.ValueOf(num))

default:
Expand Down Expand Up @@ -212,16 +219,19 @@ func parseTopicsIntoMap(out map[string]interface{}, fields abi.Arguments, topics
case abi.BoolTy:
out[arg.Name] = topics[0][common.HashLength-1] == 1
case abi.IntTy, abi.UintTy:
num := new(big.Int).SetBytes(topics[0][:])
out[arg.Name] = num
out[arg.Name] = abi.ReadInteger(arg.Type.T, arg.Type.Kind, topics[0].Bytes())
case abi.AddressTy:
var addr common.Address
copy(addr[:], topics[0][common.HashLength-common.AddressLength:])
out[arg.Name] = addr
case abi.HashTy:
out[arg.Name] = topics[0]
case abi.FixedBytesTy:
out[arg.Name] = topics[0][:]
array, err := abi.ReadFixedBytes(arg.Type, topics[0].Bytes())
if err != nil {
return err
}
out[arg.Name] = array
case abi.StringTy, abi.BytesTy, abi.SliceTy, abi.ArrayTy:
// Array types (including strings and bytes) have their keccak256 hashes stored in the topic- not a hash
// whose bytes can be decoded to the actual value- so the best we can do is retrieve that hash
Expand Down
112 changes: 97 additions & 15 deletions accounts/abi/bind/topics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package bind

import (
"math/big"
"reflect"
"testing"

Expand Down Expand Up @@ -55,27 +56,44 @@ func TestMakeTopics(t *testing.T) {
}
}

func TestParseTopics(t *testing.T) {
type bytesStruct struct {
StaticBytes [5]byte
}
type args struct {
createObj func() interface{}
resultObj func() interface{}
resultMap func() map[string]interface{}
fields abi.Arguments
topics []common.Hash
}

type bytesStruct struct {
StaticBytes [5]byte
}
type int8Struct struct {
Int8Value int8
}
type int256Struct struct {
Int256Value *big.Int
}

type topicTest struct {
name string
args args
wantErr bool
}

func setupTopicsTests() []topicTest {
bytesType, _ := abi.NewType("bytes5", "", nil)
type args struct {
createObj func() interface{}
resultObj func() interface{}
fields abi.Arguments
topics []common.Hash
}
tests := []struct {
name string
args args
wantErr bool
}{
int8Type, _ := abi.NewType("int8", "", nil)
int256Type, _ := abi.NewType("int256", "", nil)

tests := []topicTest{
{
name: "support fixed byte types, right padded to 32 bytes",
args: args{
createObj: func() interface{} { return &bytesStruct{} },
resultObj: func() interface{} { return &bytesStruct{StaticBytes: [5]byte{1, 2, 3, 4, 5}} },
resultMap: func() map[string]interface{} {
return map[string]interface{}{"staticBytes": [5]byte{1, 2, 3, 4, 5}}
},
fields: abi.Arguments{abi.Argument{
Name: "staticBytes",
Type: bytesType,
Expand All @@ -87,7 +105,54 @@ func TestParseTopics(t *testing.T) {
},
wantErr: false,
},
{
name: "int8 with negative value",
args: args{
createObj: func() interface{} { return &int8Struct{} },
resultObj: func() interface{} { return &int8Struct{Int8Value: -1} },
resultMap: func() map[string]interface{} {
return map[string]interface{}{"int8Value": int8(-1)}
},
fields: abi.Arguments{abi.Argument{
Name: "int8Value",
Type: int8Type,
Indexed: true,
}},
topics: []common.Hash{
{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
},
},
wantErr: false,
},
{
name: "int256 with negative value",
args: args{
createObj: func() interface{} { return &int256Struct{} },
resultObj: func() interface{} { return &int256Struct{Int256Value: big.NewInt(-1)} },
resultMap: func() map[string]interface{} {
return map[string]interface{}{"int256Value": big.NewInt(-1)}
},
fields: abi.Arguments{abi.Argument{
Name: "int256Value",
Type: int256Type,
Indexed: true,
}},
topics: []common.Hash{
{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255},
},
},
wantErr: false,
},
}

return tests
}

func TestParseTopics(t *testing.T) {
tests := setupTopicsTests()

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
createObj := tt.args.createObj()
Expand All @@ -101,3 +166,20 @@ func TestParseTopics(t *testing.T) {
})
}
}

func TestParseTopicsIntoMap(t *testing.T) {
tests := setupTopicsTests()

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
outMap := make(map[string]interface{})
if err := parseTopicsIntoMap(outMap, tt.args.fields, tt.args.topics); (err != nil) != tt.wantErr {
t.Errorf("parseTopicsIntoMap() error = %v, wantErr %v", err, tt.wantErr)
}
resultMap := tt.args.resultMap()
if !reflect.DeepEqual(outMap, resultMap) {
t.Errorf("parseTopicsIntoMap() = %v, want %v", outMap, resultMap)
}
})
}
}
22 changes: 12 additions & 10 deletions accounts/abi/unpack.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,18 @@ import (
)

var (
maxUint256 = big.NewInt(0).Add(
// MaxUint256 is the maximum value that can be represented by a uint256
MaxUint256 = big.NewInt(0).Add(
big.NewInt(0).Exp(big.NewInt(2), big.NewInt(256), nil),
big.NewInt(-1))
maxInt256 = big.NewInt(0).Add(
// MaxInt256 is the maximum value that can be represented by a int256
MaxInt256 = big.NewInt(0).Add(
big.NewInt(0).Exp(big.NewInt(2), big.NewInt(255), nil),
big.NewInt(-1))
)

// reads the integer based on its kind
func readInteger(typ byte, kind reflect.Kind, b []byte) interface{} {
// ReadInteger reads the integer based on its kind and returns the appropriate value
func ReadInteger(typ byte, kind reflect.Kind, b []byte) interface{} {
switch kind {
case reflect.Uint8:
return b[len(b)-1]
Expand All @@ -63,8 +65,8 @@ func readInteger(typ byte, kind reflect.Kind, b []byte) interface{} {
return ret
}

if ret.Cmp(maxInt256) > 0 {
ret.Add(maxUint256, big.NewInt(0).Neg(ret))
if ret.Cmp(MaxInt256) > 0 {
ret.Add(MaxUint256, big.NewInt(0).Neg(ret))
ret.Add(ret, big.NewInt(1))
ret.Neg(ret)
}
Expand Down Expand Up @@ -103,8 +105,8 @@ func readFunctionType(t Type, word []byte) (funcTy [24]byte, err error) {
return
}

// through reflection, creates a fixed array to be read from
func readFixedBytes(t Type, word []byte) (interface{}, error) {
// ReadFixedBytes uses reflection to create a fixed array to be read from
func ReadFixedBytes(t Type, word []byte) (interface{}, error) {
if t.T != FixedBytesTy {
return nil, errors.New("abi: invalid type in call to make fixed byte array")
}
Expand Down Expand Up @@ -231,7 +233,7 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) {
case StringTy: // variable arrays are written at the end of the return bytes
return string(output[begin : begin+length]), nil
case IntTy, UintTy:
return readInteger(t.T, t.Kind, returnOutput), nil
return ReadInteger(t.T, t.Kind, returnOutput), nil
case BoolTy:
return readBool(returnOutput)
case AddressTy:
Expand All @@ -241,7 +243,7 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) {
case BytesTy:
return output[begin : begin+length], nil
case FixedBytesTy:
return readFixedBytes(t, returnOutput)
return ReadFixedBytes(t, returnOutput)
case FunctionTy:
return readFunctionType(t, returnOutput)
default:
Expand Down

0 comments on commit f18c320

Please sign in to comment.