aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--accounts/abi/abi.go34
-rw-r--r--accounts/abi/abi_test.go186
-rw-r--r--accounts/abi/argument.go23
-rw-r--r--accounts/abi/bind/base.go16
-rw-r--r--accounts/abi/bind/base_test.go297
-rw-r--r--accounts/abi/bind/topics.go48
-rw-r--r--accounts/abi/unpack.go2
7 files changed, 596 insertions, 10 deletions
diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go
index ba1774c64..c5fbc1e77 100644
--- a/accounts/abi/abi.go
+++ b/accounts/abi/abi.go
@@ -72,19 +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")
+}
+
+// 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")
}
diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go
index b9444f9f0..42b60a639 100644
--- a/accounts/abi/abi_test.go
+++ b/accounts/abi/abi_test.go
@@ -694,6 +694,192 @@ func TestUnpackEvent(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 {
+ 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": []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")
+ }
+
+ receivedAddrMap := map[string]interface{}{}
+ if err = abi.UnpackIntoMap(receivedAddrMap, "receivedAddr", data); err != nil {
+ t.Error(err)
+ }
+ if len(receivedAddrMap) != 1 {
+ t.Error("unpacked `receivedAddr` map expected to have length 1")
+ }
+ if receivedAddrMap["sender"] != expectedReceivedMap["sender"] {
+ 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")
+ }
+}
+
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 d0a6b035c..501cb1621 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])
}
+// 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.
// 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
}
+// 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
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 c37bdf11d..f70f911d3 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/base_test.go b/accounts/abi/bind/base_test.go
index 02caf457a..f65c9e9b4 100644
--- a/accounts/abi/bind/base_test.go
+++ b/accounts/abi/bind/base_test.go
@@ -17,14 +17,20 @@
package bind_test
import (
+ "bytes"
"context"
"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"
+ "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 {
@@ -41,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{}
@@ -78,3 +83,291 @@ func TestPassingBlockNumber(t *testing.T) {
t.Fatalf("CodeAt() was passed a block number when it should not have been")
}
}
+
+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("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"}]`
+ 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": 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["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).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")
+ }
+}
+
+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 af9c272cd..c7657b4a4 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"
@@ -191,3 +192,50 @@ func parseTopics(out interface{}, fields abi.Arguments, topics []common.Hash) er
}
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
+}
diff --git a/accounts/abi/unpack.go b/accounts/abi/unpack.go
index 8406b09c8..b2e61d06c 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 {