diff --git a/accounts/abi/bind/topics.go b/accounts/abi/bind/topics.go index e27fa5484..c908c9258 100644 --- a/accounts/abi/bind/topics.go +++ b/accounts/abi/bind/topics.go @@ -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: @@ -212,8 +219,7 @@ 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:]) @@ -221,7 +227,11 @@ func parseTopicsIntoMap(out map[string]interface{}, fields abi.Arguments, topics 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 diff --git a/accounts/abi/bind/topics_test.go b/accounts/abi/bind/topics_test.go index f18e2d1bd..c62f5bab3 100644 --- a/accounts/abi/bind/topics_test.go +++ b/accounts/abi/bind/topics_test.go @@ -17,6 +17,7 @@ package bind import ( + "math/big" "reflect" "testing" @@ -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, @@ -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() @@ -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) + } + }) + } +} diff --git a/accounts/abi/unpack.go b/accounts/abi/unpack.go index b2e61d06c..2a5db3b31 100644 --- a/accounts/abi/unpack.go +++ b/accounts/abi/unpack.go @@ -26,16 +26,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] @@ -62,8 +64,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) } @@ -102,8 +104,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, fmt.Errorf("abi: invalid type in call to make fixed byte array") } @@ -230,7 +232,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: @@ -240,7 +242,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: