浏览代码

[#20266] Fix bugs decoding integers and fixed bytes in indexed event fields (#20269)

* fix parseTopics() and add tests

* remove printf

* add ParseTopicsIntoMap() tests

* fix FixedBytesTy

* fix int and fixed bytes

* golint topics_test.go
Jeff Wentworth 5 年之前
父节点
当前提交
6ae9dc15cc
共有 3 个文件被更改,包括 122 次插入28 次删除
  1. 13 3
      accounts/abi/bind/topics.go
  2. 97 15
      accounts/abi/bind/topics_test.go
  3. 12 10
      accounts/abi/unpack.go

+ 13 - 3
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

+ 97 - 15
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)
+			}
+		})
+	}
+}

+ 12 - 10
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: