From 8be278dda8c19a7971a8fdb45368f3f408990cad Mon Sep 17 00:00:00 2001 From: Ian Norden Date: Mon, 14 Jan 2019 10:45:02 -0600 Subject: [PATCH 1/3] add methods to abi that allow for the unpacking of event logs into maps (allows for agnostic unpacking of logs) --- accounts/abi/abi.go | 18 ++++++++++++ accounts/abi/abi_test.go | 56 +++++++++++++++++++++++++++++++++++++ accounts/abi/argument.go | 23 +++++++++++++++ accounts/abi/bind/base.go | 16 +++++++++++ accounts/abi/bind/topics.go | 39 ++++++++++++++++++++++++++ 5 files changed, 152 insertions(+) diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index ba1774c6470e..65a983f009cc 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -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") } +// 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 func (abi *ABI) UnmarshalJSON(data []byte) error { var fields []struct { diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index b9444f9f0d94..6c2b7c2f56ec 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -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) { const abiJSON = `[ {"type":"function","name":"receive","constant":false,"inputs":[{"name":"memo","type":"bytes"}],"outputs":[],"payable":true,"stateMutability":"payable"}, diff --git a/accounts/abi/argument.go b/accounts/abi/argument.go index d0a6b035c66a..49cbf1399753 100644 --- a/accounts/abi/argument.go +++ b/accounts/abi/argument.go @@ -102,6 +102,16 @@ func (arguments Arguments) Unpack(v interface{}, data []byte) error { 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. // Note the dst here must be settable. func unpack(t *Type, dst interface{}, src interface{}) error { @@ -160,6 +170,19 @@ func unpack(t *Type, dst interface{}, src interface{}) error { 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 func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues interface{}) error { if arguments.LengthNonIndexed() == 0 { diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index c37bdf11d53a..f70f911d37b2 100644 --- a/accounts/abi/bind/base.go +++ b/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/accounts/abi/bind/topics.go b/accounts/abi/bind/topics.go index 600dfcda9739..cd84fb164c87 100644 --- a/accounts/abi/bind/topics.go +++ b/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 +} From 545294564198add052d38f869cc15403d6caadbc Mon Sep 17 00:00:00 2001 From: Ian Norden Date: Fri, 8 Mar 2019 12:39:23 -0600 Subject: [PATCH 2/3] add missing support for string and a base_test that demonstrates unpacking event with indexed string --- accounts/abi/abi_test.go | 26 +++++++-------- accounts/abi/bind/base_test.go | 59 +++++++++++++++++++++++++++++++++- accounts/abi/bind/topics.go | 10 ++++++ 3 files changed, 81 insertions(+), 14 deletions(-) diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index 6c2b7c2f56ec..f033c0e8ba14 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -694,7 +694,7 @@ func TestUnpackEvent(t *testing.T) { } } -func TestUnpackIntoMapEvent(t *testing.T) { +func TestUnpackEventIntoMap(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 { @@ -716,37 +716,37 @@ func TestUnpackIntoMapEvent(t *testing.T) { "amount": big.NewInt(1), "memo": []uint8{88}, } - - err = abi.UnpackIntoMap(receivedMap, "received", data) - if err != nil { + if err := abi.UnpackIntoMap(receivedMap, "received", data); err != nil { t.Error(err) } + if len(receivedMap) != 3 { + t.Error("unpacked map expected to have length 3") + } if receivedMap["sender"] != expectedReceivedMap["sender"] { - t.Errorf("unpacked map does not match expected map") + t.Error("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") + t.Error("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") + t.Error("unpacked map does not match expected map") } } receivedAddrMap := map[string]interface{}{} - - err = abi.UnpackIntoMap(receivedAddrMap, "receivedAddr", data) - if err != nil { + if err = abi.UnpackIntoMap(receivedAddrMap, "receivedAddr", data); err != nil { t.Error(err) } + if len(receivedAddrMap) != 1 { + t.Error("unpacked map expected to have length 1") + } if receivedAddrMap["sender"] != expectedReceivedMap["sender"] { - t.Errorf("unpacked map does not match expected map") + t.Error("unpacked map does not match expected map") } } diff --git a/accounts/abi/bind/base_test.go b/accounts/abi/bind/base_test.go index 02caf457a444..1e47ae285e47 100644 --- a/accounts/abi/bind/base_test.go +++ b/accounts/abi/bind/base_test.go @@ -18,10 +18,13 @@ package bind_test import ( "context" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" "math/big" + "strings" "testing" - ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" @@ -78,3 +81,57 @@ func TestPassingBlockNumber(t *testing.T) { t.Fatalf("CodeAt() was passed a block number when it should not have been") } } + +func TestUnpackIntoMap(t *testing.T) { + hexData := "0x000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158" + mockLog := types.Log{ + Address: common.HexToAddress("0x0"), + Topics: []common.Hash{ + common.HexToHash("0x99b5620489b6ef926d4518936cfec15d305452712b88bd59da2d9c10fb0953e8"), + common.BytesToHash([]byte("testName")), + }, + Data: hexutil.MustDecode(hexData), + BlockNumber: uint64(26), + TxHash: common.HexToHash("0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42"), + TxIndex: 111, + BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}), + Index: 7, + Removed: false, + } + + // This event has an indexed string, which cannot be handled by the normal Unpack method + abiString := `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"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"}]` + parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) + bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) + + receivedMap := make(map[string]interface{}) + expectedReceivedMap := map[string]interface{}{ + "name": "testName", + "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), + "amount": big.NewInt(1), + "memo": []uint8{88}, + } + if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil { + t.Error(err) + } + + if len(receivedMap) != 4 { + t.Fatal("unpacked map expected to have length 4") + } + if receivedMap["name"] != expectedReceivedMap["name"] { + t.Error("unpacked map does not match expected map") + } + if receivedMap["sender"] != expectedReceivedMap["sender"] { + t.Error("unpacked map does not match expected map") + } + if receivedMap["amount"].(*big.Int).String() != expectedReceivedMap["amount"].(*big.Int).String() { + t.Error("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.Error("unpacked map does not match expected map") + } + } +} diff --git a/accounts/abi/bind/topics.go b/accounts/abi/bind/topics.go index cd84fb164c87..1569fae5495c 100644 --- a/accounts/abi/bind/topics.go +++ b/accounts/abi/bind/topics.go @@ -218,6 +218,16 @@ func parseTopicsIntoMap(out map[string]interface{}, fields abi.Arguments, topics out[arg.Name] = topics[0] case abi.BytesTy, abi.FixedBytesTy: out[arg.Name] = topics[0][:] + case abi.StringTy: + bytes := topics[0].Bytes() + var trimmedBytes []byte + for i, by := range bytes { + if by != 0 { + trimmedBytes = bytes[i:] + break + } + } + out[arg.Name] = string(trimmedBytes) default: } From 8b3364a23494d5cf2d4aaeb67265001ecfc1b7fa Mon Sep 17 00:00:00 2001 From: Ian Norden Date: Fri, 15 Mar 2019 12:27:43 -0500 Subject: [PATCH 3/3] review fixes/changes --- accounts/abi/abi.go | 30 ++-- accounts/abi/abi_test.go | 160 +++++++++++++++++-- accounts/abi/argument.go | 4 +- accounts/abi/bind/base_test.go | 274 ++++++++++++++++++++++++++++++--- accounts/abi/bind/topics.go | 33 ++-- accounts/abi/unpack.go | 2 +- 6 files changed, 435 insertions(+), 68 deletions(-) diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 65a983f009cc..c5fbc1e77e9f 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -72,37 +72,39 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) { } // Unpack output in v according to the abi specification -func (abi ABI) Unpack(v interface{}, name string, output []byte) (err error) { - if len(output) == 0 { +func (abi ABI) Unpack(v 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(output)%32 != 0 { - return fmt.Errorf("abi: improperly formatted output: %s - Bytes: [%+v]", string(output), output) + if len(data)%32 != 0 { + return fmt.Errorf("abi: improperly formatted output: %s - Bytes: [%+v]", string(data), data) } - return method.Outputs.Unpack(v, output) - } else if event, ok := abi.Events[name]; ok { - return event.Inputs.Unpack(v, output) + return method.Outputs.Unpack(v, data) + } + if event, ok := abi.Events[name]; ok { + return event.Inputs.Unpack(v, data) } 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 { +// 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(output)%32 != 0 { + if len(data)%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 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") } diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index f033c0e8ba14..42b60a6395d0 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -714,39 +714,169 @@ func TestUnpackEventIntoMap(t *testing.T) { expectedReceivedMap := map[string]interface{}{ "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), "amount": big.NewInt(1), - "memo": []uint8{88}, + "memo": []byte{88}, } if err := abi.UnpackIntoMap(receivedMap, "received", data); err != nil { t.Error(err) } - if len(receivedMap) != 3 { - t.Error("unpacked map expected to have length 3") + t.Error("unpacked `received` map expected to have length 3") } if receivedMap["sender"] != expectedReceivedMap["sender"] { - t.Error("unpacked map does not match expected map") + t.Error("unpacked `received` map does not match expected map") } - if receivedMap["amount"].(*big.Int).String() != expectedReceivedMap["amount"].(*big.Int).String() { - t.Error("unpacked map does not match expected map") + if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 { + t.Error("unpacked `received` map does not match expected map") } - u8 := receivedMap["memo"].([]uint8) - expectedU8 := expectedReceivedMap["memo"].([]uint8) - for i, v := range expectedU8 { - if u8[i] != v { - t.Error("unpacked map does not match expected map") - } + if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) { + t.Error("unpacked `received` map does not match expected map") } receivedAddrMap := map[string]interface{}{} if err = abi.UnpackIntoMap(receivedAddrMap, "receivedAddr", data); err != nil { t.Error(err) } - if len(receivedAddrMap) != 1 { - t.Error("unpacked map expected to have length 1") + t.Error("unpacked `receivedAddr` map expected to have length 1") } if receivedAddrMap["sender"] != expectedReceivedMap["sender"] { - t.Error("unpacked map does not match expected map") + t.Error("unpacked `receivedAddr` map does not match expected map") + } +} + +func TestUnpackMethodIntoMap(t *testing.T) { + const abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"send","outputs":[{"name":"amount","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"get","outputs":[{"name":"hash","type":"bytes"}],"payable":true,"stateMutability":"payable","type":"function"}]` + abi, err := JSON(strings.NewReader(abiJSON)) + if err != nil { + t.Fatal(err) + } + const hexdata = `00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000015800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000158000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001580000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000015800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000158` + data, err := hex.DecodeString(hexdata) + if err != nil { + t.Fatal(err) + } + if len(data)%32 != 0 { + t.Errorf("len(data) is %d, want a multiple of 32", len(data)) + } + + // Tests a method with no outputs + receiveMap := map[string]interface{}{} + if err = abi.UnpackIntoMap(receiveMap, "receive", data); err != nil { + t.Error(err) + } + if len(receiveMap) > 0 { + t.Error("unpacked `receive` map expected to have length 0") + } + + // Tests a method with only outputs + sendMap := map[string]interface{}{} + if err = abi.UnpackIntoMap(sendMap, "send", data); err != nil { + t.Error(err) + } + if len(sendMap) != 1 { + t.Error("unpacked `send` map expected to have length 1") + } + if sendMap["amount"].(*big.Int).Cmp(big.NewInt(1)) != 0 { + t.Error("unpacked `send` map expected `amount` value of 1") + } + + // Tests a method with outputs and inputs + getMap := map[string]interface{}{} + if err = abi.UnpackIntoMap(getMap, "get", data); err != nil { + t.Error(err) + } + if len(sendMap) != 1 { + t.Error("unpacked `get` map expected to have length 1") + } + expectedBytes := []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 88, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 96, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 88, 0} + if !bytes.Equal(getMap["hash"].([]byte), expectedBytes) { + t.Errorf("unpacked `get` map expected `hash` value of %v", expectedBytes) + } +} + +func TestUnpackIntoMapNamingConflict(t *testing.T) { + // Two methods have the same name + var abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"get","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[],"name":"send","outputs":[{"name":"amount","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"addr","type":"address"}],"name":"get","outputs":[{"name":"hash","type":"bytes"}],"payable":true,"stateMutability":"payable","type":"function"}]` + abi, err := JSON(strings.NewReader(abiJSON)) + if err != nil { + t.Fatal(err) + } + var hexdata = `00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158` + 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)) + } + getMap := map[string]interface{}{} + if err = abi.UnpackIntoMap(getMap, "get", data); err == nil { + t.Error("naming conflict between two methods; error expected") + } + + // Two events have the same name + 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":"received","type":"event"}]` + abi, err = JSON(strings.NewReader(abiJSON)) + if err != nil { + t.Fatal(err) + } + 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{}{} + if err = abi.UnpackIntoMap(receivedMap, "received", data); err != nil { + t.Error("naming conflict between two events; no error expected") + } + if len(receivedMap) != 1 { + t.Error("naming conflict between two events; event defined latest in the abi expected to be used") + } + + // Method and event have the same name + abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"received","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) + } + if len(data)%32 == 0 { + t.Errorf("len(data) is %d, want a non-multiple of 32", len(data)) + } + if err = abi.UnpackIntoMap(receivedMap, "received", data); err == nil { + t.Error("naming conflict between an event and a method; error expected") + } + + // Conflict is case sensitive + abiJSON = `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"received","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) + } + if len(data)%32 == 0 { + t.Errorf("len(data) is %d, want a non-multiple of 32", len(data)) + } + expectedReceivedMap := map[string]interface{}{ + "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), + "amount": big.NewInt(1), + "memo": []byte{88}, + } + if err = abi.UnpackIntoMap(receivedMap, "Received", data); err != nil { + t.Error(err) + } + if len(receivedMap) != 3 { + t.Error("unpacked `received` map expected to have length 3") + } + if receivedMap["sender"] != expectedReceivedMap["sender"] { + t.Error("unpacked `received` map does not match expected map") + } + if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 { + t.Error("unpacked `received` map does not match expected map") + } + if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) { + t.Error("unpacked `received` map does not match expected map") } } diff --git a/accounts/abi/argument.go b/accounts/abi/argument.go index 49cbf1399753..501cb1621397 100644 --- a/accounts/abi/argument.go +++ b/accounts/abi/argument.go @@ -102,7 +102,7 @@ func (arguments Arguments) Unpack(v interface{}, data []byte) error { return arguments.unpackAtomic(v, marshalledValues[0]) } -// Unpack performs the operation hexdata -> mapping of argument name to argument value +// 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 { @@ -170,7 +170,7 @@ func unpack(t *Type, dst interface{}, src interface{}) error { return nil } -// Unpack arguments into map +// 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 { diff --git a/accounts/abi/bind/base_test.go b/accounts/abi/bind/base_test.go index 1e47ae285e47..f65c9e9b4963 100644 --- a/accounts/abi/bind/base_test.go +++ b/accounts/abi/bind/base_test.go @@ -17,9 +17,8 @@ package bind_test import ( + "bytes" "context" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core/types" "math/big" "strings" "testing" @@ -28,6 +27,10 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" ) type mockCaller struct { @@ -44,7 +47,6 @@ func (mc *mockCaller) CallContract(ctx context.Context, call ethereum.CallMsg, b mc.callContractBlockNumber = blockNumber return nil, nil } - func TestPassingBlockNumber(t *testing.T) { mc := &mockCaller{} @@ -82,34 +84,35 @@ func TestPassingBlockNumber(t *testing.T) { } } -func TestUnpackIntoMap(t *testing.T) { - hexData := "0x000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158" +const hexData = "0x000000000000000000000000376c47978271565f56deb45495afa69e59c16ab200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000158" + +func TestUnpackIndexedStringTyLogIntoMap(t *testing.T) { + hash := crypto.Keccak256Hash([]byte("testName")) mockLog := types.Log{ Address: common.HexToAddress("0x0"), Topics: []common.Hash{ - common.HexToHash("0x99b5620489b6ef926d4518936cfec15d305452712b88bd59da2d9c10fb0953e8"), - common.BytesToHash([]byte("testName")), + common.HexToHash("0x0"), + hash, }, Data: hexutil.MustDecode(hexData), BlockNumber: uint64(26), - TxHash: common.HexToHash("0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42"), + TxHash: common.HexToHash("0x0"), TxIndex: 111, BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}), Index: 7, Removed: false, } - // This event has an indexed string, which cannot be handled by the normal Unpack method - abiString := `[{"constant":false,"inputs":[{"name":"memo","type":"bytes"}],"name":"receive","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"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"}]` + abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) receivedMap := make(map[string]interface{}) expectedReceivedMap := map[string]interface{}{ - "name": "testName", + "name": hash, "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), "amount": big.NewInt(1), - "memo": []uint8{88}, + "memo": []byte{88}, } if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil { t.Error(err) @@ -124,14 +127,247 @@ func TestUnpackIntoMap(t *testing.T) { if receivedMap["sender"] != expectedReceivedMap["sender"] { t.Error("unpacked map does not match expected map") } - if receivedMap["amount"].(*big.Int).String() != expectedReceivedMap["amount"].(*big.Int).String() { + if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 { + t.Error("unpacked map does not match expected map") + } + if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) { + t.Error("unpacked map does not match expected map") + } +} + +func TestUnpackIndexedSliceTyLogIntoMap(t *testing.T) { + sliceBytes, err := rlp.EncodeToBytes([]string{"name1", "name2", "name3", "name4"}) + if err != nil { + t.Fatal(err) + } + hash := crypto.Keccak256Hash(sliceBytes) + mockLog := types.Log{ + Address: common.HexToAddress("0x0"), + Topics: []common.Hash{ + common.HexToHash("0x0"), + hash, + }, + Data: hexutil.MustDecode(hexData), + BlockNumber: uint64(26), + TxHash: common.HexToHash("0x0"), + TxIndex: 111, + BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}), + Index: 7, + Removed: false, + } + + abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"names","type":"string[]"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` + parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) + bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) + + receivedMap := make(map[string]interface{}) + expectedReceivedMap := map[string]interface{}{ + "names": hash, + "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), + "amount": big.NewInt(1), + "memo": []byte{88}, + } + if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil { + t.Error(err) + } + + if len(receivedMap) != 4 { + t.Fatal("unpacked map expected to have length 4") + } + if receivedMap["names"] != expectedReceivedMap["names"] { + t.Error("unpacked map does not match expected map") + } + if receivedMap["sender"] != expectedReceivedMap["sender"] { + t.Error("unpacked map does not match expected map") + } + if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 { + t.Error("unpacked map does not match expected map") + } + if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) { t.Error("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.Error("unpacked map does not match expected map") - } +} + +func TestUnpackIndexedArrayTyLogIntoMap(t *testing.T) { + arrBytes, err := rlp.EncodeToBytes([2]common.Address{common.HexToAddress("0x0"), common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2")}) + if err != nil { + t.Fatal(err) + } + hash := crypto.Keccak256Hash(arrBytes) + mockLog := types.Log{ + Address: common.HexToAddress("0x0"), + Topics: []common.Hash{ + common.HexToHash("0x0"), + hash, + }, + Data: hexutil.MustDecode(hexData), + BlockNumber: uint64(26), + TxHash: common.HexToHash("0x0"), + TxIndex: 111, + BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}), + Index: 7, + Removed: false, + } + + abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"addresses","type":"address[2]"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` + parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) + bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) + + receivedMap := make(map[string]interface{}) + expectedReceivedMap := map[string]interface{}{ + "addresses": hash, + "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), + "amount": big.NewInt(1), + "memo": []byte{88}, + } + if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil { + t.Error(err) + } + + if len(receivedMap) != 4 { + t.Fatal("unpacked map expected to have length 4") + } + if receivedMap["addresses"] != expectedReceivedMap["addresses"] { + t.Error("unpacked map does not match expected map") + } + if receivedMap["sender"] != expectedReceivedMap["sender"] { + t.Error("unpacked map does not match expected map") + } + if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 { + t.Error("unpacked map does not match expected map") + } + if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) { + t.Error("unpacked map does not match expected map") + } +} + +func TestUnpackIndexedFuncTyLogIntoMap(t *testing.T) { + mockAddress := common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2") + addrBytes := mockAddress.Bytes() + hash := crypto.Keccak256Hash([]byte("mockFunction(address,uint)")) + functionSelector := hash[:4] + functionTyBytes := append(addrBytes, functionSelector...) + var functionTy [24]byte + copy(functionTy[:], functionTyBytes[0:24]) + mockLog := types.Log{ + Address: common.HexToAddress("0x0"), + Topics: []common.Hash{ + common.HexToHash("0x99b5620489b6ef926d4518936cfec15d305452712b88bd59da2d9c10fb0953e8"), + common.BytesToHash(functionTyBytes), + }, + Data: hexutil.MustDecode(hexData), + BlockNumber: uint64(26), + TxHash: common.HexToHash("0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42"), + TxIndex: 111, + BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}), + Index: 7, + Removed: false, + } + + abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"function","type":"function"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` + parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) + bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) + + receivedMap := make(map[string]interface{}) + expectedReceivedMap := map[string]interface{}{ + "function": functionTy, + "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), + "amount": big.NewInt(1), + "memo": []byte{88}, + } + if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil { + t.Error(err) + } + + if len(receivedMap) != 4 { + t.Fatal("unpacked map expected to have length 4") + } + if receivedMap["function"] != expectedReceivedMap["function"] { + t.Error("unpacked map does not match expected map") + } + if receivedMap["sender"] != expectedReceivedMap["sender"] { + t.Error("unpacked map does not match expected map") + } + if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 { + t.Error("unpacked map does not match expected map") + } + if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) { + t.Error("unpacked map does not match expected map") + } +} + +func TestUnpackIndexedBytesTyLogIntoMap(t *testing.T) { + byts := []byte{1, 2, 3, 4, 5} + hash := crypto.Keccak256Hash(byts) + mockLog := types.Log{ + Address: common.HexToAddress("0x0"), + Topics: []common.Hash{ + common.HexToHash("0x99b5620489b6ef926d4518936cfec15d305452712b88bd59da2d9c10fb0953e8"), + hash, + }, + Data: hexutil.MustDecode(hexData), + BlockNumber: uint64(26), + TxHash: common.HexToHash("0x5c698f13940a2153440c6d19660878bc90219d9298fdcf37365aa8d88d40fc42"), + TxIndex: 111, + BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}), + Index: 7, + Removed: false, + } + + abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"content","type":"bytes"},{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"memo","type":"bytes"}],"name":"received","type":"event"}]` + parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) + bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) + + receivedMap := make(map[string]interface{}) + expectedReceivedMap := map[string]interface{}{ + "content": hash, + "sender": common.HexToAddress("0x376c47978271565f56DEB45495afa69E59c16Ab2"), + "amount": big.NewInt(1), + "memo": []byte{88}, + } + if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err != nil { + t.Error(err) + } + + if len(receivedMap) != 4 { + t.Fatal("unpacked map expected to have length 4") + } + if receivedMap["content"] != expectedReceivedMap["content"] { + t.Error("unpacked map does not match expected map") + } + if receivedMap["sender"] != expectedReceivedMap["sender"] { + t.Error("unpacked map does not match expected map") + } + if receivedMap["amount"].(*big.Int).Cmp(expectedReceivedMap["amount"].(*big.Int)) != 0 { + t.Error("unpacked map does not match expected map") + } + if !bytes.Equal(receivedMap["memo"].([]byte), expectedReceivedMap["memo"].([]byte)) { + t.Error("unpacked map does not match expected map") + } +} + +func TestUnpackIntoMapNamingConflict(t *testing.T) { + hash := crypto.Keccak256Hash([]byte("testName")) + mockLog := types.Log{ + Address: common.HexToAddress("0x0"), + Topics: []common.Hash{ + common.HexToHash("0x0"), + hash, + }, + Data: hexutil.MustDecode(hexData), + BlockNumber: uint64(26), + TxHash: common.HexToHash("0x0"), + TxIndex: 111, + BlockHash: common.BytesToHash([]byte{1, 2, 3, 4, 5}), + Index: 7, + Removed: false, + } + + abiString := `[{"anonymous":false,"inputs":[{"indexed":true,"name":"name","type":"string"},{"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":"received","type":"event"}]` + parsedAbi, _ := abi.JSON(strings.NewReader(abiString)) + bc := bind.NewBoundContract(common.HexToAddress("0x0"), parsedAbi, nil, nil, nil) + receivedMap := make(map[string]interface{}) + if err := bc.UnpackLogIntoMap(receivedMap, "received", mockLog); err == nil { + t.Error("naming conflict between two events; error expected") } } diff --git a/accounts/abi/bind/topics.go b/accounts/abi/bind/topics.go index 1569fae5495c..9a2012b30b07 100644 --- a/accounts/abi/bind/topics.go +++ b/accounts/abi/bind/topics.go @@ -17,6 +17,7 @@ package bind import ( + "encoding/binary" "errors" "fmt" "math/big" @@ -188,7 +189,7 @@ 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 +// 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) { @@ -202,11 +203,7 @@ func parseTopicsIntoMap(out map[string]interface{}, fields abi.Arguments, topics switch arg.Type.T { case abi.BoolTy: - if topics[0][common.HashLength-1] == 1 { - out[arg.Name] = true - } else { - out[arg.Name] = false - } + 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 @@ -216,19 +213,21 @@ func parseTopicsIntoMap(out map[string]interface{}, fields abi.Arguments, topics out[arg.Name] = addr case abi.HashTy: out[arg.Name] = topics[0] - case abi.BytesTy, abi.FixedBytesTy: + case abi.FixedBytesTy: out[arg.Name] = topics[0][:] - case abi.StringTy: - bytes := topics[0].Bytes() - var trimmedBytes []byte - for i, by := range bytes { - if by != 0 { - trimmedBytes = bytes[i:] - break - } + 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()) } - out[arg.Name] = string(trimmedBytes) - default: + 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:] diff --git a/accounts/abi/unpack.go b/accounts/abi/unpack.go index 8406b09c801d..b2e61d06c416 100644 --- a/accounts/abi/unpack.go +++ b/accounts/abi/unpack.go @@ -269,7 +269,7 @@ func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err totalSize.Add(totalSize, bigOffsetEnd) totalSize.Add(totalSize, lengthBig) 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 {