Skip to content

Commit 508bcae

Browse files
committed
eth: Use user set gas limit.
The user set max fee was only used to compare with the server suggested fee and did not apply to trades. Use the user set max fee for all swaps, redeems, and refunds. Also use the higher of the server estimate and an on chain estimate for fund locking.
1 parent 9c1a7f2 commit 508bcae

File tree

2 files changed

+110
-108
lines changed

2 files changed

+110
-108
lines changed

client/asset/eth/eth.go

Lines changed: 86 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1969,8 +1969,13 @@ func (w *assetWallet) PreRedeem(req *asset.PreRedeemForm) (*asset.PreRedeem, err
19691969
return nil, err
19701970
}
19711971

1972-
bestCase := nRedeem * req.FeeSuggestion
1973-
worstCase := oneRedeem * req.Lots * req.FeeSuggestion
1972+
feeRate, _, _, err := w.feeRate(w.ctx, req.FeeSuggestion, true)
1973+
if err != nil {
1974+
return nil, fmt.Errorf("unable to get fee rate: %v", err)
1975+
}
1976+
1977+
bestCase := nRedeem * feeRate
1978+
worstCase := oneRedeem * req.Lots * feeRate
19741979
userOpRequired := false
19751980

19761981
w.bundlerMtx.RLock()
@@ -2000,7 +2005,11 @@ func (w *assetWallet) SingleLotRedeemFees(assetVer uint32, feeSuggestion uint64)
20002005
if g == nil {
20012006
return 0, fmt.Errorf("no gases known for %d, constract version %d", w.assetID, contractVersion(assetVer))
20022007
}
2003-
return g.Redeem * feeSuggestion, nil
2008+
feeRate, _, _, err := w.feeRate(w.ctx, feeSuggestion, true)
2009+
if err != nil {
2010+
return 0, fmt.Errorf("unable to get fee rate: %v", err)
2011+
}
2012+
return g.Redeem * feeRate, nil
20042013
}
20052014

20062015
// coin implements the asset.Coin interface for ETH
@@ -2069,29 +2078,28 @@ func (w *ETHWallet) FundOrder(ord *asset.Order) (asset.Coins, []dex.Bytes, uint6
20692078
dex.BipIDSymbol(w.assetID), ord.MaxFeeRate, dexeth.MinGasTipCap)
20702079
}
20712080

2072-
if w.gasFeeLimit() < ord.MaxFeeRate {
2073-
return nil, nil, 0, fmt.Errorf(
2074-
"%v: server's max fee rate %v higher than configured fee rate limit %v",
2075-
dex.BipIDSymbol(w.assetID), ord.MaxFeeRate, w.gasFeeLimit())
2081+
feeRate, _, _, err := w.feeRate(w.ctx, ord.MaxFeeRate, true)
2082+
if err != nil {
2083+
return nil, nil, 0, fmt.Errorf("unable to get fee rate: %v", err)
20762084
}
20772085

20782086
contractVer := contractVersion(ord.AssetVersion)
20792087

20802088
g, err := w.initGasEstimate(int(ord.MaxSwapCount), contractVer,
2081-
ord.RedeemVersion, ord.RedeemAssetID, ord.MaxFeeRate)
2089+
ord.RedeemVersion, ord.RedeemAssetID, feeRate)
20822090
if err != nil {
20832091
return nil, nil, 0, fmt.Errorf("error estimating swap gas: %v", err)
20842092
}
20852093

2086-
ethToLock := ord.MaxFeeRate*g.Swap*ord.MaxSwapCount + ord.Value
2094+
ethToLock := feeRate*g.Swap*ord.MaxSwapCount + ord.Value
20872095
// Note: In a future refactor, we could lock the redemption funds here too
20882096
// and signal to the user so that they don't call `RedeemN`. This has the
20892097
// same net effect, but avoids a lockFunds -> unlockFunds for us and likely
20902098
// some work for the caller as well. We can't just always do it that way and
20912099
// remove RedeemN, since we can't guarantee that the redemption asset is in
20922100
// our fee-family. though it could still be an AccountRedeemer.
20932101
w.log.Debugf("Locking %s to swap %s in up to %d swaps at a fee rate of %d gwei/gas using up to %d gas per swap",
2094-
w.amtString(ethToLock), w.amtString(ord.Value), ord.MaxSwapCount, ord.MaxFeeRate, g.Swap)
2102+
w.amtString(ethToLock), w.amtString(ord.Value), ord.MaxSwapCount, feeRate, g.Swap)
20952103

