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)
This commit is contained in:
parent
86e77900c5
commit
cd79bc61a9
@ -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")
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
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])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 {
|
|||||||
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)
|
|||||||
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 {
|
||||||
|
@ -17,14 +17,20 @@
|
|||||||
package bind_test
|
package bind_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"strings"
|
||||||
"testing"
|
"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"
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"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 {
|
type mockCaller struct {
|
||||||
@ -41,7 +47,6 @@ func (mc *mockCaller) CallContract(ctx context.Context, call ethereum.CallMsg, b
|
|||||||
mc.callContractBlockNumber = blockNumber
|
mc.callContractBlockNumber = blockNumber
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPassingBlockNumber(t *testing.T) {
|
func TestPassingBlockNumber(t *testing.T) {
|
||||||
|
|
||||||
mc := &mockCaller{}
|
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")
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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
|
|||||||
}
|
}
|
||||||
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
|
|||||||
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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user