Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 1 addition & 25 deletions client/asset/eth/eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -1243,7 +1243,7 @@ func (eth *baseWallet) wallet(assetID uint32) *assetWallet {
return eth.wallets[assetID]
}

func (eth *baseWallet) gasFeeLimit() uint64 {
func (eth *baseWallet) GasFeeLimit() uint64 {
return atomic.LoadUint64(&eth.gasFeeLimitV)
}

Expand Down Expand Up @@ -2069,12 +2069,6 @@ func (w *ETHWallet) FundOrder(ord *asset.Order) (asset.Coins, []dex.Bytes, uint6
dex.BipIDSymbol(w.assetID), ord.MaxFeeRate, dexeth.MinGasTipCap)
}

if w.gasFeeLimit() < ord.MaxFeeRate {
return nil, nil, 0, fmt.Errorf(
"%v: server's max fee rate %v higher than configured fee rate limit %v",
dex.BipIDSymbol(w.assetID), ord.MaxFeeRate, w.gasFeeLimit())
}

contractVer := contractVersion(ord.AssetVersion)

g, err := w.initGasEstimate(int(ord.MaxSwapCount), contractVer,
Expand Down Expand Up @@ -2109,12 +2103,6 @@ func (w *TokenWallet) FundOrder(ord *asset.Order) (asset.Coins, []dex.Bytes, uin
dex.BipIDSymbol(w.assetID), ord.MaxFeeRate, dexeth.MinGasTipCap)
}

if w.gasFeeLimit() < ord.MaxFeeRate {
return nil, nil, 0, fmt.Errorf(
"%v: server's max fee rate %v higher than configured fee rate limit %v",
dex.BipIDSymbol(w.assetID), ord.MaxFeeRate, w.gasFeeLimit())
}