20962104
coin := w.createFundingCoin(ethToLock)
20972105

@@ -2109,10 +2117,9 @@ func (w *TokenWallet) FundOrder(ord *asset.Order) (asset.Coins, []dex.Bytes, uin
21092117
dex.BipIDSymbol(w.assetID), ord.MaxFeeRate, dexeth.MinGasTipCap)
21102118
}
21112119

2112-
if w.gasFeeLimit() < ord.MaxFeeRate {
2113-
return nil, nil, 0, fmt.Errorf(
2114-
"%v: server's max fee rate %v higher than configured fee rate limit %v",
2115-
dex.BipIDSymbol(w.assetID), ord.MaxFeeRate, w.gasFeeLimit())
2120+
feeRate, _, _, err := w.feeRate(w.ctx, ord.MaxFeeRate, true)
2121+
if err != nil {
2122+
return nil, nil, 0, fmt.Errorf("unable to get fee rate: %v", err)
21162123
}
21172124

21182125
approvalStatus, err := w.swapContractApprovalStatus(ord.AssetVersion)
@@ -2130,12 +2137,12 @@ func (w *TokenWallet) FundOrder(ord *asset.Order) (asset.Coins, []dex.Bytes, uin
21302137
}
21312138

21322139
g, err := w.initGasEstimate(int(ord.MaxSwapCount), contractVersion(ord.AssetVersion),
2133-
ord.RedeemVersion, ord.RedeemAssetID, ord.MaxFeeRate)
2140+
ord.RedeemVersion, ord.RedeemAssetID, feeRate)
21342141
if err != nil {
21352142
return nil, nil, 0, fmt.Errorf("error estimating swap gas: %v", err)
21362143
}
21372144

