diff --git a/client/asset/eth/eth.go b/client/asset/eth/eth.go index 402b47af59..5cf1517423 100644 --- a/client/asset/eth/eth.go +++ b/client/asset/eth/eth.go @@ -2768,7 +2768,7 @@ func isValidSend(addr string, value uint64, subtract bool) error { // canSend ensures that the wallet has enough to cover send value and returns // the fee rate and max fee required for the send tx. If isPreEstimate is false, // wallet balance must be enough to cover total spend. -func (w *ETHWallet) canSend(value uint64, isPreEstimate bool) (uint64, *big.Int, error) { +func (w *ETHWallet) canSend(value uint64, verifyBalance, isPreEstimate bool) (uint64, *big.Int, error) { maxFeeRate, err := w.recommendedMaxFeeRate(w.ctx) if err != nil { return 0, nil, fmt.Errorf("error getting max fee rate: %w", err) @@ -2776,7 +2776,11 @@ func (w *ETHWallet) canSend(value uint64, isPreEstimate bool) (uint64, *big.Int, maxFee := defaultSendGasLimit * dexeth.WeiToGwei(maxFeeRate) - if !isPreEstimate { + if isPreEstimate { + maxFee = maxFee * 12 / 10 // 20% buffer + } + + if verifyBalance { bal, err := w.Balance() if err != nil { return 0, nil, err @@ -2795,7 +2799,7 @@ func (w *ETHWallet) canSend(value uint64, isPreEstimate bool) (uint64, *big.Int, // canSend ensures that the wallet has enough to cover send value and returns // the fee rate and max fee required for the send tx. -func (w *TokenWallet) canSend(value uint64, isPreEstimate bool) (uint64, *big.Int, error) { +func (w *TokenWallet) canSend(value uint64, verifyBalance, isPreEstimate bool) (uint64, *big.Int, error) { maxFeeRate, err := w.recommendedMaxFeeRate(w.ctx) if err != nil { return 0, nil, fmt.Errorf("error getting max fee rate: %w", err) @@ -2808,7 +2812,11 @@ func (w *TokenWallet) canSend(value uint64, isPreEstimate bool) (uint64, *big.In maxFee := dexeth.WeiToGwei(maxFeeRate) * g.Transfer - if !isPreEstimate { + if isPreEstimate { + maxFee = maxFee * 12 / 10 // 20% buffer + } + + if verifyBalance { bal, err := w.Balance() if err != nil { return 0, nil, err @@ -2839,7 +2847,7 @@ func (w *ETHWallet) EstimateSendTxFee(addr string, value, _ uint64, subtract boo if err := isValidSend(addr, value, subtract); err != nil && addr != "" { // fee estimate for a send tx. return 0, false, err } - maxFee, _, err := w.canSend(value, addr == "") + maxFee, _, err := w.canSend(value, addr != "", true) if err != nil { return 0, false, err } @@ -2854,7 +2862,7 @@ func (w *TokenWallet) EstimateSendTxFee(addr string, value, _ uint64, subtract b if err := isValidSend(addr, value, subtract); err != nil && addr != "" { // fee estimate for a send tx. return 0, false, err } - maxFee, _, err := w.canSend(value, addr == "") + maxFee, _, err := w.canSend(value, addr != "", true) if err != nil { return 0, false, err } @@ -2935,7 +2943,7 @@ func (w *ETHWallet) Send(addr string, value, _ uint64) (asset.Coin, error) { return nil, err } - maxFee, maxFeeRate, err := w.canSend(value, false) + maxFee, maxFeeRate, err := w.canSend(value, true, false) if err != nil { return nil, err } @@ -2961,7 +2969,7 @@ func (w *TokenWallet) Send(addr string, value, _ uint64) (asset.Coin, error) { return nil, err } - maxFee, maxFeeRate, err := w.canSend(value, false) + maxFee, maxFeeRate, err := w.canSend(value, true, false) if err != nil { return nil, err } diff --git a/client/asset/eth/eth_test.go b/client/asset/eth/eth_test.go index e48a1f9243..91e91cb85c 100644 --- a/client/asset/eth/eth_test.go +++ b/client/asset/eth/eth_test.go @@ -4620,6 +4620,55 @@ func TestMarshalMonitoredTx(t *testing.T) { } } +// Ensures that a small rise in the base fee between estimation +// and sending will not cause a failure. +func TestEstimateVsActualSendFees(t *testing.T) { + t.Run("eth", func(t *testing.T) { testEstimateVsActualSendFees(t, BipID) }) + t.Run("token", func(t *testing.T) { testEstimateVsActualSendFees(t, simnetTokenID) }) +} + +func testEstimateVsActualSendFees(t *testing.T, assetID uint32) { + w, _, node, shutdown := tassetWallet(assetID) + defer shutdown() + + tx := tTx(0, 0, 0, &testAddressA, nil) + node.sendTxTx = tx + node.tokenContractor.transferTx = tx + + const testAddr = "dd93b447f7eBCA361805eBe056259853F3912E04" + + txFeeEstimator := w.(asset.TxFeeEstimator) + fee, _, err := txFeeEstimator.EstimateSendTxFee("", 0, 0, false) + if err != nil { + t.Fatalf("error estimating fee: %v", err) + } + + // Increase the base fee by 10%. + node.baseFee = node.baseFee.Mul(node.baseFee, big.NewInt(11)) + node.baseFee = node.baseFee.Div(node.baseFee, big.NewInt(10)) + + if assetID == BipID { + node.bal = dexeth.GweiToWei(11e9) + canSend := new(big.Int).Sub(node.bal, dexeth.GweiToWei(fee)) + canSendGwei, err := dexeth.WeiToGweiUint64(canSend) + if err != nil { + t.Fatalf("error converting canSend to gwei: %v", err) + } + _, err = w.Send(testAddr, canSendGwei, 0) + if err != nil { + t.Fatalf("error sending: %v", err) + } + } else { + tokenVal := uint64(10e9) + node.tokenContractor.bal = dexeth.GweiToWei(tokenVal) + node.bal = dexeth.GweiToWei(fee) + _, err = w.Send(testAddr, tokenVal, 0) + if err != nil { + t.Fatalf("error sending: %v", err) + } + } +} + func TestEstimateSendTxFee(t *testing.T) { t.Run("eth", func(t *testing.T) { testEstimateSendTxFee(t, BipID) }) t.Run("token", func(t *testing.T) { testEstimateSendTxFee(t, simnetTokenID) }) @@ -4633,6 +4682,9 @@ func testEstimateSendTxFee(t *testing.T, assetID uint32) { ethFees := dexeth.WeiToGwei(maxFeeRate) * defaultSendGasLimit tokenFees := dexeth.WeiToGwei(maxFeeRate) * tokenGases.Transfer + ethFees = ethFees * 12 / 10 + tokenFees = tokenFees * 12 / 10 + const testAddr = "dd93b447f7eBCA361805eBe056259853F3912E04" const val = 10e9