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
2728var (
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
3135type 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
260326func (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