Skip to content

Commit

Permalink
fix funding calcs on server
Browse files Browse the repository at this point in the history
  • Loading branch information
buck54321 committed Nov 8, 2023
1 parent 1c56049 commit ee423d6
Show file tree
Hide file tree
Showing 12 changed files with 62 additions and 36 deletions.
10 changes: 5 additions & 5 deletions client/asset/btc/btc.go
Original file line number Diff line number Diff line change
Expand Up @@ -1192,8 +1192,8 @@ func newUnconnectedWallet(cfg *BTCCloneCFG, walletCfg *WalletConfig) (*baseWalle
useLegacyBalance: cfg.LegacyBalance,
balanceFunc: cfg.BalanceFunc,
segwit: cfg.Segwit,
initTxSize: uint64(initTxSize),
initTxSizeBase: uint64(initTxSizeBase),
initTxSize: initTxSize,
initTxSizeBase: initTxSizeBase,
signNonSegwit: nonSegwitSigner,
localFeeRate: cfg.FeeEstimator,
externalFeeRate: cfg.ExternalFeeEstimator,
Expand Down Expand Up @@ -4794,8 +4794,8 @@ func (btc *baseWallet) MakeBondTx(ver uint16, amt, feeRate uint64, lockTime time
return nil, nil, fmt.Errorf("error constructing p2sh script: %v", err)
}
txOut := wire.NewTxOut(int64(amt), pkScript)
if dexbtc.IsDust(txOut, feeRate) {
return nil, nil, fmt.Errorf("bond output value of %d is dust", amt)
if btc.IsDust(txOut, feeRate) {
return nil, nil, fmt.Errorf("bond output value of %d (fee rate %d) is dust", amt, feeRate)
}
baseTx.AddTxOut(txOut)

Expand Down Expand Up @@ -4951,7 +4951,7 @@ func (btc *baseWallet) makeBondRefundTxV0(txid *chainhash.Hash, vout uint32, amt
}
redeemTxOut := wire.NewTxOut(int64(amt-fee), redeemPkScript)
if btc.IsDust(redeemTxOut, feeRate) { // hard to imagine
return nil, fmt.Errorf("bond redeem output is dust")
return nil, fmt.Errorf("bond redeem output (amt = %d, feeRate = %d, outputSize = %d) is dust", amt, feeRate, redeemTxOut.SerializeSize())
}
msgTx.AddTxOut(redeemTxOut)

Expand Down
12 changes: 6 additions & 6 deletions client/asset/zec/zec.go
Original file line number Diff line number Diff line change
Expand Up @@ -711,14 +711,15 @@ func (w *zecWallet) FundOrder(ord *asset.Order) (asset.Coins, []dex.Bytes, uint6
spents = []*btc.Output{op}
} else if useSplit {
// No shielded split needed. Should we do a split to avoid overlock.
baggage := dexzec.TxFeesZIP317(inputsSize, 2*dexbtc.P2PKHOutputSize+1, 0, 0, 0, 0)
excess := sum - dexzec.RequiredOrderFunds(ord.Value, 1, dexbtc.RedeemP2PKHInputSize, ord.MaxSwapCount)
if baggage >= excess {
splitTxFees := dexzec.TxFeesZIP317(inputsSize, 2*dexbtc.P2PKHOutputSize+1, 0, 0, 0, 0)
requiredForOrderWithoutSplit := dexzec.RequiredOrderFunds(ord.Value, uint64(len(coins)), inputsSize, ord.MaxSwapCount)
excessWithoutSplit := sum - requiredForOrderWithoutSplit
if splitTxFees >= excessWithoutSplit {
w.log.Debugf("Skipping split transaction because cost is greater than potential over-lock. "+
"%s > %s", btcutil.Amount(baggage), btcutil.Amount(excess))
"%s > %s", btcutil.Amount(splitTxFees), btcutil.Amount(excessWithoutSplit))
} else {
splitOutputVal := dexzec.RequiredOrderFunds(ord.Value, 1, dexbtc.RedeemP2PKHInputSize, ord.MaxSwapCount)
transparentSplitFees = baggage
transparentSplitFees = splitTxFees
baseTx, _, _, err := w.fundedTx(spents)
if err != nil {
return nil, nil, 0, fmt.Errorf("fundedTx error: %w", err)
Expand Down Expand Up @@ -782,7 +783,6 @@ func (w *zecWallet) FundOrder(ord *asset.Order) (asset.Coins, []dex.Bytes, uint6
if err != nil {
return nil, nil, 0, newError(errLockUnspent, "LockUnspent error: %w", err)
}

return coins, redeemScripts, shieldedSplitFees + transparentSplitFees, nil
}

Expand Down
1 change: 0 additions & 1 deletion client/webserver/site/src/js/markets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3291,7 +3291,6 @@ class BalanceWidget {
side.parentBal = balTmpl.bal
}
}

addRow(intl.prep(intl.ID_AVAILABLE), bal.available, asset.unitInfo)
addRow(intl.prep(intl.ID_LOCKED), bal.locked + bal.contractlocked + bal.bondlocked, asset.unitInfo)
addRow(intl.prep(intl.ID_IMMATURE), bal.immature, asset.unitInfo)
Expand Down
6 changes: 3 additions & 3 deletions dex/networks/btc/script.go
Original file line number Diff line number Diff line change
Expand Up @@ -413,12 +413,12 @@ func IsDust(txOut *wire.TxOut, minRelayTxFee uint64) bool {
}

// IsDustVal is like IsDust but only takes the txSize, amount and if segwit.
func IsDustVal(txSize, value, minRelayTxFee uint64, segwit bool) bool {
totalSize := txSize + 41
func IsDustVal(txOutSize, value, minRelayTxFee uint64, segwit bool) bool {
totalSize := txOutSize + 41
if segwit {
// This function is taken from btcd, but noting here that we are not
// rounding up and probably should be.
totalSize += (107 / witnessWeight)
totalSize += (107 / witnessWeight) // + 26
} else {
totalSize += 107
}
Expand Down
4 changes: 2 additions & 2 deletions dex/networks/dcr/script.go
Original file line number Diff line number Diff line change
Expand Up @@ -631,8 +631,8 @@ func IsDust(txOut *wire.TxOut, minRelayTxFee uint64) bool {

// IsDustVal is like IsDust but it only needs the size of the serialized output
// and its amount.
func IsDustVal(sz, amt, minRelayTxFee uint64) bool {
totalSize := sz + 165
func IsDustVal(txOutSize, amt, minRelayTxFee uint64) bool {
totalSize := txOutSize + 165
return amt/(3*totalSize) < minRelayTxFee
}

Expand Down
13 changes: 6 additions & 7 deletions dex/testing/dcrdex/harness.sh
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,11 @@ if [[ -n ${NODERELAY} ]]; then
DCR_CONFIG_PATH="${RELAY_CONF_PATH}"
fi

# For BTC, the min bond size that avoids the dust filter is fee_rate * 330
# To avoid the refund tx output being dust, add fee_rate * 118
# So the total min bond size is = fee_rate * 448
# Using maxFeeRate of 100, this means min bond size of 44800.

cat << EOF >> "./markets.json"
}
],
Expand All @@ -311,9 +316,6 @@ cat << EOF >> "./markets.json"
"maxFeeRate": 10,
"swapConf": 1,
"configPath": "${DCR_CONFIG_PATH}",
"regConfs": 1,
"regFee": 100000000,
"regXPub": "spubVWKGn9TGzyo7M4b5xubB5UV4joZ5HBMNBmMyGvYEaoZMkSxVG4opckpmQ26E85iHg8KQxrSVTdex56biddqtXBerG9xMN8Dvb3eNQVFFwpE",
"bondAmt": 50000000,
"bondConfs": 1,
"nodeRelayID": "${DCR_NODERELAY_ID}"
Expand All @@ -324,10 +326,7 @@ cat << EOF >> "./markets.json"
"maxFeeRate": 100,
"swapConf": 1,
"configPath": "${BTC_CONFIG_PATH}",
"regConfs": 2,
"regFee": 20000000,
"regXPub": "vpub5SLqN2bLY4WeZJ9SmNJHsyzqVKreTXD4ZnPC22MugDNcjhKX5xNX9QiQWcE4SSRzVWyHWUihpKRT7hckDGNzVc69wSX2JPcfGeNiT5c2XZy",
"bondAmt": 10000,
"bondAmt": 50000,
"bondConfs": 1,
"nodeRelayID": "${BTC_NODERELAY_ID}"
EOF
Expand Down
6 changes: 6 additions & 0 deletions server/asset/btc/btc.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"time"

"decred.org/dcrdex/dex"
"decred.org/dcrdex/dex/calc"
"decred.org/dcrdex/dex/config"
dexbtc "decred.org/dcrdex/dex/networks/btc"
"decred.org/dcrdex/server/account"
Expand Down Expand Up @@ -514,6 +515,11 @@ func (btc *Backend) FundingCoin(_ context.Context, coinID []byte, redeemScript [
return utxo, nil
}

func (*Backend) ValidateOrderFunding(swapVal, valSum, _, inputsSize, maxSwaps uint64, nfo *dex.Asset) bool {
reqVal := calc.RequiredOrderFunds(swapVal, inputsSize, maxSwaps, nfo)
return valSum >= reqVal
}

// ValidateCoinID attempts to decode the coinID.
func (btc *Backend) ValidateCoinID(coinID []byte) (string, error) {
txid, vout, err := decodeCoinID(coinID)
Expand Down
2 changes: 2 additions & 0 deletions server/asset/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ type OutputTracker interface {
// with non-standard pkScripts or scripts that require zero signatures to
// redeem must return an error.
FundingCoin(ctx context.Context, coinID []byte, redeemScript []byte) (FundingCoin, error)
// ValidateOrderFunding validates that the supplied utxos are enough to fund an order.
ValidateOrderFunding(swapVal, valSum, inputCount, inputsSize, maxSwaps uint64, nfo *dex.Asset) bool
}

// AccountBalancer is implemented by backends for account-based blockchains.
Expand Down
6 changes: 6 additions & 0 deletions server/asset/dcr/dcr.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"time"

"decred.org/dcrdex/dex"
"decred.org/dcrdex/dex/calc"
dexdcr "decred.org/dcrdex/dex/networks/dcr"
"decred.org/dcrdex/server/account"
"decred.org/dcrdex/server/asset"
Expand Down Expand Up @@ -575,6 +576,11 @@ func ValidateXPub(xpub string) error {
return nil
}

func (*Backend) ValidateOrderFunding(swapVal, valSum, _, inputsSize, maxSwaps uint64, nfo *dex.Asset) bool {
reqVal := calc.RequiredOrderFunds(swapVal, inputsSize, maxSwaps, nfo)
return valSum >= reqVal
}

// ValidateCoinID attempts to decode the coinID.
func (dcr *Backend) ValidateCoinID(coinID []byte) (string, error) {
txid, vout, err := decodeCoinID(coinID)
Expand Down
5 changes: 5 additions & 0 deletions server/asset/zec/zec.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,11 @@ func (be *ZECBackend) FeeRate(context.Context) (uint64, error) {
return dexzec.LegacyFeeRate, nil
}

func (*ZECBackend) ValidateOrderFunding(swapVal, valSum, inputCount, inputsSize, maxSwaps uint64, _ *dex.Asset) bool {
reqVal := dexzec.RequiredOrderFunds(swapVal, inputCount, inputsSize, maxSwaps)
return valSum >= reqVal
}

func (be *ZECBackend) ValidateFeeRate(ci asset.Coin, reqFeeRate uint64) bool {
c, is := ci.(interface {
Fees() uint64
Expand Down
26 changes: 14 additions & 12 deletions server/market/orderrouter.go
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,11 @@ func (r *OrderRouter) processTrade(oRecord *orderRecord, tunnel MarketTunnel, as

// Funding coins are from a utxo-based asset. Need to find them.

funder, is := assets.funding.Backend.(asset.OutputTracker)
if !is {
return msgjson.NewError(msgjson.RPCInternal, "internal error")
}

// Validate coin IDs and prepare some strings for debug logging.
coinStrs := make([]string, 0, len(coins))
for _, coinID := range trade.Coins {
Expand Down Expand Up @@ -563,13 +568,12 @@ func (r *OrderRouter) processTrade(oRecord *orderRecord, tunnel MarketTunnel, as
}

// Calculate the fees and check that the utxo sum is enough.
var reqVal uint64
var swapVal uint64
if sell {
reqVal = calc.RequiredOrderFunds(trade.Quantity, uint64(spendSize), lots, &fundingAsset.Asset)
swapVal = trade.Quantity
} else {
if rate > 0 { // limit buy
quoteQty := calc.BaseToQuote(rate, trade.Quantity)
reqVal = calc.RequiredOrderFunds(quoteQty, uint64(spendSize), lots, &assets.quote.Asset)
swapVal = calc.BaseToQuote(rate, trade.Quantity)
} else {
// This is a market buy order, so the quantity gets special handling.
// 1. The quantity is in units of the quote asset.
Expand All @@ -580,18 +584,16 @@ func (r *OrderRouter) processTrade(oRecord *orderRecord, tunnel MarketTunnel, as
}
buyBuffer := tunnel.MarketBuyBuffer()
lotWithBuffer := uint64(float64(lotSize) * buyBuffer)
minReq := matcher.BaseToQuote(midGap, lotWithBuffer)
if trade.Quantity < minReq {
errStr := fmt.Sprintf("order quantity does not satisfy market buy buffer. %d < %d. midGap = %d", trade.Quantity, minReq, midGap)
swapVal = matcher.BaseToQuote(midGap, lotWithBuffer)
if trade.Quantity < swapVal {
errStr := fmt.Sprintf("order quantity does not satisfy market buy buffer. %d < %d. midGap = %d", trade.Quantity, swapVal, midGap)
return false, msgjson.NewError(msgjson.FundingError, errStr)
}
reqVal = calc.RequiredOrderFunds(minReq, uint64(spendSize), 1, &assets.quote.Asset)
}

}
if valSum < reqVal {
return false, msgjson.NewError(msgjson.FundingError,
fmt.Sprintf("not enough funds. need at least %d, got %d", reqVal, valSum))

if !funder.ValidateOrderFunding(swapVal, valSum, uint64(len(trade.Coins)), uint64(spendSize), lots, &assets.funding.Asset) {
return false, msgjson.NewError(msgjson.FundingError, "failed funding validation")
}

return false, nil
Expand Down
7 changes: 7 additions & 0 deletions server/market/routers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ type TBackend struct {
syncedErr error
confsMinus2 int64
invalidFeeRate bool
unfunded bool
}

func tNewUTXOBackend() *tUTXOBackend {
Expand Down Expand Up @@ -488,6 +489,10 @@ func (b *tUTXOBackend) VerifyUnspentCoin(_ context.Context, coinID []byte) error
return err
}

func (b *tUTXOBackend) ValidateOrderFunding(swapVal, valSum, inputCount, inputsSize, maxSwaps uint64, nfo *dex.Asset) bool {
return !b.unfunded
}

type tAccountBackend struct {
*TBackend
bal uint64
Expand Down Expand Up @@ -1434,7 +1439,9 @@ func testPrefixTrade(prefix *msgjson.Prefix, trade *msgjson.Trade, fundingAsset,

// Not enough funding
trade.Coins = ogUTXOs[:1]
fundingAsset.unfunded = true
checkCode("unfunded", msgjson.FundingError)
fundingAsset.unfunded = false
trade.Coins = ogUTXOs

// Invalid address
Expand Down

0 comments on commit ee423d6

Please sign in to comment.