Commit cd79bc61 authored by Ian Norden's avatar Ian Norden Committed by Guillaume Ballet

accounts/abi: generic unpacking of event logs into map[string]interface{} (#18440)

Add methods that allow for the unpacking of event logs into maps (allows for agnostic unpacking of logs)
parent 86e77900
...@@ -72,19 +72,39 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) { ...@@ -72,19 +72,39 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
} }
// Unpack output in v according to the abi specification // Unpack output in v according to the abi specification
func (abi ABI) Unpack(v interface{}, name string, output []byte) (err error) { func (abi ABI) Unpack(v interface{}, name string, data []byte) (err error) {
if len(output) == 0 { if len(data) == 0 {
return fmt.Errorf("abi: unmarshalling empty output") return fmt.Errorf("abi: unmarshalling empty output")
} }
// since there can't be naming collisions with contracts and events, // since there can't be naming collisions with contracts and events,
// we need to decide whether we're calling a method or an event // we need to decide whether we're calling a method or an event
if method, ok := abi.Methods[name]; ok { if method, ok := abi.Methods[name]; ok {
if len(output)%32 != 0 { if len(data)%32 != 0 {
return fmt.Errorf("abi: improperly formatted output: %s - Bytes: [%+v]", string(output), output) return fmt.Errorf("abi: improperly formatted output: %s - Bytes: [%+v]", string(data), data)
} }
return method.Outputs.Unpack(v, output) return method.Outputs.Unpack(v, data)
} else if event, ok := abi.Events[name]; ok { }
return event.Inputs.Unpack(v, output) if event, ok := abi.Events[name]; ok {
return event.Inputs.Unpack(v, data)
}
return fmt.Errorf("abi: could not locate named method or event")
}
// UnpackIntoMap unpacks a log into the provided map[string]interface{}
func (abi ABI) UnpackIntoMap(v map[string]interface{}, name string, data []byte) (err error) {
if len(data) == 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(data)%32 != 0 {
return fmt.Errorf("abi: improperly formatted output")
}
return method.Outputs.UnpackIntoMap(v, data)
}
if event, ok := abi.Events[name]; ok {
return event.Inputs.UnpackIntoMap(v, data)
} }
return fmt.Errorf("abi: could not locate named method or event") return fmt.Errorf("abi: could not locate named method or event")
} }
......
This diff is collapsed.
...@@ -102,6 +102,16 @@ func (arguments Arguments) Unpack(v interface{}, data []byte) error { ...@@ -102,6 +102,16 @@ func (arguments Arguments) Unpack(v interface{}, data []byte) error {
return arguments.unpackAtomic(v, marshalledValues[0]) return arguments.unpackAtomic(v, marshalledValues[0])
} }
// UnpackIntoMap performs the operation hexdata -> mapping of argument name to argument value
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)
}
// unpack sets the unmarshalled value to go format. // unpack sets the unmarshalled value to go format.
// Note the dst here must be settable. // Note the dst here must be settable.
func unpack(t *Type, dst interface{}, src interface{}) error { func unpack(t *Type, dst interface{}, src interface{}) error {
...@@ -160,6 +170,19 @@ func unpack(t *Type, dst interface{}, src interface{}) error { ...@@ -160,6 +170,19 @@ func unpack(t *Type, dst interface{}, src interface{}) error {
return nil return nil
} }
// unpackIntoMap unpacks marshalledValues into the provided map[string]interface{}
func (arguments Arguments) unpackIntoMap(v map[string]interface{}, marshalledValues []interface{}) error {
// Make sure map is not nil
if v == nil {
return fmt.Errorf("abi: cannot unpack into a nil map")
}
for i, arg := range arguments.NonIndexed() {
v[arg.Name] = marshalledValues[i]
}
return nil
}
// unpackAtomic unpacks ( hexdata -> go ) a single value // unpackAtomic unpacks ( hexdata -> go ) a single value
func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues interface{}) error { func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues interface{}) error {
if arguments.LengthNonIndexed() == 0 { if arguments.LengthNonIndexed() == 0 {
......
...@@ -340,6 +340,22 @@ func (c *BoundContract) UnpackLog(out interface{}, event string, log types.Log) ...@@ -340,6 +340,22 @@ func (c *BoundContract) UnpackLog(out interface{}, event string, log types.Log)
return parseTopics(out, indexed, log.Topics[1:]) 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 // ensureContext is a helper method to ensure a context is not nil, even if the
// user specified it as such. // user specified it as such.
func ensureContext(ctx context.Context) context.Context { func ensureContext(ctx context.Context) context.Context {
......
This diff is collapsed.
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
package bind package bind
import ( import (
"encoding/binary"
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
...@@ -191,3 +192,50 @@ func parseTopics(out interface{}, fields abi.Arguments, topics []common.Hash) er ...@@ -191,3 +192,50 @@ func parseTopics(out interface{}, fields abi.Arguments, topics []common.Hash) er
} }
return nil return nil
} }
// 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 {
// 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:
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
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][:]
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]
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())
}
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)
}
topics = topics[1:]
}
return nil
}
...@@ -269,7 +269,7 @@ func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err ...@@ -269,7 +269,7 @@ func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err
totalSize.Add(totalSize, bigOffsetEnd) totalSize.Add(totalSize, bigOffsetEnd)
totalSize.Add(totalSize, lengthBig) totalSize.Add(totalSize, lengthBig)
if totalSize.BitLen() > 63 { if totalSize.BitLen() > 63 {
return 0, 0, fmt.Errorf("abi length larger than int64: %v", totalSize) return 0, 0, fmt.Errorf("abi: length larger than int64: %v", totalSize)
} }
if totalSize.Cmp(outputLength) > 0 { if totalSize.Cmp(outputLength) > 0 {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment