-
Notifications
You must be signed in to change notification settings - Fork 103
/
listener.go
141 lines (114 loc) · 3.37 KB
/
listener.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/**
* @file
* @copyright defined in go-seele/LICENSE
*/
package listener
import (
"fmt"
"io/ioutil"
"strings"
"github.com/seeleteam/go-seele/accounts/abi"
"github.com/seeleteam/go-seele/common"
"github.com/seeleteam/go-seele/common/errors"
"github.com/seeleteam/go-seele/core/types"
)
var (
// ErrInvalidArguments is returned when NewContractEventABI arguments are invalid.
ErrInvalidArguments = errors.New("the abiPath, eventName and contract address cannot be empty")
)
// ContractEventABI represents contract event parser.
type ContractEventABI struct {
// the map key is topic, the value is event name,
// topic to compare with log's topic,
// event name to label the event how to deal.
topicEventNames map[common.Hash]string
// contract address, to avoid different contracts have the same event name and arguments
contract common.Address
parser abi.ABI
}
// NewContractEventABI returns a ContractEventABI instance.
func NewContractEventABI(abiPath string, contract common.Address, eventNames ...string) (*ContractEventABI, error) {
if len(abiPath) == 0 || len(eventNames) == 0 {
return nil, ErrInvalidArguments
}
if contract.Equal(common.EmptyAddress) {
return nil, ErrInvalidArguments
}
// ensure the contract address is EVM contract
if !contract.IsEVMContract() {
return nil, fmt.Errorf("the address is not EVM contract, %v", contract)
}
file, err := ioutil.ReadFile(abiPath)
if err != nil {
return nil, errors.NewStackedError(err, "failed to read abi file")
}
parser, err := abi.JSON(strings.NewReader(string(file)))
if err != nil {
return nil, errors.NewStackedError(err, "failed to parse abi")
}
c := &ContractEventABI{
contract: contract,
topicEventNames: make(map[common.Hash]string),
}
c.parser = parser
for _, eventName := range eventNames {
event, ok := parser.Events[eventName]
if !ok {
return nil, fmt.Errorf("event name %v not found in ABI file %v", eventName, abiPath)
}
c.topicEventNames[event.Id()] = eventName
}
return c, nil
}
// Event represents a contract event instance from Log.
type Event struct {
TxHash common.Hash
Contract common.Address
EventName string
Topic common.Hash
Arguments []interface{}
}
// GetEvents get events from receipts.
func (c *ContractEventABI) GetEvents(receipts []*types.Receipt) ([]*Event, error) {
var events []*Event
for _, receipt := range receipts {
eventsSlice, err := c.GetEvent(receipt)
if err != nil {
return nil, err
}
events = append(events, eventsSlice...)
}
return events, nil
}
// GetEvent get events from receipt.
func (c *ContractEventABI) GetEvent(receipt *types.Receipt) ([]*Event, error) {
var events []*Event
if receipt.Failed {
return nil, nil
}
for _, log := range receipt.Logs {
if !log.Address.Equal(c.contract) || len(log.Topics) < 1 {
continue
}
eventName, ok := c.topicEventNames[log.Topics[0]]
if !ok {
continue
}
event := &Event{
TxHash: receipt.TxHash,
Contract: c.contract,
EventName: eventName,
Topic: log.Topics[0],
}
var err error
if log.Data != nil {
// unnecessary to check whether parser.Events has the event name, we have check it before
event.Arguments, err = c.parser.Events[eventName].Inputs.UnpackValues(log.Data)
if err != nil {
return nil, fmt.Errorf("event name %v not found in ABI file", eventName)
}
}
events = append(events, event)
}
return events, nil
}