Skip to content

accounts/abi/bind: Refactored topics #20851

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Apr 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion accounts/abi/argument.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) {
retval := make([]interface{}, 0, arguments.LengthNonIndexed())
virtualArgs := 0
for index, arg := range arguments.NonIndexed() {
marshalledValue, err := toGoType((index+virtualArgs)*32, arg.Type, data)
marshalledValue, err := ToGoType((index+virtualArgs)*32, arg.Type, data)
if arg.Type.T == ArrayTy && !isDynamicType(arg.Type) {
// If we have a static array, like [3]uint256, these are coded as
// just like uint256,uint256,uint256.
Expand Down
157 changes: 34 additions & 123 deletions accounts/abi/bind/topics.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,151 +103,62 @@ func makeTopics(query ...[]interface{}) ([][]common.Hash, error) {
return topics, nil
}

// Big batch of reflect types for topic reconstruction.
var (
reflectHash = reflect.TypeOf(common.Hash{})
reflectAddress = reflect.TypeOf(common.Address{})
reflectBigInt = reflect.TypeOf(new(big.Int))
)

// parseTopics converts the indexed topic fields into actual log field values.
//
// Note, dynamic types cannot be reconstructed since they get mapped to Keccak256
// hashes as the topic value!
func parseTopics(out interface{}, fields abi.Arguments, topics []common.Hash) error {
// Sanity check that the fields and topics match up
if len(fields) != len(topics) {
return errors.New("topic/field count mismatch")
}
// Iterate over all the fields and reconstruct them from topics
for _, arg := range fields {
if !arg.Indexed {
return errors.New("non-indexed field in topic reconstruction")
}
field := reflect.ValueOf(out).Elem().FieldByName(capitalise(arg.Name))

// Try to parse the topic back into the fields based on primitive types
switch field.Kind() {
case reflect.Bool:
if topics[0][common.HashLength-1] == 1 {
field.Set(reflect.ValueOf(true))
}
case reflect.Int8:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(int8(num.Int64())))

case reflect.Int16:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(int16(num.Int64())))

case reflect.Int32:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(int32(num.Int64())))

case reflect.Int64:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(num.Int64()))

case reflect.Uint8:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(uint8(num.Uint64())))

case reflect.Uint16:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(uint16(num.Uint64())))

case reflect.Uint32:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(uint32(num.Uint64())))

case reflect.Uint64:
num := new(big.Int).SetBytes(topics[0][:])
field.Set(reflect.ValueOf(num.Uint64()))

default:
// Ran out of plain primitive types, try custom types

switch field.Type() {
case reflectHash: // Also covers all dynamic types
field.Set(reflect.ValueOf(topics[0]))

case reflectAddress:
var addr common.Address
copy(addr[:], topics[0][common.HashLength-common.AddressLength:])
field.Set(reflect.ValueOf(addr))

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:
// Ran out of custom types, try the crazies
switch {
// static byte array
case arg.Type.T == abi.FixedBytesTy:
reflect.Copy(field, reflect.ValueOf(topics[0][:arg.Type.Size]))
default:
return fmt.Errorf("unsupported indexed type: %v", arg.Type)
}
}
}
topics = topics[1:]
}
return nil
return parseTopicWithSetter(fields, topics,
func(arg abi.Argument, reconstr interface{}) {
field := reflect.ValueOf(out).Elem().FieldByName(capitalise(arg.Name))
field.Set(reflect.ValueOf(reconstr))
})
}

// parseTopicsIntoMap converts the indexed topic field-value pairs into map key-value pairs
func parseTopicsIntoMap(out map[string]interface{}, fields abi.Arguments, topics []common.Hash) error {
return parseTopicWithSetter(fields, topics,
func(arg abi.Argument, reconstr interface{}) {
out[arg.Name] = reconstr
})
}