approvalStatus, err := w.swapContractApprovalStatus(ord.AssetVersion)
if err != nil {
return nil, nil, 0, fmt.Errorf("error getting approval status: %v", err)
Expand Down Expand Up @@ -2161,12 +2149,6 @@ func (w *TokenWallet) FundOrder(ord *asset.Order) (asset.Coins, []dex.Bytes, uin
// FundMultiOrder funds multiple orders in one shot. No special handling is
// required for ETH as ETH does not over-lock during funding.
func (w *ETHWallet) FundMultiOrder(ord *asset.MultiOrder, maxLock uint64) ([]asset.Coins, [][]dex.Bytes, uint64, error) {
if w.gasFeeLimit() < ord.MaxFeeRate {
return nil, nil, 0, fmt.Errorf(
"%v: server's max fee rate %v higher than configured fee rate limit %v",
dex.BipIDSymbol(w.assetID), ord.MaxFeeRate, w.gasFeeLimit())
}

g, err := w.initGasEstimate(1, ord.AssetVersion, ord.RedeemVersion, ord.RedeemAssetID, ord.MaxFeeRate)
if err != nil {
return nil, nil, 0, fmt.Errorf("error estimating swap gas: %v", err)
Expand Down Expand Up @@ -2199,12 +2181,6 @@ func (w *ETHWallet) FundMultiOrder(ord *asset.MultiOrder, maxLock uint64) ([]ass
// FundMultiOrder funds multiple orders in one shot. No special handling is
// required for ETH as ETH does not over-lock during funding.
func (w *TokenWallet) FundMultiOrder(ord *asset.MultiOrder, maxLock uint64) ([]asset.Coins, [][]dex.Bytes, uint64, error) {
if w.gasFeeLimit() < ord.MaxFeeRate {
return nil, nil, 0, fmt.Errorf(
"%v: server's max fee rate %v higher than configured fee rate limit %v",
dex.BipIDSymbol(w.assetID), ord.MaxFeeRate, w.gasFeeLimit())
}

approvalStatus, err := w.swapContractApprovalStatus(ord.AssetVersion)
if err != nil {
return nil, nil, 0, fmt.Errorf("error getting approval status: %v", err)
Expand Down
19 changes: 5 additions & 14 deletions client/asset/eth/eth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2386,15 +2386,6 @@ func testFundOrderReturnCoinsFundingCoins(t *testing.T, assetID uint32) {
node.tokenContractor.allow = unlimitedAllowance
}

// Test eth wallet gas fee limit > server MaxFeeRate causes error
tmpGasFeeLimit := eth.gasFeeLimit()
eth.gasFeeLimitV = order.MaxFeeRate - 1
_, _, _, err = w.FundOrder(&order)
if err == nil {
t.Fatalf("eth wallet gas fee limit > server MaxFeeRate should cause error")
}
eth.gasFeeLimitV = tmpGasFeeLimit

w2, eth2, _, shutdown2 := tassetWallet(assetID)
defer shutdown2()
eth2.node = node
Expand Down Expand Up @@ -4605,8 +4596,8 @@ func TestDriverOpen(t *testing.T) {
if !ok {
t.Fatalf("failed to cast wallet as ETHWallet")
}
if eth.gasFeeLimit() != defaultGasFeeLimit {
t.Fatalf("expected gasFeeLimit to be default, but got %v", eth.gasFeeLimit())
if eth.GasFeeLimit() != defaultGasFeeLimit {
t.Fatalf("expected GasFeeLimit to be default, but got %v", eth.GasFeeLimit())
}

// Make sure gas fee limit is properly parsed from settings
Expand All @@ -4619,8 +4610,8 @@ func TestDriverOpen(t *testing.T) {
if !ok {
t.Fatalf("failed to cast wallet as ETHWallet")
}
if eth.gasFeeLimit() != 150 {
t.Fatalf("expected gasFeeLimit to be 150, but got %v", eth.gasFeeLimit())
if eth.GasFeeLimit() != 150 {
t.Fatalf("expected GasFeeLimit to be 150, but got %v", eth.GasFeeLimit())
}
}

Expand Down Expand Up @@ -5170,7 +5161,7 @@ func TestReconfigure(t *testing.T) {
t.Fatalf("unexpected restart")
}

if eth.baseWallet.gasFeeLimit() != ethCfg.GasFeeLimit {
if eth.baseWallet.GasFeeLimit() != ethCfg.GasFeeLimit {
t.Fatal("gas fee limit was not updated properly")
}
}
Expand Down
3 changes: 3 additions & 0 deletions client/asset/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -948,6 +948,9 @@ type DynamicSwapper interface {
DynamicSwapFeesPaid(ctx context.Context, coinID, contractData dex.Bytes) (fee uint64, secretHashes [][]byte, err error)
// DynamicRedemptionFeesPaid returns fees for redemption transactions.
DynamicRedemptionFeesPaid(ctx context.Context, coinID, contractData dex.Bytes) (fee uint64, secretHashes [][]byte, err error)
// GasFeeLimit returns the user set gas fee limit to be used as the max
// fee if higher than server.
GasFeeLimit() uint64
}

// FeeRater is capable of retrieving a non-critical fee rate estimate for an
Expand Down
2 changes: 1 addition & 1 deletion client/asset/polygon/polygon.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func init() {
const (
// BipID is the BIP-0044 asset ID for Polygon.
BipID = 966
defaultGasFeeLimit = 1000
defaultGasFeeLimit = 2500
walletTypeRPC = "rpc"
walletTypeToken = "token"
)
Expand Down
31 changes: 26 additions & 5 deletions client/core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -6218,7 +6218,8 @@ func (c *Core) prepareForTradeRequestPrep(pw []byte, base, quote uint32, host st
}

func (c *Core) createTradeRequest(wallets *walletSet, coins asset.Coins, redeemScripts []dex.Bytes, dc *dexConnection, redeemAddr string,
form *TradeForm, redemptionRefundLots uint64, fundingFees uint64, assetConfigs *assetSet, mktConf *msgjson.Market, errCloser *dex.ErrorCloser) (*tradeRequest, error) {
form *TradeForm, redemptionRefundLots uint64, fundingFees, maxFeeRate uint64, assetConfigs *assetSet, mktConf *msgjson.Market,
errCloser *dex.ErrorCloser) (*tradeRequest, error) {
coinIDs := make([]order.CoinID, 0, len(coins))
for i := range coins {
coinIDs = append(coinIDs, []byte(coins[i].ID()))
Expand Down Expand Up @@ -6498,11 +6499,21 @@ func (c *Core) prepareTradeRequest(pw []byte, form *TradeForm) (*tradeRequest, e
qty, assetConfigs.baseAsset.Symbol, rate, mktConf.LotSize)
}

maxFeeRate := assetConfigs.fromAsset.MaxFeeRate
if dynamicSwapper, is := fromWallet.Wallet.(asset.DynamicSwapper); is {
localMaxFee := dynamicSwapper.GasFeeLimit()
if maxFeeRate > localMaxFee {
return nil, codedError(walletErr, fmt.Errorf("%v: server's max fee rate %v higher than configured fee rate limit %v",
dex.BipIDSymbol(assetConfigs.fromAsset.ID), maxFeeRate, localMaxFee))
}
maxFeeRate = localMaxFee
}

coins, redeemScripts, fundingFees, err := fromWallet.FundOrder(&asset.Order{
AssetVersion: assetConfigs.fromAsset.Version,
Value: fundQty,
MaxSwapCount: lots,
MaxFeeRate: assetConfigs.fromAsset.MaxFeeRate,
MaxFeeRate: maxFeeRate,
Immediate: isImmediate,
FeeSuggestion: c.feeSuggestion(dc, assetConfigs.fromAsset.ID),
Options: form.Options,
Expand Down Expand Up @@ -6535,7 +6546,7 @@ func (c *Core) prepareTradeRequest(pw []byte, form *TradeForm) (*tradeRequest, e
})

tradeRequest, err := c.createTradeRequest(wallets, coins, redeemScripts, dc, redeemAddr, form,
redemptionRefundLots, fundingFees, assetConfigs, mktConf, errCloser)
redemptionRefundLots, fundingFees, maxFeeRate, assetConfigs, mktConf, errCloser)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -6588,10 +6599,20 @@ func (c *Core) prepareMultiTradeRequests(pw []byte, form *MultiTradeForm) ([]*tr
})
}

maxFeeRate := assetConfigs.fromAsset.MaxFeeRate
if dynamicSwapper, is := fromWallet.Wallet.(asset.DynamicSwapper); is {
localMaxFee := dynamicSwapper.GasFeeLimit()
if maxFeeRate > localMaxFee {
return nil, codedError(walletErr, fmt.Errorf("%v: server's max fee rate %v higher than configured fee rate limit %v",
dex.BipIDSymbol(assetConfigs.fromAsset.ID), maxFeeRate, localMaxFee))
}
maxFeeRate = localMaxFee
}

allCoins, allRedeemScripts, fundingFees, err := fromWallet.FundMultiOrder(&asset.MultiOrder{
AssetVersion: assetConfigs.fromAsset.Version,
Values: orderValues,
MaxFeeRate: assetConfigs.fromAsset.MaxFeeRate,
MaxFeeRate: maxFeeRate,
FeeSuggestion: c.feeSuggestion(dc, assetConfigs.fromAsset.ID),
Options: form.Options,
RedeemVersion: assetConfigs.toAsset.Version,
Expand Down Expand Up @@ -6646,7 +6667,7 @@ func (c *Core) prepareMultiTradeRequests(pw []byte, form *MultiTradeForm) ([]*tr
fees = fundingFees
}
req, err := c.createTradeRequest(wallets, coins, allRedeemScripts[i], dc, redeemAddresses[i], tradeForm,
orderValues[i].MaxSwapCount, fees, assetConfigs, mktConf, errClosers[i])
orderValues[i].MaxSwapCount, fees, maxFeeRate, assetConfigs, mktConf, errClosers[i])
if err != nil {
return nil, err
}
Expand Down
150 changes: 150 additions & 0 deletions client/core/core_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10732,9 +10732,47 @@ func (dtfc *TDynamicSwapper) DynamicSwapFeesPaid(ctx context.Context, coinID, co
func (dtfc *TDynamicSwapper) DynamicRedemptionFeesPaid(ctx context.Context, coinID, contractData dex.Bytes) (uint64, [][]byte, error) {
return dtfc.tfpPaid, dtfc.tfpSecretHashes, dtfc.tfpErr
}
func (dtfc *TDynamicSwapper) GasFeeLimit() uint64 {
return 200
}

var _ asset.DynamicSwapper = (*TDynamicSwapper)(nil)

// TDynamicAccountLocker combines TAccountLocker with DynamicSwapper interface
// for testing gas fee limit validation in prepareTradeRequest.
type TDynamicAccountLocker struct {
*TAccountLocker
gasFeeLimit uint64
tfpPaid uint64
tfpSecretHashes [][]byte
tfpErr error
}

func newTDynamicAccountLocker(assetID uint32) (*xcWallet, *TDynamicAccountLocker) {
xcWallet, accountLocker := newTAccountLocker(assetID)
dynamicAccountLocker := &TDynamicAccountLocker{
TAccountLocker: accountLocker,
gasFeeLimit: 200, // default higher than tACCTAsset.MaxFeeRate (20)
}
xcWallet.Wallet = dynamicAccountLocker
return xcWallet, dynamicAccountLocker
}

func (w *TDynamicAccountLocker) DynamicSwapFeesPaid(ctx context.Context, coinID, contractData dex.Bytes) (uint64, [][]byte, error) {
return w.tfpPaid, w.tfpSecretHashes, w.tfpErr
}

func (w *TDynamicAccountLocker) DynamicRedemptionFeesPaid(ctx context.Context, coinID, contractData dex.Bytes) (uint64, [][]byte, error) {
return w.tfpPaid, w.tfpSecretHashes, w.tfpErr
}

func (w *TDynamicAccountLocker) GasFeeLimit() uint64 {
return w.gasFeeLimit
}

var _ asset.DynamicSwapper = (*TDynamicAccountLocker)(nil)
var _ asset.AccountLocker = (*TDynamicAccountLocker)(nil)

func TestUpdateFeesPaid(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
Expand Down Expand Up @@ -10820,6 +10858,118 @@ func TestUpdateFeesPaid(t *testing.T) {
}
}

// TestDynamicSwapperGasFeeLimit tests the gas fee limit validation in
// prepareTradeRequest for DynamicSwapper wallets.
func TestDynamicSwapperGasFeeLimit(t *testing.T) {
rig := newTestRig()
defer rig.shutdown()
tCore := rig.core

// Set up BTC wallet (to wallet when buying BTC with ETH)
btcWallet, _ := newTWallet(tUTXOAssetB.ID)
tCore.wallets[tUTXOAssetB.ID] = btcWallet
btcWallet.address = "12DXGkvxFjuq5btXYkwWfBZaz1rVwFgini"
btcWallet.Unlock(rig.crypter)

// Set up ETH wallet with DynamicSwapper interface (from wallet when buying BTC with ETH)
ethWallet, tEthWallet := newTDynamicAccountLocker(tACCTAsset.ID)
tCore.wallets[tACCTAsset.ID] = ethWallet
ethWallet.address = "18d65fb8d60c1199bb1ad381be47aa692b482605"
ethWallet.Unlock(rig.crypter)

var lots uint64 = 10
qty := dcrBtcLotSize * lots
rate := dcrBtcRateStep * 1000

// Set up ETH as funding coins (ETH is from wallet when Sell=false on btc_eth market)
ethVal := calc.BaseToQuote(rate, qty*2)
ethCoin := &tCoin{
id: encode.RandomBytes(36),
val: ethVal,
}
tEthWallet.fundingCoins = asset.Coins{ethCoin}
tEthWallet.fundRedeemScripts = []dex.Bytes{nil}

book := newBookie(rig.dc, tUTXOAssetB.ID, tACCTAsset.ID, nil, tLogger)
rig.dc.books[tBtcEthMktName] = book

msgOrderNote := &msgjson.BookOrderNote{
OrderNote: msgjson.OrderNote{
OrderID: encode.RandomBytes(32),
},
TradeNote: msgjson.TradeNote{
Side: msgjson.SellOrderNum,
Quantity: dcrBtcLotSize,
Time: uint64(time.Now().Unix()),
Rate: rate,
},
}

err := book.Sync(&msgjson.OrderBook{
MarketID: tBtcEthMktName,
Seq: 1,
Epoch: 1,
Orders: []*msgjson.BookOrderNote{msgOrderNote},
})
if err != nil {
t.Fatalf("order book sync error: %v", err)
}

handleLimit := func(msg *msgjson.Message, f msgFunc) error {
t.Helper()
msgOrder := new(msgjson.LimitOrder)
err := msg.Unmarshal(msgOrder)
if err != nil {
t.Fatalf("unmarshal error: %v", err)
}
lo := convertMsgLimitOrder(msgOrder)
f(orderResponse(msg.ID, msgOrder, lo, false, false, false))
return nil
}

// Form for buying BTC with ETH (ETH is the from/funding wallet with DynamicSwapper)
// Market is btc_eth (Base=BTC, Quote=ETH), Sell=false means buying base (BTC),
// so the from wallet is the quote asset (ETH).
form := &TradeForm{
Host: tDexHost,
IsLimit: true,
Sell: false, // Buying BTC, selling ETH. ETH is from wallet.
Base: tUTXOAssetB.ID,
Quote: tACCTAsset.ID,
Qty: qty,
Rate: rate,
TifNow: false,
}

// Test 1: Gas fee limit higher than server's max fee rate - should succeed
// tACCTAsset.MaxFeeRate = 20, gasFeeLimit = 200
tEthWallet.gasFeeLimit = 200
rig.ws.queueResponse(msgjson.LimitRoute, handleLimit)
_, err = tCore.Trade(tPW, form)
if err != nil {
t.Fatalf("trade with adequate gas fee limit should succeed: %v", err)
}

// Test 2: Gas fee limit lower than server's max fee rate - should fail
// tACCTAsset.MaxFeeRate = 20, gasFeeLimit = 10
tEthWallet.gasFeeLimit = 10
_, err = tCore.Trade(tPW, form)
if err == nil {
t.Fatal("trade with gas fee limit lower than server max fee rate should fail")
}
if !strings.Contains(err.Error(), "higher than configured fee rate limit") {
t.Fatalf("expected gas fee limit error, got: %v", err)
}

// Test 3: Gas fee limit equal to server's max fee rate - should succeed
tEthWallet.gasFeeLimit = tACCTAsset.MaxFeeRate
rig.ws.queueResponse(msgjson.LimitRoute, handleLimit)
_, err = tCore.Trade(tPW, form)
if err != nil {
t.Fatalf("trade with gas fee limit equal to server max fee rate should succeed: %v", err)
}
}

func TestUpdateBondOptions(t *testing.T) {
const feeRate = 50

Expand Down
Loading