From 5307de2b97f294c987bc559946964eb965c09505 Mon Sep 17 00:00:00 2001 From: Ian Norden Date: Sat, 3 Nov 2018 14:01:41 -0500 Subject: [PATCH] modify go-etheruem vendor lib with methods that allow for unpacking of events into maps of interfaces --- .../ethereum/go-ethereum/accounts/abi/abi.go | 18 +++++++++ .../go-ethereum/accounts/abi/argument.go | 40 +++++++++++++++++++ .../go-ethereum/accounts/abi/bind/base.go | 16 ++++++++ .../go-ethereum/accounts/abi/bind/topics.go | 39 ++++++++++++++++++ 4 files changed, 113 insertions(+) diff --git a/vendor/github.com/ethereum/go-ethereum/accounts/abi/abi.go b/vendor/github.com/ethereum/go-ethereum/accounts/abi/abi.go index 254b1f7f..ecd7432f 100644 --- a/vendor/github.com/ethereum/go-ethereum/accounts/abi/abi.go +++ b/vendor/github.com/ethereum/go-ethereum/accounts/abi/abi.go @@ -91,6 +91,24 @@ func (abi ABI) Unpack(v interface{}, name string, output []byte) (err error) { return fmt.Errorf("abi: could not locate named method or event") } +// Unpack output into a map according to the abi specification +func (abi ABI) UnpackIntoMap(v map[string]interface{}, name string, output []byte) (err error) { + if len(output) == 0 { + return fmt.Errorf("abi: unmarshalling empty output") + } + // since there can't be naming collisions with contracts and events, + // we need to decide whether we're calling a method or an event + if method, ok := abi.Methods[name]; ok { + if len(output)%32 != 0 { + return fmt.Errorf("abi: improperly formatted output") + } + return method.Outputs.Unpack(v, output) + } else if event, ok := abi.Events[name]; ok { + return event.Inputs.UnpackIntoMap(v, output) + } + return fmt.Errorf("abi: could not locate named method or event") +} + // UnmarshalJSON implements json.Unmarshaler interface func (abi *ABI) UnmarshalJSON(data []byte) error { var fields []struct { diff --git a/vendor/github.com/ethereum/go-ethereum/accounts/abi/argument.go b/vendor/github.com/ethereum/go-ethereum/accounts/abi/argument.go index 1b480da6..4f796fd1 100644 --- a/vendor/github.com/ethereum/go-ethereum/accounts/abi/argument.go +++ b/vendor/github.com/ethereum/go-ethereum/accounts/abi/argument.go @@ -100,6 +100,16 @@ func (arguments Arguments) Unpack(v interface{}, data []byte) error { return arguments.unpackAtomic(v, marshalledValues) } +// Unpack performs the operation hexdata -> Go format +func (arguments Arguments) UnpackIntoMap(v map[string]interface{}, data []byte) error { + marshalledValues, err := arguments.UnpackValues(data) + if err != nil { + return err + } + + return arguments.unpackIntoMap(v, marshalledValues) +} + func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interface{}) error { var ( @@ -159,6 +169,36 @@ func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interfa return nil } +// Unpack arguments into map +func (arguments Arguments) unpackIntoMap(v map[string]interface{}, marshalledValues []interface{}) error { + // Make sure fields exist in map + exists := make(map[string]bool) + for _, arg := range arguments { + field := arg.Name + if field == "" { + return fmt.Errorf("abi: purely underscored output cannot unpack to map") + } + if exists[field] { + return fmt.Errorf("abi: multiple outputs mapping to the same map field '%s'", field) + } + exists[field] = true + } + + for name, _ := range exists { + _, ok := v[name] + if !ok { + return fmt.Errorf("abi: output map missing argument name") + } + } + + for i, arg := range arguments.NonIndexed() { + reflectValue := reflect.ValueOf(marshalledValues[i]) + v[arg.Name] = reflectValue + } + + return nil +} + // unpackAtomic unpacks ( hexdata -> go ) a single value func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues []interface{}) error { if len(marshalledValues) != 1 { diff --git a/vendor/github.com/ethereum/go-ethereum/accounts/abi/bind/base.go b/vendor/github.com/ethereum/go-ethereum/accounts/abi/bind/base.go index 83ad1c8a..2da4f449 100644 --- a/vendor/github.com/ethereum/go-ethereum/accounts/abi/bind/base.go +++ b/vendor/github.com/ethereum/go-ethereum/accounts/abi/bind/base.go @@ -340,6 +340,22 @@ func (c *BoundContract) UnpackLog(out interface{}, event string, log types.Log) return parseTopics(out, indexed, log.Topics[1:]) } +// UnpackLogIntoMap unpacks a retrieved log into the provided map. +func (c *BoundContract) UnpackLogIntoMap(out map[string]interface{}, event string, log types.Log) error { + if len(log.Data) > 0 { + if err := c.abi.UnpackIntoMap(out, event, log.Data); err != nil { + return err + } + } + var indexed abi.Arguments + for _, arg := range c.abi.Events[event].Inputs { + if arg.Indexed { + indexed = append(indexed, arg) + } + } + return parseTopicsIntoMap(out, indexed, log.Topics[1:]) +} + // ensureContext is a helper method to ensure a context is not nil, even if the // user specified it as such. func ensureContext(ctx context.Context) context.Context { diff --git a/vendor/github.com/ethereum/go-ethereum/accounts/abi/bind/topics.go b/vendor/github.com/ethereum/go-ethereum/accounts/abi/bind/topics.go index 600dfcda..cd84fb16 100644 --- a/vendor/github.com/ethereum/go-ethereum/accounts/abi/bind/topics.go +++ b/vendor/github.com/ethereum/go-ethereum/accounts/abi/bind/topics.go @@ -187,3 +187,42 @@ func parseTopics(out interface{}, fields abi.Arguments, topics []common.Hash) er } return nil } + +// parseTopics 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 { + // 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") + } + + switch arg.Type.T { + case abi.BoolTy: + if topics[0][common.HashLength-1] == 1 { + out[arg.Name] = true + } else { + out[arg.Name] = false + } + case abi.IntTy, abi.UintTy: + num := new(big.Int).SetBytes(topics[0][:]) + out[arg.Name] = num + 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.BytesTy, abi.FixedBytesTy: + out[arg.Name] = topics[0][:] + default: + } + + topics = topics[1:] + } + + return nil +}