From 6fb1523944824d15b2f88f113747af22174c9045 Mon Sep 17 00:00:00 2001 From: Max Wolter Date: Mon, 15 Aug 2016 19:17:38 +0200 Subject: [PATCH] Added support to ABI to unpack event & using it to retrieve events --- accounts/abi/abi.go | 45 +++++++++++++++---------- accounts/abi/bind/base.go | 71 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 97 insertions(+), 19 deletions(-) diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 1b07b2f68e..0e54c48178 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -232,8 +232,6 @@ var ( // Unpack output in v according to the abi specification func (abi ABI) Unpack(v interface{}, name string, output []byte) error { - var method = abi.Methods[name] - if len(output) == 0 { return fmt.Errorf("abi: unmarshalling empty output") } @@ -244,18 +242,31 @@ func (abi ABI) Unpack(v interface{}, name string, output []byte) error { return fmt.Errorf("abi: Unpack(non-pointer %T)", v) } + // get information about the method or event + var args []Argument + method, ok := abi.Methods[name] + if ok { + args = method.Outputs + } else { + event, ok := abi.Events[name] + if !ok { + return fmt.Errorf("abi: method or event not found (%v)", name) + } + args = event.Inputs + } + var ( value = valueOf.Elem() typ = value.Type() ) - if len(method.Outputs) > 1 { + if len(args) > 1 { switch value.Kind() { // struct will match named return values to the struct's field // names case reflect.Struct: - for i := 0; i < len(method.Outputs); i++ { - marshalledValue, err := toGoType(i, method.Outputs[i], output) + for i := 0; i < len(args); i++ { + marshalledValue, err := toGoType(i, args[i], output) if err != nil { return err } @@ -264,8 +275,8 @@ func (abi ABI) Unpack(v interface{}, name string, output []byte) error { for j := 0; j < typ.NumField(); j++ { field := typ.Field(j) // TODO read tags: `abi:"fieldName"` - if field.Name == strings.ToUpper(method.Outputs[i].Name[:1])+method.Outputs[i].Name[1:] { - if err := set(value.Field(j), reflectValue, method.Outputs[i]); err != nil { + if field.Name == strings.ToUpper(args[i].Name[:1])+method.Outputs[i].Name[1:] { + if err := set(value.Field(j), reflectValue, args[i]); err != nil { return err } } @@ -278,17 +289,17 @@ func (abi ABI) Unpack(v interface{}, name string, output []byte) error { // if the slice already contains values, set those instead of the interface slice itself. if value.Len() > 0 { - if len(method.Outputs) > value.Len() { - return fmt.Errorf("abi: cannot marshal in to slices of unequal size (require: %v, got: %v)", len(method.Outputs), value.Len()) + if len(args) > value.Len() { + return fmt.Errorf("abi: cannot marshal in to slices of unequal size (require: %v, got: %v)", len(args), value.Len()) } - for i := 0; i < len(method.Outputs); i++ { - marshalledValue, err := toGoType(i, method.Outputs[i], output) + for i := 0; i < len(args); i++ { + marshalledValue, err := toGoType(i, args[i], output) if err != nil { return err } reflectValue := reflect.ValueOf(marshalledValue) - if err := set(value.Index(i).Elem(), reflectValue, method.Outputs[i]); err != nil { + if err := set(value.Index(i).Elem(), reflectValue, args[i]); err != nil { return err } } @@ -297,9 +308,9 @@ func (abi ABI) Unpack(v interface{}, name string, output []byte) error { // create a new slice and start appending the unmarshalled // values to the new interface slice. - z := reflect.MakeSlice(typ, 0, len(method.Outputs)) - for i := 0; i < len(method.Outputs); i++ { - marshalledValue, err := toGoType(i, method.Outputs[i], output) + z := reflect.MakeSlice(typ, 0, len(args)) + for i := 0; i < len(args); i++ { + marshalledValue, err := toGoType(i, args[i], output) if err != nil { return err } @@ -311,11 +322,11 @@ func (abi ABI) Unpack(v interface{}, name string, output []byte) error { } } else { - marshalledValue, err := toGoType(0, method.Outputs[0], output) + marshalledValue, err := toGoType(0, args[0], output) if err != nil { return err } - if err := set(value, reflect.ValueOf(marshalledValue), method.Outputs[0]); err != nil { + if err := set(value, reflect.ValueOf(marshalledValue), args[0]); err != nil { return err } } diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index 03fe863372..3d33283288 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" "math/big" + "reflect" "sync/atomic" "github.com/ethereum/go-ethereum" @@ -65,6 +66,15 @@ type SubscribeOpts struct { Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) } +// EventOpts is a collection of options to fine tune the retrieval of events that +// happened on a contract. +type EventOpts struct { + FromBlock *big.Int + ToBlock *big.Int + + Context context.Context +} + // BoundContract is the base wrapper object that reflects a contract on the // Ethereum network. It contains a collection of methods that are used by the // higher level contract bindings to operate. @@ -247,11 +257,68 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i return signedTx, nil } +// Events will return a list of events for this contract for the given topics and +// the given start & end blocks. +func (c *BoundContract) Events(opts *EventOpts, name string, output interface{}, topics ...[]common.Hash) error { + + // get the event so we can encode the name into the first topic + event, ok := c.abi.Events[name] + if !ok { + return fmt.Errorf("unknown event name: %v", name) + } + names := []common.Hash{event.Id()} + topics = append([][]common.Hash{names}, topics...) + + // check that output is a pointer + ptr := reflect.ValueOf(output) + if ptr.Kind() != reflect.Ptr { + return fmt.Errorf("need pointer to slice as output, have %T", output) + } + + // check that it points to a slice + slice := ptr.Elem() + if slice.Kind() != reflect.Slice { + return fmt.Errorf("need pointer to slice as output, have %T", output) + } + + // create the filter query and retrieve the events through the backend + filterQuery := ethereum.FilterQuery{ + FromBlock: opts.FromBlock, + ToBlock: opts.ToBlock, + Addresses: []common.Address{c.address}, + Topics: topics, + } + logs, err := c.backend.FilterLogs(opts.Context, filterQuery) + if err != nil { + return fmt.Errorf("could not retrieve events (%v)", err) + } + + // for each log entry, create an event, unpack the data and append it + item := slice.Elem() + events := reflect.MakeSlice(slice.Type(), 0, len(logs)) + for _, log := range logs { + event := reflect.New(item.Type()) + err = c.abi.Unpack(item.Interface(), name, log.Data) + if err != nil { + return fmt.Errorf("could not unpack event data (%v)", err) + } + if len(log.Topics) > 1 { + // TODO: extract indexed parameters from topics 1-3 + } + events = reflect.Append(events, event) + } + + // set the output slice to our slice of events + slice.Set(events) + return nil +} + // Subscribe subscribes to the contract for the given topics. Subscription events // will be submitted to the provided channel. Upon cancelation of the subscription, // the channel will be closed. A channel should only be used for ones subscription. -func (c *BoundContract) Subscribe(opts *SubscribeOpts, channel chan<- vm.Log, topics ...[]common.Hash) error { - // Check if we already have a subscription on this channel +func (c *BoundContract) Subscribe(opts *SubscribeOpts, name string, channel chan<- vm.Log, topics ...[]common.Hash) error { + + // check if we already have a subscription on this channel _, ok := c.subscriptions[channel] if ok { return fmt.Errorf("cannot reuse channel for multiple subscriptions")