diff --git a/core/chains/evm/gas/universal_estimator.go b/core/chains/evm/gas/universal_estimator.go index 6493642ee42..645d94fb362 100644 --- a/core/chains/evm/gas/universal_estimator.go +++ b/core/chains/evm/gas/universal_estimator.go @@ -53,19 +53,20 @@ var ( ) const ( - queryTimeout = 10 * time.Second - MinimumBumpPercentage = 10 // based on geth's spec + queryTimeout = 10 * time.Second + MinimumBumpPercentage = 10 // based on geth's spec ConnectivityPercentile = 80 BaseFeeBufferPercentage = 40 ) type UniversalEstimatorConfig struct { - CacheTimeout time.Duration BumpPercent uint16 + CacheTimeout time.Duration BlockHistoryRange uint64 // inclusive range RewardPercentile float64 + HasMempool bool } //go:generate mockery --quiet --name universalEstimatorClient --output ./mocks/ --case=underscore --structname UniversalEstimatorClient @@ -331,21 +332,30 @@ func (u *UniversalEstimator) BumpDynamicFee(ctx context.Context, originalFee Dyn bumpedMaxPriorityFeePerGas := originalFee.TipCap.AddPercentage(u.config.BumpPercent) bumpedMaxFeePerGas := originalFee.FeeCap.AddPercentage(u.config.BumpPercent) - bumpedMaxPriorityFeePerGas, err = LimitBumpedFee(originalFee.TipCap, currentDynamicPrice.TipCap, bumpedMaxPriorityFeePerGas, maxPrice) - if err != nil { - return bumped, fmt.Errorf("maxPriorityFeePerGas error: %s", err.Error()) - } - priorityFeeThreshold, err := u.getPriorityFeeThreshold() - if err != nil { - return - } - // If either of these two values are 0 it could be that the network has extremely low priority fees or most likely it doesn't have - // a mempool and priority fees are not taken into account. Either way, we should skip the connectivity check because we're only - // going to be charged for the base fee, which is mandatory. - if (priorityFeeThreshold.Cmp(assets.NewWeiI(0)) > 0) && (bumpedMaxPriorityFeePerGas.Cmp(assets.NewWeiI(0)) > 0) && - bumpedMaxPriorityFeePerGas.Cmp(priorityFeeThreshold) > 0 { - return bumped, fmt.Errorf("bumpedMaxPriorityFeePergas: %s is above market's %sth percentile: %s, bumping is halted", - bumpedMaxPriorityFeePerGas, strconv.Itoa(ConnectivityPercentile), priorityFeeThreshold) + if u.config.HasMempool { + bumpedMaxPriorityFeePerGas, err = LimitBumpedFee(originalFee.TipCap, currentDynamicPrice.TipCap, bumpedMaxPriorityFeePerGas, maxPrice) + if err != nil { + return bumped, fmt.Errorf("maxPriorityFeePerGas error: %s", err.Error()) + } + + priorityFeeThreshold, e := u.getPriorityFeeThreshold() + if e != nil { + err = e + return + } + + // If either of these two values are 0 it could be that the network has extremely low priority fees. We should skip the + // connectivity check because we're only going to be charged for the base fee, which is mandatory. + if (priorityFeeThreshold.Cmp(assets.NewWeiI(0)) > 0) && (bumpedMaxPriorityFeePerGas.Cmp(assets.NewWeiI(0)) > 0) && + bumpedMaxPriorityFeePerGas.Cmp(priorityFeeThreshold) > 0 { + return bumped, fmt.Errorf("bumpedMaxPriorityFeePergas: %s is above market's %sth percentile: %s, bumping is halted", + bumpedMaxPriorityFeePerGas, strconv.Itoa(ConnectivityPercentile), priorityFeeThreshold) + } + + } else { + // If the network doesn't have a mempool then transactions are processed in a FCFS manner and maxPriorityFeePerGas value is irrelevant. + // We just need to cap the value at maxPrice in case maxFeePerGas also gets capped. + bumpedMaxPriorityFeePerGas = assets.WeiMin(bumpedMaxPriorityFeePerGas, maxPrice) } bumpedMaxFeePerGas, err = LimitBumpedFee(originalFee.FeeCap, currentDynamicPrice.FeeCap, bumpedMaxFeePerGas, maxPrice) diff --git a/core/chains/evm/gas/universal_estimator_test.go b/core/chains/evm/gas/universal_estimator_test.go index 30eab673495..b61e7cbd711 100644 --- a/core/chains/evm/gas/universal_estimator_test.go +++ b/core/chains/evm/gas/universal_estimator_test.go @@ -338,6 +338,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { cfg := gas.UniversalEstimatorConfig{ BlockHistoryRange: 2, BumpPercent: 50, + HasMempool: true, } expectedFeeCap := originalFee.FeeCap.AddPercentage(cfg.BumpPercent) @@ -401,6 +402,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { cfg := gas.UniversalEstimatorConfig{ BlockHistoryRange: 1, BumpPercent: 50, + HasMempool: true, } u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) @@ -431,6 +433,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { cfg := gas.UniversalEstimatorConfig{ BlockHistoryRange: 1, BumpPercent: 50, + HasMempool: true, } u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) @@ -460,6 +463,7 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { cfg := gas.UniversalEstimatorConfig{ BlockHistoryRange: 1, BumpPercent: 50, + HasMempool: true, } u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) @@ -497,4 +501,35 @@ func TestUniversalEstimatorBumpDynamicFee(t *testing.T) { _, err := u.BumpDynamicFee(tests.Context(t), originalFee, maxPrice, nil) assert.Error(t, err) }) + + t.Run("ignores maxPriorityFeePerGas if there is no mempool", func(t *testing.T) { + client := mocks.NewUniversalEstimatorClient(t) + originalFee := gas.DynamicFee{ + FeeCap: assets.NewWeiI(40), + TipCap: assets.NewWeiI(0), + } + + // Market fees + baseFee := big.NewInt(10) + maxPriorityFeePerGas := big.NewInt(0) + feeHistoryResult := ðereum.FeeHistory{ + OldestBlock: big.NewInt(1), + Reward: [][]*big.Int{{maxPriorityFeePerGas, big.NewInt(0)}}, // first one represents market price and second one connectivity price + BaseFee: []*big.Int{baseFee}, + GasUsedRatio: nil, + } + client.On("FeeHistory", mock.Anything, mock.Anything, mock.Anything).Return(feeHistoryResult, nil).Once() + + cfg := gas.UniversalEstimatorConfig{ + BlockHistoryRange: 1, + BumpPercent: 20, + HasMempool: false, + } + + u := gas.NewUniversalEstimator(logger.Test(t), client, cfg, chainID, nil) + bumpedFee, err := u.BumpDynamicFee(tests.Context(t), originalFee, globalMaxPrice, nil) + assert.NoError(t, err) + assert.Equal(t, assets.NewWeiI(0), (*assets.Wei)(maxPriorityFeePerGas)) + assert.Equal(t, originalFee.FeeCap.AddPercentage(20), bumpedFee.FeeCap) + }) }