mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
add methods to abi that allow for the unpacking of event logs into maps (allows for agnostic unpacking of logs)
This commit is contained in:
parent
1a29bf0ee2
commit
8be278dda8
5 changed files with 152 additions and 0 deletions
|
|
@ -89,6 +89,24 @@ func (abi ABI) Unpack(v interface{}, name string, output []byte) (err error) {
|
||||||
return fmt.Errorf("abi: could not locate named method or event")
|
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.UnpackIntoMap(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
|
// UnmarshalJSON implements json.Unmarshaler interface
|
||||||
func (abi *ABI) UnmarshalJSON(data []byte) error {
|
func (abi *ABI) UnmarshalJSON(data []byte) error {
|
||||||
var fields []struct {
|
var fields []struct {
|
||||||
|
|
|
||||||
|
|
@ -694,6 +694,62 @@ func TestUnpackEvent(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnpackIntoMapEvent(t *testing.T) {
|
||||||
|
const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"}],"name":"receivedAddr","type":"event"}]`
|
||||||
|
abi, err := JSON(strings.NewReader(abiJSON))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
const hexdata = `000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158`
|
||||||
|
data, err := hex.DecodeString(hexdata)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(data)%32 == 0 {
|
||||||
|
t.Errorf("len(data) is %d, want a non-multiple of 32", len(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
receivedMap := map[string]interface{}{}
|
||||||
|
expectedReceivedMap := map[string]interface{}{
|
||||||
|
"sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"),
|
||||||
|
"amount": big.NewInt(1),
|
||||||
|
"memo": []uint8{88},
|
||||||
|
}
|
||||||
|
|
||||||
|
err = abi.UnpackIntoMap(receivedMap, "received", data)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if receivedMap["sender"] != expectedReceivedMap["sender"] {
|
||||||
|
t.Errorf("unpacked map does not match expected map")
|
||||||
|
}
|
||||||
|
|
||||||
|
if receivedMap["amount"].(*big.Int).String() != expectedReceivedMap["amount"].(*big.Int).String() {
|
||||||
|
t.Errorf("unpacked map does not match expected map")
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 := receivedMap["memo"].([]uint8)
|
||||||
|
expectedU8 := expectedReceivedMap["memo"].([]uint8)
|
||||||
|
for i, v := range expectedU8 {
|
||||||
|
if u8[i] != v {
|
||||||
|
t.Errorf("unpacked map does not match expected map")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
receivedAddrMap := map[string]interface{}{}
|
||||||
|
|
||||||
|
err = abi.UnpackIntoMap(receivedAddrMap, "receivedAddr", data)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if receivedAddrMap["sender"] != expectedReceivedMap["sender"] {
|
||||||
|
t.Errorf("unpacked map does not match expected map")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestABI_MethodById(t *testing.T) {
|
func TestABI_MethodById(t *testing.T) {
|
||||||
const abiJSON = `[
|
const abiJSON = `[
|
||||||
{"type":"function","name":"receive","constant":false,"inputs":[{"name":"memo","type":"bytes"}],"outputs":[],"payable":true,"stateMutability":"payable"},
|
{"type":"function","name":"receive","constant":false,"inputs":[{"name":"memo","type":"bytes"}],"outputs":[],"payable":true,"stateMutability":"payable"},
|
||||||
|
|
|
||||||
|
|
@ -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])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unpack 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 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unpack arguments into map
|
||||||
|
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)
|
||||||
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 {
|
||||||
|
|
|
||||||
|
|
@ -187,3 +187,42 @@ func parseTopics(out interface{}, fields abi.Arguments, topics []common.Hash) er
|
||||||
}
|
}
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue