Skip to content

Commit

Permalink
client/eth: Add send tx fee estiamte buffer
Browse files Browse the repository at this point in the history
This adds a 20% buffer to the send tx fee estimate in order to not return
an error from the send function when the base fee rises between the time
the estimate is done and the ETH is sent.
  • Loading branch information
martonp committed Mar 21, 2023
1 parent 3619377 commit b7fdb95
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 8 deletions.
24 changes: 16 additions & 8 deletions client/asset/eth/eth.go
Expand Up @@ -2768,15 +2768,19 @@ 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)
}

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
Expand All @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down
52 changes: 52 additions & 0 deletions client/asset/eth/eth_test.go
Expand Up @@ -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) })
Expand All @@ -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
Expand Down

0 comments on commit b7fdb95

Please sign in to comment.