-
Notifications
You must be signed in to change notification settings - Fork 2.2k
/
types.go
450 lines (346 loc) · 11.7 KB
/
types.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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
package relayer
import (
"context"
"errors"
"fmt"
"math/big"
"time"
"unicode/utf8"
"log/slog"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
)
var (
ZeroHash = common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000000")
ZeroAddress = common.HexToAddress("0x0000000000000000000000000000000000000000")
)
type confirmer interface {
TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
BlockNumber(ctx context.Context) (uint64, error)
}
// WaitReceipt keeps waiting until the given transaction has an execution
// receipt to know whether it was reverted or not.
func WaitReceipt(ctx context.Context, confirmer confirmer, txHash common.Hash) (*types.Receipt, error) {
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-ticker.C:
receipt, err := confirmer.TransactionReceipt(ctx, txHash)
if err != nil {
continue
}
if receipt.Status != types.ReceiptStatusSuccessful {
return nil, fmt.Errorf("transaction reverted, hash: %s", txHash)
}
return receipt, nil
}
}
}
var (
errStillWaiting = errors.New("still waiting")
)
// WaitConfirmations won't return before N blocks confirmations have been seen
// on destination chain, or context is cancelled.
func WaitConfirmations(ctx context.Context, confirmer confirmer, confirmations uint64, txHash common.Hash) error {
checkConfs := func() error {
receipt, err := confirmer.TransactionReceipt(ctx, txHash)
if err != nil {
return err
}
latest, err := confirmer.BlockNumber(ctx)
if err != nil {
return err
}
want := receipt.BlockNumber.Uint64() + confirmations
if latest < want {
slog.Info("waiting for confirmations", "latestBlockNum", latest, "wantBlockNum", want)
return errStillWaiting
}
return nil
}
if err := checkConfs(); err != nil && err != ethereum.NotFound && err != errStillWaiting {
slog.Error("encountered error getting receipt", "txHash", txHash.Hex(), "error", err)
return err
}
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return ctx.Err()
case <-ticker.C:
if err := checkConfs(); err != nil {
if err == ethereum.NotFound || err == errStillWaiting {
continue
}
slog.Error("encountered error getting receipt", "txHash", txHash.Hex(), "error", err)
return err
}
return nil
}
}
}
// splitByteArray splits a byte array into chunks of chunkSize.
// It returns a slice of byte slices.
func splitByteArray(data []byte, chunkSize int) [][]byte {
var chunks [][]byte
for i := 0; i < len(data); i += chunkSize {
end := i + chunkSize
// Ensure we don't go past the end of the slice
if end > len(data) {
end = len(data)
}
chunks = append(chunks, data[i:end])
}
return chunks
}
func decodeDataAsERC20(decodedData []byte) (CanonicalToken, *big.Int, error) {
var token CanonicalERC20
canonicalTokenDataStartingindex := int64(2)
chunks := splitByteArray(decodedData, 32)
if len(chunks) < 4 {
return token, big.NewInt(0), errors.New("data too short")
}
offset, ok := new(big.Int).SetString(common.Bytes2Hex((chunks[canonicalTokenDataStartingindex])), 16)
if !ok {
return token, big.NewInt(0), errors.New("data for BigInt is invalid")
}
// Calculate the starting index for canonicalTokenData
startIndex := offset.Int64() + canonicalTokenDataStartingindex*32
// Boundary check
if startIndex >= int64(len(decodedData)) || startIndex < 0 {
slog.Warn("startIndex greater than decodedData length",
"startIndex", startIndex,
"lenDecodedData", int64(len(decodedData)),
)
return token, big.NewInt(0), errors.New("calculated index is out of bounds")
}
canonicalTokenData := decodedData[startIndex:]
types := []string{"uint64", "address", "uint8", "string", "string"}
values, err := decodeABI(types, canonicalTokenData)
if err != nil && len(values) != 5 {
return token, big.NewInt(0), err
}
// Type assertions and validations
chainId, ok := values[0].(uint64)
if !ok {
return token, big.NewInt(0), errors.New("invalid chainId type")
}
addr, ok := values[1].(common.Address)
if !ok {
return token, big.NewInt(0), errors.New("invalid address type")
}
decimals, ok := values[2].(uint8)
if !ok {
return token, big.NewInt(0), errors.New("invalid decimals type")
}
symbol, ok := values[3].(string)
if !ok || !utf8.ValidString(symbol) {
return token, big.NewInt(0), errors.New("invalid symbol string")
}
name, ok := values[4].(string)
if !ok || !utf8.ValidString(name) {
return token, big.NewInt(0), errors.New("invalid name string")
}
token.ChainId = chainId
token.Addr = addr
token.Decimals = decimals
token.Symbol = symbol
token.Name = name
amount, ok := new(big.Int).SetString(common.Bytes2Hex((chunks[canonicalTokenDataStartingindex+3])), 16)
if !ok {
return token, big.NewInt(0), errors.New("data for BigInt is invalid")
}
return token, amount, nil
}
func decodeDataAsNFT(decodedData []byte) (EventType, CanonicalToken, *big.Int, error) {
var token CanonicalNFT
canonicalTokenDataStartingindex := int64(2)
chunks := splitByteArray(decodedData, 32)
offset, ok := new(big.Int).SetString(common.Bytes2Hex((chunks[canonicalTokenDataStartingindex])), 16)
if !ok || offset.Int64()%32 != 0 {
return EventTypeSendETH, token, big.NewInt(0), errors.New("data for BigInt is invalid")
}
// Calculate the starting index for canonicalTokenData
startIndex := offset.Int64() + canonicalTokenDataStartingindex*32
// Boundary check
if startIndex >= int64(len(decodedData)) || startIndex < 0 {
slog.Warn("startIndex greater than decodedData length",
"startIndex", startIndex,
"lenDecodedData", int64(len(decodedData)),
)
return EventTypeSendETH, token, big.NewInt(0), errors.New("calculated index is out of bounds")
}
canonicalTokenData := decodedData[startIndex:]
types := []string{"uint64", "address", "string", "string"}
values, err := decodeABI(types, canonicalTokenData)
if err != nil && len(values) != 4 {
return EventTypeSendETH, token, big.NewInt(0), err
}
// Type assertions and validations
chainId, ok := values[0].(uint64)
if !ok {
return EventTypeSendETH, token, big.NewInt(0), errors.New("invalid chainId type")
}
addr, ok := values[1].(common.Address)
if !ok {
return EventTypeSendETH, token, big.NewInt(0), errors.New("invalid address type")
}
symbol, ok := values[2].(string)
if !ok || !utf8.ValidString(symbol) {
return EventTypeSendETH, token, big.NewInt(0), errors.New("invalid symbol string")
}
name, ok := values[3].(string)
if !ok || !utf8.ValidString(name) {
return EventTypeSendETH, token, big.NewInt(0), errors.New("invalid name string")
}
token.ChainId = chainId
token.Addr = addr
token.Symbol = symbol
token.Name = name
if offset.Int64() == 128 {
amount := big.NewInt(1)
return EventTypeSendERC721, token, amount, nil
} else if offset.Int64() == 160 {
offset, ok := new(big.Int).SetString(common.Bytes2Hex((chunks[canonicalTokenDataStartingindex+4])), 16)
if !ok || offset.Int64()%32 != 0 {
return EventTypeSendETH, token, big.NewInt(0), errors.New("data for BigInt is invalid")
}
indexOffset := canonicalTokenDataStartingindex + int64(offset.Int64()/32)
length, ok := new(big.Int).SetString(common.Bytes2Hex((chunks[indexOffset])), 16)
if !ok {
return EventTypeSendETH, token, big.NewInt(0), errors.New("data for BigInt is invalid")
}
amount := big.NewInt(0)
for i := int64(0); i < length.Int64(); i++ {
amountsData := decodedData[(indexOffset+i+1)*32 : (indexOffset+i+2)*32]
types := []string{"uint256"}
values, err = decodeABI(types, amountsData)
if err != nil && len(values) != 1 {
return EventTypeSendETH, token, big.NewInt(0), err
}
amount = amount.Add(amount, values[0].(*big.Int))
}
return EventTypeSendERC1155, token, amount, nil
}
return EventTypeSendETH, token, big.NewInt(0), nil
}
func decodeABI(types []string, data []byte) ([]interface{}, error) {
arguments := make(abi.Arguments, len(types))
for i, t := range types {
arguments[i].Type, _ = abi.NewType(t, "", nil)
}
values, err := arguments.UnpackValues(data)
if err != nil {
return nil, err
}
return values, nil
}
// DecodeMessageData tries to tell if it's an ETH, ERC20, ERC721, or ERC1155 bridge,
// which lets the processor look up whether the contract has already been deployed or not,
// to help better estimate gas needed for processing the message.
func DecodeMessageData(eventData []byte, value *big.Int) (EventType, CanonicalToken, *big.Int, error) {
// Default eventType is ETH
eventType := EventTypeSendETH
var canonicalToken CanonicalToken
var amount *big.Int = value
onMessageInvocationFunctionSig := "7f07c947"
// Check if eventData is valid
if len(eventData) > 3 &&
common.Bytes2Hex(eventData[:4]) == onMessageInvocationFunctionSig {
// Try to decode data as ERC20
canonicalToken, amount, err := decodeDataAsERC20(eventData[4:])
if err == nil {
return EventTypeSendERC20, canonicalToken, amount, nil
}
// Try to decode data as NFT
eventType, canonicalToken, amount, err = decodeDataAsNFT(eventData[4:])
if err == nil {
return eventType, canonicalToken, amount, nil
}
}
return eventType, canonicalToken, amount, nil
}
type CanonicalToken interface {
ChainID() uint64
Address() common.Address
ContractName() string
TokenDecimals() uint8
ContractSymbol() string
}
type CanonicalERC20 struct {
// nolint
ChainId uint64 `json:"chainId"`
Addr common.Address `json:"addr"`
Decimals uint8 `json:"decimals"`
Symbol string `json:"symbol"`
Name string `json:"name"`
}
func (c CanonicalERC20) ChainID() uint64 {
return c.ChainId
}
func (c CanonicalERC20) Address() common.Address {
return c.Addr
}
func (c CanonicalERC20) ContractName() string {
return c.Name
}
func (c CanonicalERC20) ContractSymbol() string {
return c.Symbol
}
func (c CanonicalERC20) TokenDecimals() uint8 {
return c.Decimals
}
type CanonicalNFT struct {
// nolint
ChainId uint64 `json:"chainId"`
Addr common.Address `json:"addr"`
Symbol string `json:"symbol"`
Name string `json:"name"`
}
func (c CanonicalNFT) ChainID() uint64 {
return c.ChainId
}
func (c CanonicalNFT) Address() common.Address {
return c.Addr
}
func (c CanonicalNFT) ContractName() string {
return c.Name
}
func (c CanonicalNFT) TokenDecimals() uint8 {
return 0
}
func (c CanonicalNFT) ContractSymbol() string {
return c.Symbol
}
// DecodeRevertReason decodes a hex-encoded revert reason from an Ethereum transaction.
func DecodeRevertReason(hexStr string) (string, error) {
// Decode the hex string to bytes
data, err := hexutil.Decode(hexStr)
if err != nil {
return "", err
}
// Ensure the data is long enough to contain a valid revert reason
if len(data) < 68 {
return "", errors.New("data too short to contain a valid revert reason")
}
// The revert reason is encoded in the data returned by a failed transaction call
// It starts with the error signature 0x08c379a0 (method ID), followed by the offset
// of the string data, the length of the string, and finally the string itself.
// Skip the first 4 bytes (method ID) and the next 32 bytes (offset)
// Then read the length of the string (next 32 bytes)
strLen := new(big.Int).SetBytes(data[36:68]).Uint64()
// Ensure the data contains the full revert string
if uint64(len(data)) < 68+strLen {
return "", errors.New("data too short to contain the full revert reason")
}
// Extract the revert reason string
revertReason := string(data[68 : 68+strLen])
return revertReason, nil
}