2138-
ethToLock := ord.MaxFeeRate * g.Swap * ord.MaxSwapCount
2145+
ethToLock := feeRate * g.Swap * ord.MaxSwapCount
21392146
var success bool
21402147
if err = w.lockFunds(ord.Value, initiationReserve); err != nil {
21412148
return nil, nil, 0, fmt.Errorf("error locking token funds: %v", err)
@@ -2147,7 +2154,7 @@ func (w *TokenWallet) FundOrder(ord *asset.Order) (asset.Coins, []dex.Bytes, uin
21472154
}()
21482155

21492156
w.log.Debugf("Locking %s to swap %s in up to %d swaps at a fee rate of %d gwei/gas using up to %d gas per swap",
2150-
w.parent.amtString(ethToLock), w.amtString(ord.Value), ord.MaxSwapCount, ord.MaxFeeRate, g.Swap)
2157+
w.parent.amtString(ethToLock), w.amtString(ord.Value), ord.MaxSwapCount, feeRate, g.Swap)
21512158
if err := w.parent.lockFunds(ethToLock, initiationReserve); err != nil {
21522159
return nil, nil, 0, err
21532160
}
@@ -2161,21 +2168,20 @@ func (w *TokenWallet) FundOrder(ord *asset.Order) (asset.Coins, []dex.Bytes, uin
21612168
// FundMultiOrder funds multiple orders in one shot. No special handling is
21622169
// required for ETH as ETH does not over-lock during funding.
21632170
func (w *ETHWallet) FundMultiOrder(ord *asset.MultiOrder, maxLock uint64) ([]asset.Coins, [][]dex.Bytes, uint64, error) {
2164-
if w.gasFeeLimit() < ord.MaxFeeRate {
2165-
return nil, nil, 0, fmt.Errorf(
2166-
"%v: server's max fee rate %v higher than configured fee rate limit %v",
2167-
dex.BipIDSymbol(w.assetID), ord.MaxFeeRate, w.gasFeeLimit())
2171+
feeRate, _, _, err := w.feeRate(w.ctx, ord.MaxFeeRate, true)
2172+
if err != nil {
2173+
return nil, nil, 0, fmt.Errorf("unable to get fee rate: %v", err)
21682174
}
21692175

2170-
g, err := w.initGasEstimate(1, ord.AssetVersion, ord.RedeemVersion, ord.RedeemAssetID, ord.MaxFeeRate)
2176+
g, err := w.initGasEstimate(1, ord.AssetVersion, ord.RedeemVersion, ord.RedeemAssetID, feeRate)
21712177
if err != nil {
21722178
return nil, nil, 0, fmt.Errorf("error estimating swap gas: %v", err)
21732179
}
21742180

21752181
var totalToLock uint64
21762182
allCoins := make([]asset.Coins, 0, len(ord.Values))
21772183
for _, value := range ord.Values {
2178-
toLock := ord.MaxFeeRate*g.Swap*value.MaxSwapCount + value.Value
2184+
toLock := feeRate*g.Swap*value.MaxSwapCount + value.Value
21792185
allCoins = append(allCoins, asset.Coins{w.createFundingCoin(toLock)})
21802186
totalToLock += toLock
21812187
}
@@ -2199,10 +2205,9 @@ func (w *ETHWallet) FundMultiOrder(ord *asset.MultiOrder, maxLock uint64) ([]ass
21992205
// FundMultiOrder funds multiple orders in one shot. No special handling is
22002206
// required for ETH as ETH does not over-lock during funding.
22012207
func (w *TokenWallet) FundMultiOrder(ord *asset.MultiOrder, maxLock uint64) ([]asset.Coins, [][]dex.Bytes, uint64, error) {
2202-
if w.gasFeeLimit() < ord.MaxFeeRate {
2203-
return nil, nil, 0, fmt.Errorf(
2204-
"%v: server's max fee rate %v higher than configured fee rate limit %v",
2205-
dex.BipIDSymbol(w.assetID), ord.MaxFeeRate, w.gasFeeLimit())
2208+
feeRate, _, _, err := w.feeRate(w.ctx, ord.MaxFeeRate, true)
2209+
if err != nil {
2210+
return nil, nil, 0, fmt.Errorf("unable to get fee rate: %v", err)
22062211
}
22072212

22082213
approvalStatus, err := w.swapContractApprovalStatus(ord.AssetVersion)
@@ -2220,15 +2225,15 @@ func (w *TokenWallet) FundMultiOrder(ord *asset.MultiOrder, maxLock uint64) ([]a
22202225
}
22212226

22222227
g, err := w.initGasEstimate(1, ord.AssetVersion,
2223-
ord.RedeemVersion, ord.RedeemAssetID, ord.MaxFeeRate)
2228+
ord.RedeemVersion, ord.RedeemAssetID, feeRate)
22242229
if err != nil {
22252230
return nil, nil, 0, fmt.Errorf("error estimating swap gas: %v", err)
22262231
}
22272232

22282233
var totalETHToLock, totalTokenToLock uint64
22292234
allCoins := make([]asset.Coins, 0, len(ord.Values))
22302235
for _, value := range ord.Values {
2231-
ethToLock := ord.MaxFeeRate * g.Swap * value.MaxSwapCount
2236+
ethToLock := feeRate * g.Swap * value.MaxSwapCount
22322237
tokenToLock := value.Value
22332238
allCoins = append(allCoins, asset.Coins{w.createTokenFundingCoin(tokenToLock, ethToLock)})
22342239
totalETHToLock += ethToLock
@@ -2561,8 +2566,9 @@ var _ asset.Receipt = (*swapReceipt)(nil)
25612566
// max fees that will possibly be used, since in ethereum with EIP-1559 we cannot
25622567
// know exactly how much fees will be used.
25632568
func (w *ETHWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, uint64, error) {
2564-
if swaps.FeeRate == 0 {
2565-
return nil, nil, 0, fmt.Errorf("cannot send swap with with zero fee rate")
2569+
feeRate, maxFeeRate, tipRate, err := w.feeRate(w.ctx, swaps.FeeRate, false)
2570+
if err != nil {
2571+
return nil, nil, 0, fmt.Errorf("unable to get fee rate: %v", err)
25662572
}
25672573

25682574
fail := func(s string, a ...any) ([]asset.Receipt, asset.Coin, uint64, error) {
@@ -2585,20 +2591,20 @@ func (w *ETHWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, uint6
25852591

25862592
contractVer := contractVersion(swaps.AssetVersion)
25872593
n := len(swaps.Contracts)
2588-
oneSwap, nSwap, err := w.swapGas(n, contractVer, swaps.FeeRate)
2594+
oneSwap, nSwap, err := w.swapGas(n, contractVer, feeRate)
25892595
if err != nil {
25902596
return fail("error getting gas fees: %v", err)
25912597
}
25922598
gasLimit := oneSwap * uint64(n) // naive unbatched, higher but not realistic
2593-
fees := gasLimit * swaps.FeeRate
2599+
fees := gasLimit * feeRate
25942600
if swapVal+fees > reservedVal {
25952601
if n == 1 {
25962602
return fail("unfunded swap: %d < %d", reservedVal, swapVal+fees)
25972603
}
25982604
w.log.Warnf("Unexpectedly low reserves for %d swaps: %d < %d", n, reservedVal, swapVal+fees)
25992605
// Since this is a batch swap, attempt to use the realistic limits.
26002606
gasLimit = nSwap
2601-
fees = gasLimit * swaps.FeeRate
2607+
fees = gasLimit * feeRate
26022608
if swapVal+fees > reservedVal {
26032609
// If the live gas estimate is giving us an unrealistically high
26042610
// value, we're in trouble, so we might consider a third fallback
@@ -2611,13 +2617,8 @@ func (w *ETHWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, uint6
26112617
}
26122618
}
26132619

2614-
maxFeeRate := dexeth.GweiToWei(swaps.FeeRate)
2615-
_, tipRate, err := w.currentNetworkFees(w.ctx)
2616-
if err != nil {
2617-
return fail("Swap: failed to get network tip cap: %w", err)
2618-
}
2619-
2620-
tx, err := w.initiate(w.ctx, w.assetID, swaps.Contracts, gasLimit, maxFeeRate, tipRate, contractVer)
2620+
maxFeeRateBig := dexeth.GweiToWei(maxFeeRate)
2621+
tx, err := w.initiate(w.ctx, w.assetID, swaps.Contracts, gasLimit, maxFeeRateBig, tipRate, contractVer)
26212622
if err != nil {
26222623
return fail("Swap: initiate error: %w", err)
26232624
}
@@ -2670,8 +2671,9 @@ func acToLocator(contractVer uint32, swap *asset.Contract, evmValue *big.Int, fr
26702671
// max fees that will possibly be used, since in ethereum with EIP-1559 we cannot
26712672
// know exactly how much fees will be used.
26722673
func (w *TokenWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, uint64, error) {
2673-
if swaps.FeeRate == 0 {
2674-
return nil, nil, 0, fmt.Errorf("cannot send swap with with zero fee rate")
2674+
feeRate, maxFeeRate, tipRate, err := w.feeRate(w.ctx, swaps.FeeRate, false)
2675+
if err != nil {
2676+
return nil, nil, 0, fmt.Errorf("unable to get fee rate: %v", err)
26752677
}
26762678

26772679
fail := func(s string, a ...any) ([]asset.Receipt, asset.Coin, uint64, error) {
@@ -2699,37 +2701,32 @@ func (w *TokenWallet) Swap(swaps *asset.Swaps) ([]asset.Receipt, asset.Coin, uin
26992701

27002702
n := len(swaps.Contracts)
27012703
contractVer := contractVersion(swaps.AssetVersion)
2702-
oneSwap, nSwap, err := w.swapGas(n, contractVer, swaps.FeeRate)
2704+
oneSwap, nSwap, err := w.swapGas(n, contractVer, feeRate)
27032705
if err != nil {
27042706
return fail("error getting gas fees: %v", err)
27052707
}
27062708

27072709
gasLimit := oneSwap * uint64(n)
2708-
fees := gasLimit * swaps.FeeRate
2710+
fees := gasLimit * feeRate
27092711
if fees > reservedParent {
27102712
if n == 1 {
27112713
return fail("unfunded token swap fees: %d < %d", reservedParent, fees)
27122714
}
27132715
// Since this is a batch swap, attempt to use the realistic limits.
27142716
w.log.Warnf("Unexpectedly low reserves for %d swaps: %d < %d", n, reservedVal, swapVal+fees)
27152717
gasLimit = nSwap
2716-
fees = gasLimit * swaps.FeeRate
2718+
fees = gasLimit * feeRate
27172719
if fees > reservedParent {
27182720
return fail("unfunded token swap fees: %d < %d", reservedParent, fees)
27192721
} // See (*ETHWallet).Swap comments for a third option.
27202722
}
27212723

2722-
maxFeeRate := dexeth.GweiToWei(swaps.FeeRate)
2723-
_, tipRate, err := w.currentNetworkFees(w.ctx)
2724-
if err != nil {
2725-
return fail("Swap: failed to get network tip cap: %w", err)
2726-
}
2727-
2724+
maxFeeRateBig := dexeth.GweiToWei(maxFeeRate)
27282725
if w.netToken.SwapContracts[swaps.AssetVersion] == nil {
27292726
return fail("unable to find contract address for asset %d contract version %d", w.assetID, swaps.AssetVersion)
27302727
}
27312728

2732-
tx, err := w.initiate(w.ctx, w.assetID, swaps.Contracts, gasLimit, maxFeeRate, tipRate, contractVer)
2729+
tx, err := w.initiate(w.ctx, w.assetID, swaps.Contracts, gasLimit, maxFeeRateBig, tipRate, contractVer)
27332730
if err != nil {
27342731
return fail("Swap: initiate error: %w", err)
27352732
}
@@ -3088,17 +3085,6 @@ func (w *assetWallet) Redeem(form *asset.RedeemForm, feeWallet *assetWallet, non
30883085
return fail(fmt.Errorf("no gas table"))
30893086
}
30903087

3091-
if feeWallet == nil {
3092-
feeWallet = w
3093-
}
3094-
bal, err := feeWallet.Balance()
3095-
if err != nil {
3096-
return fail(fmt.Errorf("error getting balance in excessive gas fee recovery: %v", err))
3097-
}
3098-
3099-
gasLimit, gasFeeCap := g.Redeem*n, form.FeeSuggestion
3100-
originalFundsReserved := gasLimit * gasFeeCap
3101-
31023088
/* We could get a gas estimate via RPC, but this will reveal the secret key
31033089
before submitting the redeem transaction. This is not OK for maker.
31043090
Disable for now.
@@ -3125,25 +3111,12 @@ func (w *assetWallet) Redeem(form *asset.RedeemForm, feeWallet *assetWallet, non
31253111
}
31263112
*/
31273113

3128-
// If the base fee is higher than the FeeSuggestion we attempt to increase
3129-
// the gasFeeCap to 2*baseFee. If we don't have enough funds, we use the
3130-
// funds we have available.
3131-
baseFee, tipRate, err := w.currentNetworkFees(w.ctx)
3114+
feeRate, maxFeeRate, tipRate, err := w.feeRate(w.ctx, form.FeeSuggestion, false)
31323115
if err != nil {
3133-
return fail(fmt.Errorf("Error getting net fee state: %w", err))
3134-
}
3135-
baseFeeGwei := dexeth.WeiToGweiCeil(baseFee)
3136-
if baseFeeGwei > form.FeeSuggestion {
3137-
additionalFundsNeeded := (2 * baseFeeGwei * gasLimit) - originalFundsReserved
3138-
if bal.Available > additionalFundsNeeded {
3139-
gasFeeCap = 2 * baseFeeGwei
3140-
} else {
3141-
gasFeeCap = (bal.Available + originalFundsReserved) / gasLimit
3142-
}
3143-
w.log.Warnf("base fee %d > server max fee rate %d. using %d as gas fee cap for redemption", baseFeeGwei, form.FeeSuggestion, gasFeeCap)
3116+
return fail(fmt.Errorf("unable to get fee rate: %v", err))
31443117
}
31453118

3146-
tx, err := w.redeem(w.ctx, form.Redemptions, gasFeeCap, tipRate, gasLimit, contractVer)
3119+
tx, err := w.redeem(w.ctx, form.Redemptions, maxFeeRate, tipRate, g.Redeem*n, contractVer)
31473120
if err != nil {
31483121
return fail(fmt.Errorf("Redeem: redeem error: %w", err))
31493122
}
@@ -3162,7 +3135,7 @@ func (w *assetWallet) Redeem(form *asset.RedeemForm, feeWallet *assetWallet, non
31623135

31633136
// This is still a fee estimate. The actual gas cost will be returned in the
31643137
// receipt.
3165-
fees := g.RedeemN(len(form.Redemptions)) * form.FeeSuggestion
3138+
fees := g.RedeemN(len(form.Redemptions)) * feeRate
31663139
return txs, outputCoin, fees, nil
31673140
}
31683141

@@ -4498,13 +4471,17 @@ func (w *assetWallet) Refund(_, contract dex.Bytes, feeRate uint64) (dex.Bytes,
44984471
return nil, fmt.Errorf("Refund: swap with locator %x is not refundable", locator)
44994472
}
45004473

4501-
maxFeeRate := dexeth.GweiToWei(feeRate)
4474+
maxFeeRate := w.gasFeeLimit()
4475+
if feeRate > maxFeeRate {
4476+
w.log.Warnf("Refund fee rate higher than max fee. %d > %d", feeRate, maxFeeRate)
4477+
}
4478+
maxFeeRateBig := dexeth.GweiToWei(maxFeeRate)
45024479
_, tipRate, err := w.currentNetworkFees(w.ctx)
45034480
if err != nil {
45044481
return nil, fmt.Errorf("Refund: failed to get network tip cap: %w", err)
45054482
}
45064483

4507-
tx, err := w.refund(locator, w.atomize(vector.Value), maxFeeRate, tipRate, contractVer)
4484+
tx, err := w.refund(locator, w.atomize(vector.Value), maxFeeRateBig, tipRate, contractVer)
45084485
if err != nil {
45094486
return nil, fmt.Errorf("Refund: failed to call refund: %w", err)
45104487
}
@@ -5123,6 +5100,32 @@ func (w *baseWallet) currentBaseFee(ctx context.Context) (*big.Int, error) {
51235100
return base, nil
51245101
}
51255102

5103+
// feeRate returns the higher of suggested fee rate and the current on chain fee
5104+
// rate and caps with the maxFeeRate.
5105+
func (w *baseWallet) feeRate(ctx context.Context, suggestedFeeRate uint64, addBuffer bool) (feeRate, maxFeeRate uint64, tipRate *big.Int, err error) {
5106+
maxFeeRate = w.gasFeeLimit()
5107+
if maxFeeRate < suggestedFeeRate {
5108+
return 0, 0, nil, fmt.Errorf(
5109+
"server's max fee rate %v higher than configured fee rate limit %v",
5110+
suggestedFeeRate, maxFeeRate)
5111+
}
5112+
var estimate uint64
5113+
liveRateBig, tipRate, err := w.recommendedMaxFeeRate(ctx)
5114+
if err != nil {
5115+
return 0, 0, nil, fmt.Errorf("unable to get recommended fee rate: %v", err)
5116+
}
5117+
estimate, err = dexeth.WeiToGweiSafe(liveRateBig)
5118+
if err != nil {
5119+
return 0, 0, nil, fmt.Errorf("unable to convert to gwei: %v", err)
5120+
}
5121+
estimate = max(estimate, suggestedFeeRate)
5122+
// Add a %25 buffer for initial fund locking.
5123+
if addBuffer {
5124+
estimate = estimate * 5 / 4
5125+
}
5126+
return min(estimate, maxFeeRate), maxFeeRate, tipRate, nil
5127+
}
5128+
51265129
// currentNetworkFees give the current base fee rate (from the best header),
51275130
// and recommended tip cap.
51285131
func (w *baseWallet) currentNetworkFees(ctx context.Context) (baseRate, tipRate *big.Int, err error) {

0 commit comments

Comments
 (0)