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 }