Skip to content

Commit 2077aaf

Browse files
authored
Support EIP-1559 in transactor (#45)
* Support EIP-1559 in transactor * Defer to geth if 1559 params are unspecified
1 parent a4b9fb5 commit 2077aaf

File tree

2 files changed

+130
-45
lines changed

2 files changed

+130
-45
lines changed

eth/options.go

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,16 @@ import (
99

1010
type txOptions struct {
1111
// Transact
12-
ethValue *big.Int
13-
minGasGwei uint64
14-
maxGasGwei uint64
15-
addGasGwei uint64
16-
forceGasGwei uint64
12+
ethValue *big.Int
13+
// Legacy Tx gas price
14+
minGasGwei uint64
15+
maxGasGwei uint64
16+
addGasGwei uint64
17+
forceGasGwei uint64
18+
// EIP-1559 Tx gas price
19+
maxFeePerGasGwei uint64
20+
maxPriorityFeePerGasGwei uint64
21+
// Gas limit
1722
gasLimit uint64
1823
addGasEstimateRatio float64
1924

@@ -91,6 +96,18 @@ func WithForceGasGwei(g uint64) TxOption {
9196
})
9297
}
9398

99+
func WithMaxFeePerGasGwei(g uint64) TxOption {
100+
return newFuncTxOption(func(o *txOptions) {
101+
o.maxFeePerGasGwei = g
102+
})
103+
}
104+
105+
func WithMaxPriorityFeePerGasGwei(g uint64) TxOption {
106+
return newFuncTxOption(func(o *txOptions) {
107+
o.maxPriorityFeePerGasGwei = g
108+
})
109+
}
110+
94111
func WithGasLimit(l uint64) TxOption {
95112
return newFuncTxOption(func(o *txOptions) {
96113
o.gasLimit = l

eth/transactor.go

Lines changed: 108 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"math/big"
1010
"strings"
1111
"sync"
12+
"time"
1213

1314
"github.com/celer-network/goutils/log"
1415
"github.com/ethereum/go-ethereum"
@@ -25,7 +26,10 @@ const (
2526
)
2627

2728
var (
28-
ErrExceedMaxGas = errors.New("suggested gas price exceeds max allowed")
29+
ErrExceedMaxGas = errors.New("suggested gas price exceeds max allowed")
30+
ErrConflictingGasFlags = errors.New("cannot specify both legacy and EIP-1559 gas flags")
31+
32+
ctxTimeout = 3 * time.Second
2933
)
3034

3135
type Transactor struct {
@@ -129,40 +133,14 @@ func (t *Transactor) transact(
129133
}
130134
signer := t.newTransactOpts()
131135
client := t.client
132-
gasPrice, err := determineGasPrice(txopts, client)
136+
// Set gas price and limit
137+
err := t.determineGas(method, signer, txopts, client)
133138
if err != nil {
134-
return nil, fmt.Errorf("determineGasPrice err: %w", err)
139+
return nil, fmt.Errorf("determineGas err: %w", err)
135140
}
136-
signer.GasPrice = gasPrice
141+
// Set value
137142
signer.Value = txopts.ethValue
138-
if txopts.gasLimit > 0 {
139-
// use the specified limit
140-
signer.GasLimit = txopts.gasLimit
141-
} else if txopts.addGasEstimateRatio > 0.0 {
142-
// estimate gas and increase gas limit by configured ratio
143-
signer.NoSend = true
144-
dryTx, err := method(client, signer)
145-
if err != nil {
146-
return nil, fmt.Errorf("dry-run err: %w", err)
147-
}
148-
signer.NoSend = false
149-
typesMsg, err := dryTx.AsMessage(types.NewLondonSigner(t.chainId), big.NewInt(0))
150-
if err != nil {
151-
return nil, fmt.Errorf("failed to get typesMsg err: %w", err)
152-
}
153-
callMsg := ethereum.CallMsg{
154-
From: typesMsg.From(),
155-
To: typesMsg.To(),
156-
GasPrice: typesMsg.GasPrice(),
157-
Value: typesMsg.Value(),
158-
Data: typesMsg.Data(),
159-
}
160-
estimatedGas, err := client.EstimateGas(context.Background(), callMsg)
161-
if err != nil {
162-
return nil, fmt.Errorf("failed to estimate gas err: %w", err)
163-
}
164-
signer.GasLimit = uint64(float64(estimatedGas) * (1 + txopts.addGasEstimateRatio))
165-
}
143+
// Set nonce
166144
pendingNonce, err := t.client.PendingNonceAt(context.Background(), t.address)
167145
if err != nil {
168146
return nil, fmt.Errorf("PendingNonceAt err: %w", err)
@@ -227,16 +205,103 @@ func (t *Transactor) transact(
227205
}
228206
}
229207

230-
func determineGasPrice(txopts txOptions, client *ethclient.Client) (*big.Int, error) {
208+
// determineGas sets the gas price and gas limit on the signer
209+
func (t *Transactor) determineGas(method TxMethod, signer *bind.TransactOpts, txopts txOptions, client *ethclient.Client) error {
210+
// 1. Determine gas price
211+
// Only accept legacy flags or EIP-1559 flags, not both
212+
hasLegacyFlags := txopts.forceGasGwei > 0 || txopts.minGasGwei > 0 || txopts.maxGasGwei > 0 || txopts.addGasGwei > 0
213+
has1559Flags := txopts.maxFeePerGasGwei > 0 || txopts.maxPriorityFeePerGasGwei > 0
214+
if hasLegacyFlags && has1559Flags {
215+
return ErrConflictingGasFlags
216+
}
217+
// Check if chain supports EIP-1559
218+
ctx, cancel := context.WithTimeout(context.Background(), ctxTimeout)
219+
defer cancel()
220+
head, err := client.HeaderByNumber(ctx, nil)
221+
if err != nil {
222+
return fmt.Errorf("failed to call HeaderByNumber: %w", err)
223+
}
224+
send1559Tx := false
225+
if head.BaseFee != nil && !hasLegacyFlags {
226+
send1559Tx = true
227+
err = determine1559GasPrice(ctx, signer, txopts, client, head)
228+
if err != nil {
229+
return fmt.Errorf("failed to determine EIP-1559 gas price: %w", err)
230+
}
231+
} else {
232+
// Legacy pricing
233+
err = determineLegacyGasPrice(ctx, signer, txopts, client)
234+
if err != nil {
235+
return fmt.Errorf("failed to determine legacy gas price: %w", err)
236+
}
237+
}
238+
239+
// 2. Determine gas limit
240+
if txopts.gasLimit > 0 {
241+
// Use the specified limit if set
242+
signer.GasLimit = txopts.gasLimit
243+
return nil
244+
} else if txopts.addGasEstimateRatio > 0.0 {
245+
// 2.1. Estimate gas
246+
signer.NoSend = true
247+
dryTx, err := method(client, signer)
248+
if err != nil {
249+
return fmt.Errorf("tx dry-run err: %w", err)
250+
}
251+
signer.NoSend = false
252+
var typesMsg types.Message
253+
londonSigner := types.NewLondonSigner(t.chainId)
254+
if send1559Tx {
255+
typesMsg, err = dryTx.AsMessage(londonSigner, head.BaseFee)
256+
} else {
257+
typesMsg, err = dryTx.AsMessage(londonSigner, nil)
258+
}
259+
if err != nil {
260+
return fmt.Errorf("failed to get typesMsg: %w", err)
261+
}
262+
callMsg := ethereum.CallMsg{
263+
From: typesMsg.From(),
264+
To: typesMsg.To(),
265+
GasPrice: typesMsg.GasPrice(),
266+
Value: typesMsg.Value(),
267+
Data: typesMsg.Data(),
268+
}
269+
estimatedGas, err := client.EstimateGas(ctx, callMsg)
270+
if err != nil {
271+
return fmt.Errorf("failed to call EstimateGas: %w", err)
272+
}
273+
// 2.2. Multiply gas limit by the configured ratio
274+
signer.GasLimit = uint64(float64(estimatedGas) * (1 + txopts.addGasEstimateRatio))
275+
}
276+
// If addGasEstimateRatio not specified, just defer to go-ethereum for gas limit estimation
277+
return nil
278+
}
279+
280+
// determine1559GasPrice sets the gas price on the signer based on the EIP-1559 fee model
281+
func determine1559GasPrice(
282+
ctx context.Context, signer *bind.TransactOpts, txopts txOptions, client *ethclient.Client, head *types.Header) error {
283+
if txopts.maxPriorityFeePerGasGwei > 0 {
284+
signer.GasTipCap = new(big.Int).SetUint64(txopts.maxPriorityFeePerGasGwei * 1e9)
285+
}
286+
if txopts.maxFeePerGasGwei > 0 {
287+
signer.GasFeeCap = new(big.Int).SetUint64(txopts.maxFeePerGasGwei * 1e9)
288+
}
289+
return nil
290+
}
291+
292+
// determineLegacyGasPrice sets the gas price on the signer based on the legacy fee model
293+
func determineLegacyGasPrice(
294+
ctx context.Context, signer *bind.TransactOpts, txopts txOptions, client *ethclient.Client) error {
231295
if txopts.forceGasGwei > 0 {
232-
return new(big.Int).SetUint64(txopts.forceGasGwei * 1e9), nil
296+
signer.GasPrice = new(big.Int).SetUint64(txopts.forceGasGwei * 1e9)
297+
return nil
233298
}
234299
gasPrice, err := client.SuggestGasPrice(context.Background())
235300
if err != nil {
236-
return nil, fmt.Errorf("SuggestGasPrice err: %w", err)
301+
return fmt.Errorf("failed to call SuggestGasPrice: %w", err)
237302
}
238-
if txopts.addGasGwei > 0 { // add gas price to the suggested value to speed up transactions
239-
addPrice := new(big.Int).SetUint64(txopts.addGasGwei * 1e9) // 1e9 is 1G
303+
if txopts.addGasGwei > 0 { // Add gas price to the suggested value to speed up transactions
304+
addPrice := new(big.Int).SetUint64(txopts.addGasGwei * 1e9)
240305
gasPrice.Add(gasPrice, addPrice)
241306
}
242307
if txopts.minGasGwei > 0 { // gas can't be lower than minGas
@@ -251,10 +316,11 @@ func determineGasPrice(txopts txOptions, client *ethclient.Client) (*big.Int, er
251316
// GasPrice is larger than allowed cap, return error
252317
if maxPrice.Cmp(gasPrice) < 0 {
253318
log.Warnf("suggested gas price %s larger than cap %s", gasPrice, maxPrice)
254-
return nil, ErrExceedMaxGas
319+
return ErrExceedMaxGas
255320
}
256321
}
257-
return gasPrice, nil
322+
signer.GasPrice = gasPrice
323+
return nil
258324
}
259325

260326
func (t *Transactor) ContractCaller() bind.ContractCaller {
@@ -270,8 +336,10 @@ func (t *Transactor) WaitMined(txHash string, opts ...TxOption) (*types.Receipt,
270336
for _, o := range opts {
271337
o.apply(&txopts)
272338
}
339+
ctx, cancel := context.WithTimeout(context.Background(), ctxTimeout)
340+
defer cancel()
273341
return WaitMinedWithTxHash(
274-
context.Background(), t.client, txHash,
342+
ctx, t.client, txHash,
275343
WithBlockDelay(txopts.blockDelay),
276344
WithPollingInterval(txopts.pollingInterval),
277345
WithTimeout(txopts.timeout),

0 commit comments

Comments
 (0)