// parseTopicWithSetter converts the indexed topic field-value pairs and stores them using the
// provided set function.
//
// Note, dynamic types cannot be reconstructed since they get mapped to Keccak256
// hashes as the topic value!
func parseTopicWithSetter(fields abi.Arguments, topics []common.Hash, setter func(abi.Argument, interface{})) error {
// Sanity check that the fields and topics match up
if len(fields) != len(topics) {
return errors.New("topic/field count mismatch")
}
// Iterate over all the fields and reconstruct them from topics
for _, arg := range fields {
for i, arg := range fields {
if !arg.Indexed {
return errors.New("non-indexed field in topic reconstruction")
}

var reconstr interface{}
switch arg.Type.T {
case abi.BoolTy:
out[arg.Name] = topics[0][common.HashLength-1] == 1
case abi.IntTy, abi.UintTy:
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:
array, err := abi.ReadFixedBytes(arg.Type, topics[0].Bytes())
if err != nil {
return err
}
out[arg.Name] = array
case abi.TupleTy:
return errors.New("tuple type in topic reconstruction")
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
out[arg.Name] = topics[0]
reconstr = topics[i]
case abi.FunctionTy:
if garbage := binary.BigEndian.Uint64(topics[0][0:8]); garbage != 0 {
return fmt.Errorf("bind: got improperly encoded function type, got %v", topics[0].Bytes())
if garbage := binary.BigEndian.Uint64(topics[i][0:8]); garbage != 0 {
return fmt.Errorf("bind: got improperly encoded function type, got %v", topics[i].Bytes())
}
var tmp [24]byte
copy(tmp[:], topics[0][8:32])
out[arg.Name] = tmp
default: // Not handling tuples
return fmt.Errorf("unsupported indexed type: %v", arg.Type)
copy(tmp[:], topics[i][8:32])
reconstr = tmp
default:
var err error
reconstr, err = abi.ToGoType(0, arg.Type, topics[i].Bytes())
if err != nil {
return err
}
}

topics = topics[1:]
// Use the setter function to store the value
setter(arg, reconstr)
}

return nil
Expand Down
16 changes: 16 additions & 0 deletions accounts/abi/bind/topics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ func setupTopicsTests() []topicTest {
bytesType, _ := abi.NewType("bytes5", "", nil)
int8Type, _ := abi.NewType("int8", "", nil)
int256Type, _ := abi.NewType("int256", "", nil)
tupleType, _ := abi.NewType("tuple(int256,int8)", "", nil)

tests := []topicTest{
{
Expand Down Expand Up @@ -145,6 +146,21 @@ func setupTopicsTests() []topicTest {
},
wantErr: false,
},
{
name: "tuple(int256, int8)",
args: args{
createObj: func() interface{} { return nil },
resultObj: func() interface{} { return nil },
resultMap: func() map[string]interface{} { return make(map[string]interface{}) },
fields: abi.Arguments{abi.Argument{
Name: "tupletype",
Type: tupleType,
Indexed: true,
}},
topics: []common.Hash{},
},
wantErr: true,
},
}

return tests
Expand Down
8 changes: 4 additions & 4 deletions accounts/abi/unpack.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error)
elemSize := getTypeSize(*t.Elem)

for i, j := start, 0; j < size; i, j = i+elemSize, j+1 {
inter, err := toGoType(i, *t.Elem, output)
inter, err := ToGoType(i, *t.Elem, output)
if err != nil {
return nil, err
}
Expand All @@ -161,7 +161,7 @@ func forTupleUnpack(t Type, output []byte) (interface{}, error) {
retval := reflect.New(t.Type).Elem()
virtualArgs := 0
for index, elem := range t.TupleElems {
marshalledValue, err := toGoType((index+virtualArgs)*32, *elem, output)
marshalledValue, err := ToGoType((index+virtualArgs)*32, *elem, output)
if elem.T == ArrayTy && !isDynamicType(*elem) {
// If we have a static array, like [3]uint256, these are coded as
// just like uint256,uint256,uint256.
Expand All @@ -187,9 +187,9 @@ func forTupleUnpack(t Type, output []byte) (interface{}, error) {
return retval.Interface(), nil
}

// toGoType parses the output bytes and recursively assigns the value of these bytes
// ToGoType parses the output bytes and recursively assigns the value of these bytes
// into a go type with accordance with the ABI spec.
func toGoType(index int, t Type, output []byte) (interface{}, error) {
func ToGoType(index int, t Type, output []byte) (interface{}, error) {
if index+32 > len(output) {
return nil, fmt.Errorf("abi: cannot marshal in to go type: length insufficient %d require %d", len(output), index+32)
}
Expand Down