This repository has been archived by the owner on Jan 9, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3
/
tx.go
315 lines (279 loc) · 8.3 KB
/
tx.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
package evm
import (
"encoding/hex"
"errors"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
xc "github.com/jumpcrypto/crosschain"
"github.com/jumpcrypto/crosschain/chain/evm/erc20"
)
// Tx for EVM
type Tx struct {
EthTx *types.Transaction
Signer types.Signer
// parsed info
}
var _ xc.Tx = &Tx{}
type parsedTxInfo struct {
Sources []*xc.TxInfoEndpoint
Destinations []*xc.TxInfoEndpoint
}
// Hash returns the tx hash or id
func (tx Tx) Hash() xc.TxHash {
if tx.EthTx != nil {
return xc.TxHash(tx.EthTx.Hash().Hex())
}
return xc.TxHash("")
}
// Sighashes returns the tx payload to sign, aka sighash
func (tx Tx) Sighashes() ([]xc.TxDataToSign, error) {
if tx.EthTx == nil {
return []xc.TxDataToSign{}, errors.New("transaction not initialized")
}
sighash := tx.Signer.Hash(tx.EthTx).Bytes()
return []xc.TxDataToSign{sighash}, nil
}
// AddSignatures adds a signature to Tx
func (tx *Tx) AddSignatures(signatures ...xc.TxSignature) error {
if tx.EthTx == nil {
return errors.New("transaction not initialized")
}
signedTx, err := tx.EthTx.WithSignature(tx.Signer, signatures[0])
if err != nil {
return err
}
tx.EthTx = signedTx
return nil
}
// Serialize returns the serialized tx
func (tx Tx) Serialize() ([]byte, error) {
if tx.EthTx == nil {
return []byte{}, errors.New("transaction not initialized")
}
return tx.EthTx.MarshalBinary()
}
// ParseTransfer parses a tx and extracts higher-level transfer information
func (tx *Tx) ParseTransfer(receipt *types.Receipt, nativeAsset xc.NativeAsset) parsedTxInfo {
// 1. first try parsing as an abi we natively support.
if tx.IsContract() {
info, err := tx.ParseMultisendTransferTx()
if err != nil {
// ignore
} else {
return info
}
info, err = tx.ParseERC20TransferTx()
if err != nil {
// ignore
} else {
return info
}
}
// 2. try parsing using the logs
infoLogs := tx.parseReceipt(receipt, nativeAsset)
if len(infoLogs.Destinations) > 0 {
return infoLogs
}
// 3. use to/from/amount from the tf
return parsedTxInfo{
Sources: []*xc.TxInfoEndpoint{{
Address: tx.From(),
}},
Destinations: []*xc.TxInfoEndpoint{{
Address: tx.To(),
}},
}
}
func (tx *Tx) parseReceipt(receipt *types.Receipt, nativeAsset xc.NativeAsset) parsedTxInfo {
loggedSources := []*xc.TxInfoEndpoint{}
loggedDestinations := []*xc.TxInfoEndpoint{}
for _, log := range receipt.Logs {
event, _ := ERC20.EventByID(log.Topics[0])
if event != nil && event.RawName == "Transfer" {
erc20, _ := erc20.NewErc20(receipt.ContractAddress, nil)
tf, err := erc20.ParseTransfer(*log)
if err != nil {
fmt.Println("could not parse log: ", log.Index)
continue
}
loggedDestinations = append(loggedDestinations, &xc.TxInfoEndpoint{
Address: xc.Address(tf.To.String()),
ContractAddress: xc.ContractAddress(log.Address.String()),
Amount: xc.AmountBlockchain(*tf.Tokens),
NativeAsset: nativeAsset,
})
loggedSources = append(loggedSources, &xc.TxInfoEndpoint{
Address: xc.Address(tf.From.String()),
ContractAddress: xc.ContractAddress(log.Address.String()),
Amount: xc.AmountBlockchain(*tf.Tokens),
NativeAsset: nativeAsset,
})
}
}
return parsedTxInfo{
Sources: loggedSources,
Destinations: loggedDestinations,
}
}
// IsContract returns whether a tx is a contract or native transfer
func (tx Tx) IsContract() bool {
if tx.EthTx == nil {
return false
}
payload := tx.EthTx.Data()
return len(payload) > 0
}
// From is the sender of a transfer
func (tx Tx) From() xc.Address {
if tx.EthTx == nil || tx.Signer == nil {
return xc.Address("")
}
from, err := types.Sender(tx.Signer, tx.EthTx)
if err != nil {
return xc.Address("")
}
return xc.Address(from.String())
}
// To is the account receiving a transfer
func (tx Tx) To() xc.Address {
if tx.EthTx == nil {
return xc.Address("")
}
if tx.IsContract() {
info, err := tx.ParseERC20TransferTx()
if err != nil {
// ignore
} else {
// single token transfers have a single destination
// we will opt to use instead.
return info.Destinations[0].Address
}
}
return xc.Address(tx.EthTx.To().String())
}
// Amount returns the tx amount
func (tx Tx) Amount() xc.AmountBlockchain {
if tx.EthTx == nil {
return xc.NewAmountBlockchainFromUint64(0)
}
info, err := tx.ParseERC20TransferTx()
if err != nil {
// ignore
} else {
// if this is a erc20 transfer, we use it's amount
return info.Destinations[0].Amount
}
return xc.AmountBlockchain(*tx.EthTx.Value())
}
// ContractAddress returns the contract address for a token transfer
func (tx Tx) ContractAddress() xc.ContractAddress {
if tx.IsContract() {
return xc.ContractAddress(tx.EthTx.To().String())
}
return xc.ContractAddress("")
}
// Fee returns the fee associated to the tx
func (tx Tx) Fee(baseFeeUint uint64, gasUsedUint uint64) xc.AmountBlockchain {
// from Etherscan: (BaseFee + MaxPriority)*GasUsed
maxPriority := xc.AmountBlockchain(*tx.EthTx.GasTipCap())
gasUsed := xc.NewAmountBlockchainFromUint64(gasUsedUint)
baseFee := xc.NewAmountBlockchainFromUint64(baseFeeUint)
baseFeeAndPriority := baseFee.Add(&maxPriority)
fee1 := gasUsed.Mul(&baseFeeAndPriority)
// old gas price * gas used
gasPrice := xc.AmountBlockchain(*tx.EthTx.GasPrice())
fee2 := gasPrice.Mul(&gasUsed)
if fee1.Cmp(&fee2) < 0 {
return fee1
}
return fee2
}
// ParseERC20TransferTx parses the tx payload as ERC20 transfer
func (tx Tx) ParseERC20TransferTx() (parsedTxInfo, error) {
payload := tx.EthTx.Data()
if len(payload) != 4+32*2 || hex.EncodeToString(payload[:4]) != "a9059cbb" {
return parsedTxInfo{}, errors.New("payload is not ERC20.transfer(address,uint256)")
}
var buf1 [20]byte
copy(buf1[:], payload[4+12:4+32])
to := xc.Address("0x" + common.Address(buf1).String())
var buf2 [32]byte
copy(buf2[:], payload[4+32:4+2*32])
amount := new(big.Int).SetBytes(buf2[:])
return parsedTxInfo{
// the from should be the tx sender
Sources: []*xc.TxInfoEndpoint{{
Address: tx.From(),
Amount: xc.AmountBlockchain(*amount),
}},
// destination
Destinations: []*xc.TxInfoEndpoint{{
Address: to,
Amount: xc.AmountBlockchain(*amount),
}},
}, nil
}
// ParseMultisendTransferTx parses the tx payload as multi-send transfer
func (tx Tx) ParseMultisendTransferTx() (parsedTxInfo, error) {
res := parsedTxInfo{}
payload := tx.EthTx.Data()
abiSigETH := "1a1da075"
abiSigERC20 := "ca350aa6"
if len(payload) < 4 {
return res, errors.New("evm payload is miscellaneous")
}
abiSig := hex.EncodeToString(payload[:4])
if abiSig != abiSigETH && abiSig != abiSigERC20 {
// log.Printf("invalid abi=%s", abiSig)
return res, errors.New("payload is not multisend (1a1da075 or ca350aa6)")
}
offset := 4 + 32*2 // ignore first 2 params
// read array len
var buf20 [20]byte
var buf [32]byte
copy(buf[:], payload[offset:offset+32])
offset += 32
arrayLen := int(new(big.Int).SetBytes(buf[:]).Int64())
numParams := 2
if abiSig == abiSigERC20 {
numParams = 3
}
if len(payload) != offset+numParams*32*arrayLen {
// log.Printf("invalid payload len=%d / %d", len(buf), offset+numParams*32*arrayLen)
return res, errors.New("payload is not multisend (len)")
}
for i := 0; i < arrayLen; i++ {
res.Destinations = append(res.Destinations, &xc.TxInfoEndpoint{})
if abiSig == abiSigETH {
res.Destinations[i].Asset = "ETH"
// to
copy(buf20[:], payload[offset+12:offset+32])
res.Destinations[i].Address = xc.Address("0x" + common.Address(buf20).String())
offset += 32
// amount
copy(buf[:], payload[offset:offset+32])
res.Destinations[i].Amount = xc.AmountBlockchain(*new(big.Int).SetBytes(buf[:]))
offset += 32
} else if abiSig == abiSigERC20 {
// asset
copy(buf20[:], payload[offset+12:offset+32])
res.Destinations[i].ContractAddress = xc.ContractAddress("0x" + common.Address(buf20).String())
offset += 32
// to
copy(buf20[:], payload[offset+12:offset+32])
res.Destinations[i].Address = xc.Address("0x" + common.Address(buf20).String())
offset += 32
// amount
copy(buf[:], payload[offset:offset+32])
res.Destinations[i].Amount = xc.AmountBlockchain(*new(big.Int).SetBytes(buf[:]))
offset += 32
}
}
// a single source, the contract
res.Sources = append(res.Sources, &xc.TxInfoEndpoint{
Address: xc.Address(tx.ContractAddress()),
})
return res, nil
}