mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-25 22:26:42 +00:00
Added support to ABI to unpack event & using it to retrieve events
This commit is contained in:
parent
2f5413bbce
commit
6fb1523944
2 changed files with 97 additions and 19 deletions
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Reference in a new issue