Skip to content

Commit

Permalink
client/mm: Fix fee handling
Browse files Browse the repository at this point in the history
  • Loading branch information
martonp committed Jan 4, 2024
1 parent 50e17de commit 398dca8
Show file tree
Hide file tree
Showing 8 changed files with 841 additions and 376 deletions.
94 changes: 93 additions & 1 deletion client/mm/exchange_adaptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ type orderFees struct {
// a *core.WalletBalance.
type botCoreAdaptor interface {
SyncBook(host string, base, quote uint32) (*orderbook.OrderBook, core.BookFeed, error)
SingleLotFees(form *core.SingleLotFeesForm) (uint64, uint64, uint64, error)
SingleLotFees(form *core.SingleLotFeesForm) (swapFees, redeemFees, refundFees uint64, err error)
Cancel(oidB dex.Bytes) error
MaxBuy(host string, base, quote uint32, rate uint64) (*core.MaxOrderEstimate, error)
MaxSell(host string, base, quote uint32) (*core.MaxOrderEstimate, error)
Expand All @@ -99,6 +99,7 @@ type botCoreAdaptor interface {
CancelAllOrders() bool
FiatRate(assetID uint32) float64
OrderFees() (buyFees, sellFees *orderFees, err error)
OrderFeesInUnits(sell, base bool, rate uint64) (uint64, error)
SubscribeOrderUpdates() (updates <-chan *core.Order)
}

Expand Down Expand Up @@ -1194,6 +1195,97 @@ func (u *unifiedExchangeAdaptor) OrderFees() (buyFees, sellFees *orderFees, err
return u.buyFees, u.sellFees, nil
}

// OrderFeesInUnits returns the swap and redemption fees for either a buy or
// sell order in units of either the base or quote asset. If either the base
// or quote asset is a token, the fees are converted using fiat rates.
// Otherwise, the rate parameter is used for the conversion.
func (u *unifiedExchangeAdaptor) OrderFeesInUnits(sell, base bool, rate uint64) (uint64, error) {
buyFees, sellFees, err := u.OrderFees()
if err != nil {
return 0, fmt.Errorf("error getting order fees: %v", err)
}

var baseFees, quoteFees uint64
if sell {
baseFees += sellFees.swap
quoteFees += sellFees.redemption
} else {
baseFees += buyFees.redemption
quoteFees += buyFees.swap
}

var assetID uint32
if base {
assetID = u.market.BaseID
} else {
assetID = u.market.QuoteID
}

var baseFeesInUnits, quoteFeesInUnits uint64

baseToken := asset.TokenInfo(u.market.BaseID)
quoteToken := asset.TokenInfo(u.market.QuoteID)
if baseToken != nil {
baseParentFiatRate := u.FiatRate(baseToken.ParentID)
if baseParentFiatRate == 0 {
return 0, fmt.Errorf("no fiat rate available for base asset parent %d", baseToken.ParentID)
}
fiatRate := u.FiatRate(assetID)
if fiatRate == 0 {
return 0, fmt.Errorf("no fiat rate available for asset %d", assetID)
}
baseParentUnitInfo, err := asset.UnitInfo(baseToken.ParentID)
if err != nil {
return 0, fmt.Errorf("error getting base asset parent unit info: %v", err)
}
unitInfo, err := asset.UnitInfo(assetID)
if err != nil {
return 0, fmt.Errorf("error getting receiving asset unit info: %v", err)
}

baseFeeConv := float64(baseFees) / float64(baseParentUnitInfo.Conventional.ConversionFactor)
receivingAssetConv := baseFeeConv * baseParentFiatRate / fiatRate
baseFeesInUnits = uint64(receivingAssetConv * float64(unitInfo.Conventional.ConversionFactor))
} else {
if base {
baseFeesInUnits = baseFees
} else {
baseFeesInUnits = calc.BaseToQuote(rate, baseFees)
}
}

if quoteToken != nil {
quoteParentFiatRate := u.FiatRate(quoteToken.ParentID)
if quoteParentFiatRate == 0 {
return 0, fmt.Errorf("no fiat rate available for quote asset parent %d", quoteToken.ParentID)
}
fiatRate := u.FiatRate(assetID)
if fiatRate == 0 {
return 0, fmt.Errorf("no fiat rate available for asset %d", assetID)
}
quoteParentUnitInfo, err := asset.UnitInfo(quoteToken.ParentID)
if err != nil {
return 0, fmt.Errorf("error getting quote asset parent unit info: %v", err)
}
unitInfo, err := asset.UnitInfo(assetID)
if err != nil {
return 0, fmt.Errorf("error getting receiving asset unit info: %v", err)
}

quoteFeeConv := float64(quoteFees) / float64(quoteParentUnitInfo.Conventional.ConversionFactor)
receivingAssetConv := quoteFeeConv * quoteParentFiatRate / fiatRate
quoteFeesInUnits = uint64(receivingAssetConv * float64(unitInfo.Conventional.ConversionFactor))
} else {
if base {
quoteFeesInUnits = calc.QuoteToBase(rate, quoteFees)
} else {
quoteFeesInUnits = quoteFees
}
}

return baseFeesInUnits + quoteFeesInUnits, nil
}

// CancelAllOrders cancels all booked orders. True is returned no orders
// needed to be cancelled.
func (u *unifiedExchangeAdaptor) CancelAllOrders() bool {
Expand Down
143 changes: 143 additions & 0 deletions client/mm/exchange_adaptor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3199,3 +3199,146 @@ func TestGroupedBookedOrders(t *testing.T) {
checkGroupedOrders(groupedBuys, expectedBuyIDs)
checkGroupedOrders(groupedSells, expectedSellIDs)
}

func TestOrderFeesInUnits(t *testing.T) {
type test struct {
name string
buyFees *orderFees
sellFees *orderFees
rate uint64
market *MarketWithHost
fiatRates map[uint32]float64

expectedSellBase uint64
expectedSellQuote uint64
expectedBuyBase uint64
expectedBuyQuote uint64
}

tests := []*test{
/*{
name: "dcr/btc",
market: &MarketWithHost{
BaseID: 42,
QuoteID: 0,
},
buyFees: &orderFees{
swap: 5e5,
redemption: 1.1e4,
},
sellFees: &orderFees{
swap: 1.085e4,
redemption: 4e5,
},
rate: 5e7,
expectedSellBase: 810850,
expectedBuyBase: 1011000,
expectedSellQuote: 405425,
expectedBuyQuote: 505500,
},
{
name: "btc/usdc.eth",
market: &MarketWithHost{
BaseID: 0,
QuoteID: 60001,
},
buyFees: &orderFees{
swap: 1e7,
redemption: 4e4,
},
sellFees: &orderFees{
swap: 5e4,
redemption: 1.1e7,
},
fiatRates: map[uint32]float64{
60001: 0.99,
60: 2300,
0: 42999,
},
rate: calc.MessageRateAlt(43000, 1e8, 1e6),
expectedSellBase: 108838,
expectedBuyBase: 93489,
expectedSellQuote: 47055555,
expectedBuyQuote: 40432323,
},*/
{
name: "wbtc.polygon/usdc.eth",
market: &MarketWithHost{
BaseID: 966003,
QuoteID: 60001,
},
buyFees: &orderFees{
swap: 1e7,
redemption: 4e4,
},
sellFees: &orderFees{
swap: 5e4,
redemption: 1.1e7,
},
fiatRates: map[uint32]float64{
60001: 0.99,
60: 2300,
966003: 42500,
966: 0.8,
},
rate: calc.MessageRateAlt(43000, 1e8, 1e6),
expectedSellBase: 59529,
expectedBuyBase: 54117,
expectedSellQuote: 25555595,
expectedBuyQuote: 23232355,
},
}

runTest := func(tt *test) {
tCore := newTCore()
tCore.fiatRates = tt.fiatRates
tCore.buySwapFees = tt.buyFees.swap
tCore.buyRedeemFees = tt.buyFees.redemption
tCore.sellSwapFees = tt.sellFees.swap
tCore.sellRedeemFees = tt.sellFees.redemption
adaptor := unifiedExchangeAdaptorForBot(&exchangeAdaptorCfg{
core: tCore,
log: tLogger,
market: tt.market,
})
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
adaptor.run(ctx)

sellBase, err := adaptor.OrderFeesInUnits(true, true, tt.rate)
if err != nil {
t.Fatalf("%s: unexpected error: %v", tt.name, err)
}
if sellBase != tt.expectedSellBase {
t.Fatalf("%s: unexpected sell base fee. want %d, got %d", tt.name, tt.expectedSellBase, sellBase)
}

sellQuote, err := adaptor.OrderFeesInUnits(true, false, tt.rate)
if err != nil {
t.Fatalf("%s: unexpected error: %v", tt.name, err)
}
if sellQuote != tt.expectedSellQuote {
t.Fatalf("%s: unexpected sell quote fee. want %d, got %d", tt.name, tt.expectedSellQuote, sellQuote)
}

buyBase, err := adaptor.OrderFeesInUnits(false, true, tt.rate)
if err != nil {
t.Fatalf("%s: unexpected error: %v", tt.name, err)
}
if buyBase != tt.expectedBuyBase {
t.Fatalf("%s: unexpected buy base fee. want %d, got %d", tt.name, tt.expectedBuyBase, buyBase)
}

buyQuote, err := adaptor.OrderFeesInUnits(false, false, tt.rate)
if err != nil {
t.Fatalf("%s: unexpected error: %v", tt.name, err)
}
if buyQuote != tt.expectedBuyQuote {
t.Fatalf("%s: unexpected buy quote fee. want %d, got %d", tt.name, tt.expectedBuyQuote, buyQuote)
}
}

for _, test := range tests {
runTest(test)
}
}
Loading

0 comments on commit 398dca8

Please sign in to comment.