-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathlog.go
More file actions
146 lines (129 loc) · 5.29 KB
/
log.go
File metadata and controls
146 lines (129 loc) · 5.29 KB
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
142
143
144
145
146
package bytecode
import (
"bytes"
"fmt"
"math/big"
"strings"
abi "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/unpackdev/solgo/utils"
)
// Topic represents a single decoded topic from an Ethereum event log. Topics are attributes
// of an event, such as the method signature and indexed parameters.
type Topic struct {
Name string `json:"name"` // The name of the topic.
Value any `json:"value"` // The value of the topic, decoded into the appropriate Go data type.
}
// Log encapsulates a decoded Ethereum event log. It includes the event's details such as its name,
// signature, the contract that emitted the event, and the decoded data and topics.
type Log struct {
Event *abi.Event `json:"-"` // ABI definition of the log's event.
Address common.Address `json:"address"` // Address of the contract that emitted the event.
Abi string `json:"abi"` // ABI string of the event.
SignatureHex common.Hash `json:"signature_hex"` // Hex-encoded signature of the event.
Signature string `json:"signature"` // Signature of the event.
Type utils.LogEventType `json:"type"` // Type of the event, classified by solgo.
Name string `json:"name"` // Name of the event.
Data map[string]any `json:"data"` // Decoded event data.
Topics []Topic `json:"topics"` // Decoded topics of the event.
}
// DecodeLogFromAbi decodes an Ethereum event log using the provided ABI data. It returns a Log
// instance containing the decoded event name, data, and topics. The function requires the event log
// and its ABI as inputs. It handles errors such as missing topics or failure to parse the ABI.
func DecodeLogFromAbi(log *types.Log, abiData []byte) (*Log, error) {
if log == nil || len(log.Topics) < 1 {
return nil, fmt.Errorf("log is nil or has no topics")
}
logABI, err := abi.JSON(bytes.NewReader(abiData))
if err != nil {
return nil, fmt.Errorf("failed to parse abi: %s", err)
}
event, err := logABI.EventByID(log.Topics[0])
if err != nil {
return nil, fmt.Errorf("failed to get event by id %s: %s", log.Topics[0].Hex(), err)
}
data := make(map[string]any)
if err := event.Inputs.UnpackIntoMap(data, log.Data); err != nil {
return nil, fmt.Errorf("failed to unpack inputs into map: %s", err)
}
decodedTopics := make([]Topic, len(log.Topics))
for i, topic := range log.Topics {
if i == 0 {
continue
}
decodedTopic, err := decodeTopic(topic, event.Inputs[i-1])
if err != nil {
return nil, fmt.Errorf("failed to decode topic: %s", err)
}
decodedTopics[i] = Topic{
Name: event.Inputs[i-1].Name,
Value: decodedTopic,
}
}
eventAbi, err := utils.EventToABI(event)
if err != nil {
return nil, fmt.Errorf("failed to cast event into the abi: %w", err)
}
toReturn := &Log{
Event: event,
Address: log.Address,
Abi: eventAbi,
SignatureHex: log.Topics[0],
Signature: strings.TrimLeft(event.String(), "event "),
Name: event.Name,
Type: utils.LogEventType(strings.ToLower(event.Name)),
Data: data,
Topics: decodedTopics[1:], // Exclude the first topic (event signature)
}
return toReturn, nil
}
// decodeTopic decodes a single topic from an Ethereum event log based on its ABI argument type.
// It supports various data types including addresses, booleans, integers, strings, bytes, and more.
// This function is internal and used within DecodeLogFromAbi to process each topic individually.
func decodeTopic(topic common.Hash, argument abi.Argument) (interface{}, error) {
switch argument.Type.T {
case abi.AddressTy:
return common.BytesToAddress(topic.Bytes()), nil
case abi.BoolTy:
return topic[common.HashLength-1] == 1, nil
case abi.IntTy, abi.UintTy:
size := argument.Type.Size
if size > 256 {
return nil, fmt.Errorf("unsupported integer size: %d", size)
}
integer := new(big.Int).SetBytes(topic[:])
if argument.Type.T == abi.IntTy && size < 256 {
integer = adjustIntSize(integer, size)
}
return integer, nil
case abi.StringTy:
return topic, nil
case abi.BytesTy, abi.FixedBytesTy:
return topic.Bytes(), nil
case abi.SliceTy, abi.ArrayTy:
return nil, fmt.Errorf("array/slice decoding not implemented")
default:
return nil, fmt.Errorf("decoding for type %v not implemented", argument.Type.T)
}
}
// adjustIntSize adjusts the size of an integer to match its ABI-specified size, which is relevant
// for signed integers smaller than 256 bits. This function ensures the integer is correctly
// interpreted according to its defined bit size in the ABI.
func adjustIntSize(integer *big.Int, size int) *big.Int {
if size == 256 || integer.Bit(size-1) == 0 {
return integer
}
return new(big.Int).Sub(integer, new(big.Int).Lsh(big.NewInt(1), uint(size)))
}
// GetTopicByName searches for and returns a Topic by its name from a slice of Topic instances.
// It facilitates accessing specific topics directly by name rather than iterating over the slice.
// If the topic is not found, it returns nil.
func GetTopicByName(name string, topics []Topic) *Topic {
for _, topic := range topics {
if topic.Name == name {
return &topic
}
}
return nil
}