This repository has been archived by the owner on Feb 16, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 2
/
tx.go
272 lines (226 loc) · 7.15 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
package eth
import (
"encoding/hex"
"fmt"
"math/big"
ethCommon "github.com/ethereum/go-ethereum/common"
"github.com/magicpool-co/pool/pkg/common"
"github.com/magicpool-co/pool/pkg/crypto/tx/ethtx"
"github.com/magicpool-co/pool/types"
)
const (
baseFeeChangeDenominator = 8 // Bounds the amount the base fee can change between blocks.
elasticityMultiplier = 2 // Bounds the maximum gas limit an EIP-1559 block may have.
initialBaseFee = 1000000000 // Initial base fee for EIP-1559 blocks.
)
// BigMax returns the larger of x or y.
func bigMax(x, y *big.Int) *big.Int {
if x.Cmp(y) < 0 {
return y
}
return x
}
func calcNextBaseFee(height, gasLimit, gasUsed uint64, currentBaseFee *big.Int) *big.Int {
if height < 10_499_401 {
return new(big.Int).SetUint64(initialBaseFee)
}
var parentGasTarget = gasLimit / elasticityMultiplier
var parentGasTargetBig = new(big.Int).SetUint64(parentGasTarget)
var baseFeeChangeDenominator = new(big.Int).SetUint64(baseFeeChangeDenominator)
// If the parent gasUsed is the same as the target, the baseFee remains unchanged.
if gasUsed == parentGasTarget {
return new(big.Int).Set(currentBaseFee)
} else if gasUsed > parentGasTarget {
// If the parent block used more gas than its target, the baseFee should increase.
gasUsedDelta := new(big.Int).SetUint64(gasUsed - parentGasTarget)
x := new(big.Int).Mul(currentBaseFee, gasUsedDelta)
y := x.Div(x, parentGasTargetBig)
baseFeeDelta := bigMax(x.Div(y, baseFeeChangeDenominator), common.Big1)
return x.Add(currentBaseFee, baseFeeDelta)
} else {
// Otherwise if the parent block used less gas than its target, the baseFee should decrease.
gasUsedDelta := new(big.Int).SetUint64(parentGasTarget - gasUsed)
x := new(big.Int).Mul(currentBaseFee, gasUsedDelta)
y := x.Div(x, parentGasTargetBig)
baseFeeDelta := x.Div(y, baseFeeChangeDenominator)
return bigMax(x.Sub(currentBaseFee, baseFeeDelta), common.Big0)
}
}
func (node Node) getBaseFee() (*big.Int, error) {
height, err := node.getBlockNumber()
if err != nil {
return nil, err
}
block, err := node.getBlockByNumber(height)
if err != nil {
return nil, err
}
gasLimit, err := common.HexToUint64(block.GasLimit)
if err != nil {
return nil, err
}
gasUsed, err := common.HexToUint64(block.GasUsed)
if err != nil {
return nil, err
}
currentBaseFee, err := common.HexToBig(block.BaseFee)
if err != nil {
return nil, err
}
nextBaseFee := calcNextBaseFee(height, gasLimit, gasUsed, currentBaseFee)
return nextBaseFee, nil
}
func (node Node) GetTxExplorerURL(txid string) string {
return "https://etherscan.io/tx/" + txid
}
func (node Node) GetAddressExplorerURL(address string) string {
return "https://etherscan.io/address/" + address
}
func (node Node) GetBalance() (*big.Int, error) {
if node.erc20 != nil {
data := ethtx.GenerateContractData("balanceOf(address)", ethCommon.HexToAddress(node.address).Bytes())
params := []interface{}{
map[string]interface{}{"to": node.erc20.Address, "data": "0x" + hex.EncodeToString(data)},
"latest",
}
return node.sendCall(params)
}
return node.getBalance(node.address)
}
func (node Node) GetTx(txid string) (*types.TxResponse, error) {
tx, err := node.getTransactionByHash(txid)
if err != nil || tx == nil || len(tx.BlockNumber) <= 2 {
return nil, err
}
txReceipt, err := node.getTransactionReceipt(txid)
if err != nil {
return nil, err
}
txType, err := common.HexToUint64(tx.Type)
if err != nil {
return nil, err
}
height, err := common.HexToUint64(tx.BlockNumber)
if err != nil {
return nil, err
}
value, err := common.HexToBig(tx.Value)
if err != nil {
return nil, err
}
gasUsed, err := common.HexToBig(txReceipt.GasUsed)
if err != nil {
return nil, err
}
gasTotal, err := common.HexToBig(tx.Gas)
if err != nil {
return nil, err
}
gasPrice, err := common.HexToBig(tx.GasPrice)
if err != nil {
return nil, err
}
confirmed := txReceipt.Status == "0x1"
gasLeftover := new(big.Int).Sub(gasTotal, gasUsed)
fees := new(big.Int).Mul(gasUsed, gasPrice)
feeBalance := new(big.Int).Mul(gasLeftover, gasPrice)
// handle type 2 (EIP1559) transaction fees
if txType == 2 {
block, err := node.getBlockByNumber(height)
if err != nil {
return nil, err
}
baseFeePerGas, err := common.HexToBig(block.BaseFee)
if err != nil {
return nil, err
}
maxFeePerGas, err := common.HexToBig(tx.MaxFeePerGas)
if err != nil {
return nil, err
}
maxPriorityFeePerGas, err := common.HexToBig(tx.MaxPriorityFeePerGas)
if err != nil {
return nil, err
}
// maxPriorityFeePerGas is included in maxFeePerGas and will always be spent
// unless baseFeePerGas is greater than maxFeePerGas (which would be unlikely)
feeSavings := new(big.Int).Sub(maxFeePerGas, baseFeePerGas)
feeSavings.Sub(feeSavings, maxPriorityFeePerGas)
if feeSavings.Cmp(common.Big0) > 0 {
feeSavings.Mul(feeSavings, gasUsed)
// return nil, fmt.Errorf("invalid fee balance calc: base %s, max %s, priority %s",
// baseFeePerGas, maxFeePerGas, maxPriorityFeePerGas)
} else {
feeSavings = new(big.Int)
}
gasSavings := new(big.Int).Mul(maxFeePerGas, gasLeftover)
feeBalance = new(big.Int).Add(feeSavings, gasSavings)
}
res := &types.TxResponse{
Hash: tx.Hash,
BlockNumber: height,
Value: value,
Fee: fees,
FeeBalance: feeBalance,
Confirmed: confirmed,
}
return res, nil
}
func (node Node) CreateTx(inputs []*types.TxInput, outputs []*types.TxOutput) (string, string, error) {
if len(inputs) != 1 || len(outputs) != 1 {
return "", "", fmt.Errorf("must have exactly one input and output")
} else if inputs[0].Value.Cmp(outputs[0].Value) != 0 {
return "", "", fmt.Errorf("inputs and outputs must have same value")
}
input := inputs[0]
output := outputs[0]
chainID, err := node.getChainID()
if err != nil {
return "", "", err
}
nonce, err := node.getPendingNonce(node.address)
if err != nil {
return "", "", err
}
// handle for future nonces
nonce += uint64(input.Index)
var toAddress string
var value *big.Int
var gasLimit uint64
var data []byte
if node.erc20 != nil {
toAddress = node.erc20.Address
value = new(big.Int)
gasLimit = 100000
outputAddress := ethCommon.HexToAddress(output.Address)
data = ethtx.GenerateContractData("transfer(address,uint256)", outputAddress.Bytes(), input.Value.Bytes())
} else {
toAddress = output.Address
value = new(big.Int).Set(input.Value)
gasLimit, err = node.sendEstimateGas(node.address, toAddress, "0x"+value.Text(16))
if err != nil {
return "", "", err
} else if gasLimit != 21000 {
gasLimit += 29000
}
}
baseFee, err := node.getBaseFee()
if err != nil {
return "", "", err
}
tx, fee, err := ethtx.NewTx(node.privKey, toAddress, data, value, baseFee, gasLimit, nonce, chainID)
if err != nil {
return "", "", err
} else if node.erc20 != nil && (input.FeeBalance == nil || input.FeeBalance.Cmp(fee) > 0) {
return "", "", fmt.Errorf("insufficient fee balance")
}
txid := ethtx.CalculateTxID(tx)
if node.erc20 == nil {
output.Value.Sub(output.Value, fee)
}
output.Fee = fee
return txid, tx, nil
}
func (node Node) BroadcastTx(tx string) (string, error) {
return node.sendRawTransaction(tx)
}