decred.org/dcrdex@v1.0.5/server/book/book_test.go (about)

     1  // This code is available on the terms of the project LICENSE.md file,
     2  // also available online at https://blueoakcouncil.org/license/1.0.0.
     3  
     4  package book
     5  
     6  import (
     7  	"testing"
     8  	"time"
     9  
    10  	"decred.org/dcrdex/dex"
    11  	"decred.org/dcrdex/dex/order"
    12  	"decred.org/dcrdex/server/account"
    13  )
    14  
    15  // An arbitrary account ID for test orders.
    16  var acct0 = account.AccountID{
    17  	0x22, 0x4c, 0xba, 0xaa, 0xfa, 0x80, 0xbf, 0x3b, 0xd1, 0xff, 0x73, 0x15,
    18  	0x90, 0xbc, 0xbd, 0xda, 0x5a, 0x76, 0xf9, 0x1e, 0x60, 0xa1, 0x56, 0x99,
    19  	0x46, 0x34, 0xe9, 0x1c, 0xec, 0x25, 0xd5, 0x40,
    20  }
    21  
    22  const (
    23  	AssetDCR uint32 = iota
    24  	AssetBTC
    25  
    26  	LotSize = uint64(10 * 1e8)
    27  )
    28  
    29  func startLogger() {
    30  	logger := dex.StdOutLogger("BOOKTEST", dex.LevelTrace)
    31  	UseLogger(logger)
    32  }
    33  
    34  func newLimitOrder(sell bool, rate, quantityLots uint64, force order.TimeInForce, timeOffset int64) *order.LimitOrder {
    35  	return &order.LimitOrder{
    36  		P: order.Prefix{
    37  			AccountID:  acct0,
    38  			BaseAsset:  AssetDCR,
    39  			QuoteAsset: AssetBTC,
    40  			OrderType:  order.LimitOrderType,
    41  			ClientTime: time.Unix(1566497653+timeOffset, 0),
    42  			ServerTime: time.Unix(1566497656+timeOffset, 0),
    43  		},
    44  		T: order.Trade{
    45  			Coins:    []order.CoinID{[]byte(newFakeAddr())},
    46  			Sell:     sell,
    47  			Quantity: quantityLots * LotSize,
    48  			Address:  newFakeAddr(),
    49  		},
    50  		Rate:  rate,
    51  		Force: force,
    52  	}
    53  }
    54  
    55  var (
    56  	// Create a coherent order book of standing orders and sorted rates.
    57  	bookBuyOrders = []*order.LimitOrder{
    58  		newLimitOrder(false, 2500000, 2, order.StandingTiF, 0),
    59  		newLimitOrder(false, 2700000, 2, order.StandingTiF, 0),
    60  		//newLimitOrder(false, 3200000, 2, order.StandingTiF, 0), // Commented in these tests so buy and sell books are different lengths.
    61  		newLimitOrder(false, 3300000, 1, order.StandingTiF, 2), // newer
    62  		newLimitOrder(false, 3300000, 2, order.StandingTiF, 0), // older
    63  		newLimitOrder(false, 3600000, 4, order.StandingTiF, 0),
    64  		newLimitOrder(false, 3900000, 2, order.StandingTiF, 0),
    65  		newLimitOrder(false, 4000000, 10, order.StandingTiF, 0),
    66  		newLimitOrder(false, 4300000, 4, order.StandingTiF, 1), // newer
    67  		newLimitOrder(false, 4300000, 2, order.StandingTiF, 0), // older
    68  		newLimitOrder(false, 4500000, 1, order.StandingTiF, 0),
    69  	}
    70  	bestBuyOrder   = bookBuyOrders[len(bookBuyOrders)-1]
    71  	bookSellOrders = []*order.LimitOrder{
    72  		newLimitOrder(true, 6200000, 2, order.StandingTiF, 1), // newer
    73  		newLimitOrder(true, 6200000, 2, order.StandingTiF, 0), // older
    74  		newLimitOrder(true, 6100000, 2, order.StandingTiF, 0),
    75  		newLimitOrder(true, 6000000, 2, order.StandingTiF, 0),
    76  		newLimitOrder(true, 5500000, 1, order.StandingTiF, 0),
    77  		newLimitOrder(true, 5400000, 4, order.StandingTiF, 0),
    78  		newLimitOrder(true, 5000000, 2, order.StandingTiF, 0),
    79  		newLimitOrder(true, 4700000, 4, order.StandingTiF, 1),  // newer
    80  		newLimitOrder(true, 4700000, 10, order.StandingTiF, 0), //older
    81  		newLimitOrder(true, 4600000, 2, order.StandingTiF, 0),
    82  		newLimitOrder(true, 4550000, 1, order.StandingTiF, 0),
    83  	}
    84  	bestSellOrder = bookSellOrders[len(bookSellOrders)-1]
    85  )
    86  
    87  func newBook(t *testing.T) *Book {
    88  	resetMakers()
    89  
    90  	b := New(LotSize, AccountTrackingBase|AccountTrackingQuote)
    91  
    92  	for _, o := range bookBuyOrders {
    93  		if ok := b.Insert(o); !ok {
    94  			t.Fatalf("Failed to insert buy order %v", o)
    95  		}
    96  	}
    97  	for _, o := range bookSellOrders {
    98  		if ok := b.Insert(o); !ok {
    99  			t.Fatalf("Failed to insert sell order %v", o)
   100  		}
   101  	}
   102  	return b
   103  }
   104  
   105  func resetMakers() {
   106  	for _, o := range bookBuyOrders {
   107  		o.FillAmt = 0
   108  	}
   109  	for _, o := range bookSellOrders {
   110  		o.FillAmt = 0
   111  	}
   112  }
   113  
   114  func TestBook(t *testing.T) {
   115  	startLogger()
   116  
   117  	b := newBook(t)
   118  	if b.BuyCount() != len(bookBuyOrders) {
   119  		t.Errorf("Incorrect number of buy orders. Got %d, expected %d",
   120  			b.BuyCount(), len(bookBuyOrders))
   121  	}
   122  	if b.SellCount() != len(bookSellOrders) {
   123  		t.Errorf("Incorrect number of sell orders. Got %d, expected %d",
   124  			b.SellCount(), len(bookSellOrders))
   125  	}
   126  
   127  	if b.BestBuy().Sell {
   128  		t.Error("Best buy order was a sell order.")
   129  	}
   130  	if !b.BestSell().Sell {
   131  		t.Error("Best sell order was a buy order.")
   132  	}
   133  
   134  	if b.BestBuy().ID() != bestBuyOrder.ID() {
   135  		t.Errorf("The book returned the wrong best buy order. Got %v, expected %v",
   136  			b.BestBuy().ID(), bestBuyOrder.ID())
   137  	}
   138  	if b.BestSell().ID() != bestSellOrder.ID() {
   139  		t.Errorf("The book returned the wrong best sell order. Got %v, expected %v",
   140  			b.BestSell().ID(), bestSellOrder.ID())
   141  	}
   142  
   143  	sells := b.SellOrders()
   144  	if len(sells) != b.SellCount() {
   145  		t.Errorf("Incorrect number of sell orders. Got %d, expected %d",
   146  			len(sells), b.SellCount())
   147  	}
   148  
   149  	buys := b.BuyOrders()
   150  	if len(buys) != b.BuyCount() {
   151  		t.Errorf("Incorrect number of buy orders. Got %d, expected %d",
   152  			len(buys), b.BuyCount())
   153  	}
   154  
   155  	// Hit the OrderPQ's Realloc function manually.
   156  	b.buys.realloc(initBookHalfCapacity * 2)
   157  	b.sells.realloc(initBookHalfCapacity * 2)
   158  
   159  	buys2 := b.BuyOrders()
   160  	if len(buys) != len(buys2) {
   161  		t.Errorf("Incorrect number of buy orders after realloc. Got %d, expected %d",
   162  			len(buys), len(buys2))
   163  	}
   164  	for i := range buys2 {
   165  		if buys2[i] != buys[i] {
   166  			t.Errorf("Buy order %d mismatch after realloc. Got %s, expected %s",
   167  				i, buys2[i].UID(), buys[i].UID())
   168  		}
   169  	}
   170  
   171  	sells2 := b.SellOrders()
   172  	if len(sells) != len(sells2) {
   173  		t.Errorf("Incorrect number of sell orders after realloc. Got %d, expected %d",
   174  			len(sells), len(sells2))
   175  	}
   176  	for i := range sells2 {
   177  		if sells2[i] != sells[i] {
   178  			t.Errorf("Sell order %d mismatch after realloc. Got %s, expected %s",
   179  				i, sells2[i].UID(), sells[i].UID())
   180  		}
   181  	}
   182  
   183  	badOrder := newLimitOrder(false, 2500000, 1, order.StandingTiF, 0)
   184  	badOrder.Quantity /= 3
   185  	if b.Insert(badOrder) {
   186  		t.Errorf("Inserted order with non-integer multiple of lot size!")
   187  	}
   188  
   189  	removed, ok := b.Remove(order.OrderID{})
   190  	if ok {
   191  		t.Fatalf("Somehow removed order for fake ID. Removed %v", removed.ID())
   192  	}
   193  
   194  	// Remove not the best buy order.
   195  	removed, ok = b.Remove(bookBuyOrders[3].ID())
   196  	if !ok {
   197  		t.Fatalf("Failed to remove existing buy order %v", bookBuyOrders[3].ID())
   198  	}
   199  	if removed.ID() != bookBuyOrders[3].ID() {
   200  		t.Errorf("Failed to remove existing buy order. Got %v, wanted %v",
   201  			removed.ID(), bookBuyOrders[3].ID())
   202  	}
   203  
   204  	if b.BuyCount() != len(bookBuyOrders)-1 {
   205  		t.Errorf("Expected %d book orders, got %d", len(bookBuyOrders)-1, b.BuyCount())
   206  	}
   207  
   208  	if b.SellCount() != len(bookSellOrders) {
   209  		t.Errorf("Expected %d book orders, got %d", len(bookSellOrders), b.BuyCount())
   210  	}
   211  
   212  	// Remove not the best sell order.
   213  	removed, ok = b.Remove(bookSellOrders[2].ID())
   214  	if !ok {
   215  		t.Fatalf("Failed to remove existing buy order %v", bestBuyOrder.ID())
   216  	}
   217  	if removed.ID() != bookSellOrders[2].ID() {
   218  		t.Errorf("Failed to remove existing buy order. Got %v, wanted %v",
   219  			removed.ID(), bookSellOrders[2].ID())
   220  	}
   221  
   222  	if b.BuyCount() != len(bookBuyOrders)-1 {
   223  		t.Errorf("Expected %d book orders, got %d", len(bookBuyOrders)-1, b.BuyCount())
   224  	}
   225  
   226  	if b.SellCount() != len(bookSellOrders)-1 {
   227  		t.Errorf("Expected %d book orders, got %d", len(bookSellOrders)-1, b.BuyCount())
   228  	}
   229  
   230  	removed, ok = b.Remove(bestBuyOrder.ID())
   231  	if !ok {
   232  		t.Fatalf("Failed to remove best buy order %v", bestBuyOrder.ID())
   233  	}
   234  	if removed.ID() != bestBuyOrder.ID() {
   235  		t.Errorf("Failed to remove best buy order. Got %v, wanted %v",
   236  			removed.ID(), bestBuyOrder.ID())
   237  	}
   238  
   239  	removed, ok = b.Remove(bestSellOrder.ID())
   240  	if !ok {
   241  		t.Fatalf("Failed to remove best sell order %v", bestSellOrder.ID())
   242  	}
   243  	if removed.ID() != bestSellOrder.ID() {
   244  		t.Errorf("Failed to remove best sell order. Got %v, wanted %v",
   245  			removed.ID(), bestSellOrder.ID())
   246  	}
   247  
   248  	if b.SellCount() == 0 {
   249  		t.Errorf("sell side was empty")
   250  	}
   251  	if b.BuyCount() == 0 {
   252  		t.Errorf("buy side was empty")
   253  	}
   254  
   255  	buysRemoved, sellsRemoved := b.Clear()
   256  	if len(buysRemoved) != len(bookBuyOrders)-2 {
   257  		t.Errorf("removed %d buys, expected, %d", len(buysRemoved), len(bookBuyOrders)-2)
   258  	}
   259  	if len(sellsRemoved) != len(bookSellOrders)-2 {
   260  		t.Errorf("removed %d sells, expected, %d", len(sellsRemoved), len(bookSellOrders)-2)
   261  	}
   262  
   263  	if b.SellCount() != 0 {
   264  		t.Errorf("sell side was not empty after Clear")
   265  	}
   266  	if b.BuyCount() != 0 {
   267  		t.Errorf("buy side was not empty after Clear")
   268  	}
   269  }
   270  
   271  func TestAccountTracking(t *testing.T) {
   272  	firstSell := bookSellOrders[len(bookSellOrders)-1]
   273  	allOrders := append(bookBuyOrders, bookSellOrders...)
   274  
   275  	// Max oriented queue
   276  	b := newBook(t)
   277  	for _, lo := range allOrders {
   278  		b.Insert(lo)
   279  	}
   280  
   281  	if len(b.acctTracker.base) == 0 {
   282  		t.Fatalf("base asset not tracked")
   283  	}
   284  
   285  	if len(b.acctTracker.quote) == 0 {
   286  		t.Fatalf("quote asset not tracked")
   287  	}
   288  
   289  	// Check each order and make sure it's where we expect.
   290  	for _, ord := range allOrders {
   291  		// they are all buy orders
   292  		baseAccount := ord.BaseAccount()
   293  		ords, found := b.acctTracker.base[baseAccount]
   294  		if !found {
   295  			t.Fatalf("base order account not found")
   296  		}
   297  		_, found = ords[ord.ID()]
   298  		if !found {
   299  			t.Fatalf("base order not found")
   300  		}
   301  
   302  		quoteAccount := ord.QuoteAccount()
   303  		ords, found = b.acctTracker.quote[quoteAccount]
   304  		if !found {
   305  			t.Fatalf("quote order account not found")
   306  		}
   307  		_, found = ords[ord.ID()]
   308  		if !found {
   309  			t.Fatalf("quote order not found")
   310  		}
   311  	}
   312  
   313  	// Check that our first seller has two orders.
   314  	if len(b.acctTracker.base[firstSell.BaseAccount()]) != 2 {
   315  		t.Fatalf("didn't track two base orders for first user")
   316  	}
   317  
   318  	if len(b.acctTracker.quote[firstSell.QuoteAccount()]) != 2 {
   319  		t.Fatalf("didn't track two quote orders for first user")
   320  	}
   321  
   322  	// Remove them all.
   323  	for _, lo := range allOrders {
   324  		b.Remove(lo.ID())
   325  	}
   326  
   327  	if len(b.acctTracker.base) != 0 {
   328  		t.Fatalf("base asset not cleared")
   329  	}
   330  
   331  	if len(b.acctTracker.quote) != 0 {
   332  		t.Fatalf("quote asset not cleared")
   333  	}
   334  }