decred.org/dcrdex@v1.0.5/client/orderbook/bookside_test.go (about)

     1  package orderbook
     2  
     3  import (
     4  	"bytes"
     5  	"testing"
     6  
     7  	"decred.org/dcrdex/dex/msgjson"
     8  	"decred.org/dcrdex/dex/order"
     9  )
    10  
    11  // makeBookSideDepth creates a new book side depth from the provided
    12  // group and sort order.
    13  func makeBookSide(groups map[uint64][]*Order, rateIndex *rateIndex, orderPref orderPreference) *bookSide {
    14  	return &bookSide{
    15  		bins:      groups,
    16  		rateIndex: rateIndex,
    17  		orderPref: orderPref,
    18  	}
    19  }
    20  
    21  func makeOrder(id order.OrderID, side uint8, quantity uint64, rate uint64, time uint64) *Order {
    22  	return &Order{
    23  		OrderID:  id,
    24  		Side:     side,
    25  		Quantity: quantity,
    26  		Rate:     rate,
    27  		Time:     time,
    28  	}
    29  }
    30  
    31  func TestBookSideAdd(t *testing.T) {
    32  	tests := []struct {
    33  		label    string
    34  		side     *bookSide
    35  		entry    *Order
    36  		expected *bookSide
    37  	}{
    38  		{
    39  			label: "Add order to buy book side",
    40  			side: makeBookSide(
    41  				map[uint64][]*Order{
    42  					1: {
    43  						makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 10, 1, 10),
    44  						makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 5, 1, 10),
    45  					},
    46  					2: {
    47  						makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 2, 2, 10),
    48  						makeOrder([32]byte{'d'}, msgjson.BuyOrderNum, 3, 2, 10),
    49  					},
    50  				},
    51  				makeRateIndex([]uint64{1, 2}),
    52  				ascending,
    53  			),
    54  			entry: makeOrder([32]byte{'e'}, msgjson.BuyOrderNum, 1, 2, 10),
    55  			expected: makeBookSide(
    56  				map[uint64][]*Order{
    57  					1: {
    58  						makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 10, 1, 10),
    59  						makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 5, 1, 10),
    60  					},
    61  					2: {
    62  						makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 2, 2, 10),
    63  						makeOrder([32]byte{'d'}, msgjson.BuyOrderNum, 3, 2, 10),
    64  						makeOrder([32]byte{'e'}, msgjson.BuyOrderNum, 1, 2, 10),
    65  					},
    66  				},
    67  				makeRateIndex([]uint64{1, 2}),
    68  				ascending,
    69  			),
    70  		},
    71  		{
    72  			label: "Add order to new bin of a buy book side",
    73  			side: makeBookSide(
    74  				map[uint64][]*Order{
    75  					1: {
    76  						makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 10, 1, 10),
    77  						makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 5, 1, 10),
    78  					},
    79  				},
    80  				makeRateIndex([]uint64{1}),
    81  				ascending,
    82  			),
    83  			entry: makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 2, 2, 10),
    84  			expected: makeBookSide(
    85  				map[uint64][]*Order{
    86  					1: {
    87  						makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 10, 1, 10),
    88  						makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 5, 1, 10),
    89  					},
    90  					2: {
    91  						makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 2, 2, 10),
    92  					},
    93  				},
    94  				makeRateIndex([]uint64{1, 2}),
    95  				ascending,
    96  			),
    97  		},
    98  		{
    99  			label: "Add order to existing bin of a buy book side",
   100  			side: makeBookSide(
   101  				map[uint64][]*Order{
   102  					1: {
   103  						makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 10, 1, 10),
   104  						makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 5, 1, 10),
   105  					},
   106  				},
   107  				makeRateIndex([]uint64{1}),
   108  				ascending,
   109  			),
   110  			entry: makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 2, 1, 10),
   111  			expected: makeBookSide(
   112  				map[uint64][]*Order{
   113  					1: {
   114  						makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 10, 1, 10),
   115  						makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 5, 1, 10),
   116  						makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 2, 1, 10),
   117  					},
   118  				},
   119  				makeRateIndex([]uint64{1}),
   120  				ascending,
   121  			),
   122  		},
   123  		{
   124  			label: "Add order to existing buy book side bin sorted by order id",
   125  			side: makeBookSide(
   126  				map[uint64][]*Order{
   127  					1: {
   128  						makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 10, 1, 10),
   129  						makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 2, 1, 10),
   130  					},
   131  				},
   132  				makeRateIndex([]uint64{1}),
   133  				ascending,
   134  			),
   135  			entry: makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 5, 1, 10),
   136  			expected: makeBookSide(
   137  				map[uint64][]*Order{
   138  					1: {
   139  						makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 10, 1, 10),
   140  						makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 5, 1, 10),
   141  						makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 2, 1, 10),
   142  					},
   143  				},
   144  				makeRateIndex([]uint64{1}),
   145  				ascending,
   146  			),
   147  		},
   148  		{
   149  			label: "Add order to existing buy book side bin sorted by time",
   150  			side: makeBookSide(
   151  				map[uint64][]*Order{
   152  					1: {
   153  						makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 10, 1, 10),
   154  						makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 2, 1, 10),
   155  					},
   156  				},
   157  				makeRateIndex([]uint64{1}),
   158  				ascending,
   159  			),
   160  			entry: makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 5, 1, 5),
   161  			expected: makeBookSide(
   162  				map[uint64][]*Order{
   163  					1: {
   164  						makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 5, 1, 5),
   165  						makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 10, 1, 10),
   166  						makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 2, 1, 10),
   167  					},
   168  				},
   169  				makeRateIndex([]uint64{1}),
   170  				ascending,
   171  			),
   172  		},
   173  		{
   174  			label: "Add order to sell book side",
   175  			side: makeBookSide(
   176  				map[uint64][]*Order{
   177  					1: {
   178  						makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 10, 1, 10),
   179  					},
   180  				},
   181  				makeRateIndex([]uint64{1}),
   182  				descending,
   183  			),
   184  			entry: makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 5, 1, 10),
   185  			expected: makeBookSide(
   186  				map[uint64][]*Order{
   187  					1: {
   188  						makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 10, 1, 10),
   189  						makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 5, 1, 10),
   190  					},
   191  				},
   192  				makeRateIndex([]uint64{1}),
   193  				descending,
   194  			),
   195  		},
   196  		{
   197  			label: "Add order to sell book side bin sorted by time",
   198  			side: makeBookSide(
   199  				map[uint64][]*Order{
   200  					1: {
   201  						makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 10, 1, 10),
   202  					},
   203  				},
   204  				makeRateIndex([]uint64{1}),
   205  				descending,
   206  			),
   207  			entry: makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 5, 1, 5),
   208  			expected: makeBookSide(
   209  				map[uint64][]*Order{
   210  					1: {
   211  
   212  						makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 5, 1, 5),
   213  						makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 10, 1, 10),
   214  					},
   215  				},
   216  				makeRateIndex([]uint64{1}),
   217  				descending,
   218  			),
   219  		},
   220  	}
   221  
   222  	for idx, tc := range tests {
   223  		tc.side.Add(tc.entry)
   224  		if len(tc.side.bins) != len(tc.expected.bins) {
   225  			t.Fatalf("[BookSide.Add] #%d: expected bin size of %d,"+
   226  				" got %d", idx+1, len(tc.expected.bins), len(tc.side.bins))
   227  		}
   228  
   229  		for price, bin := range tc.side.bins {
   230  			expected := tc.expected.bins[price]
   231  			for i := 0; i < len(bin); i++ {
   232  				if !bytes.Equal(bin[i].OrderID[:], expected[i].OrderID[:]) {
   233  					t.Fatalf("[BookSide.Add] #%d: expected price bin %d "+
   234  						"entry at index %d to have id %x, got %x", idx+1,
   235  						price, idx, expected[i].OrderID[:], bin[i].OrderID[:])
   236  				}
   237  
   238  				if bin[i].Time != expected[i].Time {
   239  					t.Fatalf("[BookSide.Add] #%d: expected price bin %d "+
   240  						"entry at index %d to have timestamp %d, got %d", idx+1,
   241  						price, idx, expected[i].Time, bin[i].Time)
   242  				}
   243  
   244  				if bin[i].Quantity != expected[i].Quantity {
   245  					t.Fatalf("[BookSide.Add] #%d: expected price bin %d "+
   246  						"entry at index %d to have quantity %d, got %d", idx+1,
   247  						price, idx, expected[i].Quantity, bin[i].Quantity)
   248  				}
   249  			}
   250  		}
   251  
   252  		entryBin := tc.side.bins[tc.entry.Rate]
   253  		expectedBin := tc.expected.bins[tc.entry.Rate]
   254  
   255  		if len(entryBin) != len(expectedBin) {
   256  			t.Fatalf("[BookSide.Add] #%d: expected bin size of %d,"+
   257  				" got %d", idx+1, len(expectedBin), len(entryBin))
   258  		}
   259  
   260  		if len(tc.side.rateIndex.Rates) != len(tc.expected.rateIndex.Rates) {
   261  			t.Fatalf("[BookSide.Add] #%d: expected index size of %d,"+
   262  				" got %d", idx+1, len(tc.expected.rateIndex.Rates),
   263  				len(tc.side.rateIndex.Rates))
   264  		}
   265  
   266  		for i := 0; i < len(tc.side.rateIndex.Rates); i++ {
   267  			if tc.side.rateIndex.Rates[i] !=
   268  				tc.expected.rateIndex.Rates[i] {
   269  				t.Fatalf("[BookSide.Add] #%d: expected index "+
   270  					"rate value of %d at index %d, got %d", idx+1,
   271  					tc.expected.rateIndex.Rates[i], i,
   272  					tc.side.rateIndex.Rates[i])
   273  			}
   274  		}
   275  	}
   276  }
   277  
   278  func TestBookSideRemove(t *testing.T) {
   279  	tests := []struct {
   280  		label    string
   281  		side     *bookSide
   282  		entry    *Order
   283  		expected *bookSide
   284  		wantErr  bool
   285  	}{
   286  		{
   287  			label: "Remove order from buy book side",
   288  			side: makeBookSide(
   289  				map[uint64][]*Order{
   290  					1: {
   291  						makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 10, 1, 10),
   292  						makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 5, 1, 10),
   293  					},
   294  					2: {
   295  						makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 2, 2, 10),
   296  						makeOrder([32]byte{'d'}, msgjson.BuyOrderNum, 3, 2, 10),
   297  					},
   298  				},
   299  				makeRateIndex([]uint64{1, 2}),
   300  				ascending,
   301  			),
   302  			entry: makeOrder([32]byte{'d'}, msgjson.BuyOrderNum, 3, 2, 10),
   303  			expected: makeBookSide(
   304  				map[uint64][]*Order{
   305  					1: {
   306  						makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 10, 1, 10),
   307  						makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 5, 1, 10),
   308  					},
   309  					2: {
   310  						makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 2, 2, 10),
   311  					},
   312  				},
   313  				makeRateIndex([]uint64{1, 2}),
   314  				ascending,
   315  			),
   316  			wantErr: false,
   317  		},
   318  		{
   319  			label: "Remove last order from sell book side bin",
   320  			side: makeBookSide(
   321  				map[uint64][]*Order{
   322  					1: {
   323  						makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 10, 1, 2),
   324  						makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 5, 1, 5),
   325  					},
   326  					2: {
   327  						makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 2, 2, 10),
   328  					},
   329  				},
   330  				makeRateIndex([]uint64{1, 2}),
   331  				descending,
   332  			),
   333  			entry: makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 2, 2, 10),
   334  			expected: makeBookSide(
   335  				map[uint64][]*Order{
   336  					1: {
   337  						makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 10, 1, 2),
   338  						makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 5, 1, 5),
   339  					},
   340  				},
   341  				makeRateIndex([]uint64{1}),
   342  				descending,
   343  			),
   344  			wantErr: false,
   345  		},
   346  		{
   347  			label: "Remove non-existing order from buy book side",
   348  			side: makeBookSide(
   349  				map[uint64][]*Order{
   350  					1: {
   351  						makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 10, 1, 10),
   352  						makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 5, 1, 10),
   353  					},
   354  				},
   355  				makeRateIndex([]uint64{1}),
   356  				ascending,
   357  			),
   358  			entry: makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 2, 2, 10),
   359  			expected: makeBookSide(
   360  				map[uint64][]*Order{
   361  					1: {
   362  						makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 10, 1, 10),
   363  						makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 5, 1, 10),
   364  					},
   365  				},
   366  				makeRateIndex([]uint64{1}),
   367  				ascending,
   368  			),
   369  			wantErr: true,
   370  		},
   371  		{
   372  			label: "Remove order from sell book side bin",
   373  			side: makeBookSide(
   374  				map[uint64][]*Order{
   375  					1: {
   376  						makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 10, 1, 2),
   377  						makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 5, 1, 5),
   378  						makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 2, 1, 10),
   379  					},
   380  				},
   381  				makeRateIndex([]uint64{1}),
   382  				descending,
   383  			),
   384  			entry: makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 5, 1, 5),
   385  			expected: makeBookSide(
   386  				map[uint64][]*Order{
   387  					1: {
   388  						makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 10, 1, 2),
   389  						makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 2, 1, 10),
   390  					},
   391  				},
   392  				makeRateIndex([]uint64{1}),
   393  				descending,
   394  			),
   395  			wantErr: false,
   396  		},
   397  	}
   398  
   399  	for idx, tc := range tests {
   400  		ord := tc.entry
   401  		err := tc.side.Remove(ord.OrderID, ord.Rate)
   402  		if (err != nil) != tc.wantErr {
   403  			t.Fatalf("[BookSide.Remove] #%d: error: %v, wantErr: %v",
   404  				idx+1, err, tc.wantErr)
   405  		}
   406  
   407  		if len(tc.side.bins) != len(tc.expected.bins) {
   408  			t.Fatalf("[BookSide.Remove] #%d: expected bin size of %d,"+
   409  				" got %d", idx+1, len(tc.expected.bins),
   410  				len(tc.side.bins))
   411  		}
   412  
   413  		for price, bin := range tc.side.bins {
   414  			expected := tc.expected.bins[price]
   415  			for i := 0; i < len(bin); i++ {
   416  				if !bytes.Equal(bin[i].OrderID[:], expected[i].OrderID[:]) {
   417  					t.Fatalf("[BookSide.Remove] #%d: expected price bin %d "+
   418  						"entry at index %d to have id %x, got %x", idx+1,
   419  						price, idx, expected[i].OrderID[:], bin[i].OrderID[:])
   420  				}
   421  
   422  				if bin[i].Time != expected[i].Time {
   423  					t.Fatalf("[BookSide.Remove] #%d: expected price bin %d "+
   424  						"entry at index %d to have timestamp %d, got %d", idx+1,
   425  						price, idx, expected[i].Time, bin[i].Time)
   426  				}
   427  
   428  				if bin[i].Quantity != expected[i].Quantity {
   429  					t.Fatalf("[BookSide.Remove] #%d: expected price bin %d "+
   430  						"entry at index %d to have quantity %d, got %d", idx+1,
   431  						price, idx, expected[i].Quantity, bin[i].Quantity)
   432  				}
   433  			}
   434  		}
   435  
   436  		entryBin := tc.side.bins[tc.entry.Rate]
   437  		expectedBin := tc.expected.bins[tc.entry.Rate]
   438  
   439  		if len(entryBin) != len(expectedBin) {
   440  			t.Fatalf("[BookSide.Remove] #%d: expected bin size of %d,"+
   441  				" got %d", idx+1, len(expectedBin), len(entryBin))
   442  		}
   443  
   444  		if len(tc.side.rateIndex.Rates) != len(tc.expected.rateIndex.Rates) {
   445  			t.Fatalf("[BookSide.Remove] #%d: expected index size of %d,"+
   446  				" got %d", idx+1, len(tc.expected.rateIndex.Rates),
   447  				len(tc.side.rateIndex.Rates))
   448  		}
   449  
   450  		for i := 0; i < len(tc.side.rateIndex.Rates); i++ {
   451  			if tc.side.rateIndex.Rates[i] !=
   452  				tc.expected.rateIndex.Rates[i] {
   453  				t.Fatalf("[BookSide.Remove] #%d: expected "+
   454  					" index value of %d at index %d, got %d", idx+1,
   455  					tc.expected.rateIndex.Rates[i], i,
   456  					tc.side.rateIndex.Rates[i])
   457  			}
   458  		}
   459  	}
   460  }
   461  
   462  func TestBookSideBestNOrders(t *testing.T) {
   463  	tests := []struct {
   464  		label    string
   465  		side     *bookSide
   466  		n        int
   467  		expected []*Order
   468  	}{
   469  		{
   470  			label: "Fetch best N orders from buy book side sorted in ascending order",
   471  			side: makeBookSide(
   472  				map[uint64][]*Order{
   473  					1: {
   474  						makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 5, 1, 2),
   475  						makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 3, 1, 5),
   476  					},
   477  					2: {
   478  						makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 1, 2, 2),
   479  						makeOrder([32]byte{'d'}, msgjson.BuyOrderNum, 5, 2, 5),
   480  					},
   481  				},
   482  				makeRateIndex([]uint64{1, 2}),
   483  				ascending,
   484  			),
   485  			n: 3,
   486  			expected: []*Order{
   487  				makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 5, 1, 2),
   488  				makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 3, 1, 5),
   489  				makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 1, 2, 2),
   490  			},
   491  		},
   492  		{
   493  			label: "Fetch best N orders from buy book side sorted in descending order",
   494  			side: makeBookSide(
   495  				map[uint64][]*Order{
   496  					1: {
   497  						makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 5, 1, 2),
   498  						makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 3, 1, 5),
   499  					},
   500  					2: {
   501  						makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 1, 2, 2),
   502  						makeOrder([32]byte{'d'}, msgjson.BuyOrderNum, 5, 2, 5),
   503  					},
   504  				},
   505  				makeRateIndex([]uint64{1, 2}),
   506  				descending,
   507  			),
   508  			n: 3,
   509  			expected: []*Order{
   510  				makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 1, 2, 2),
   511  				makeOrder([32]byte{'d'}, msgjson.BuyOrderNum, 5, 2, 5),
   512  				makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 5, 1, 2),
   513  			},
   514  		},
   515  		{
   516  			label: "Fetch best N orders from sell book side sorted in ascending order",
   517  			side: makeBookSide(
   518  				map[uint64][]*Order{
   519  					1: {
   520  						makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 5, 1, 2),
   521  						makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 3, 1, 5),
   522  					},
   523  					2: {
   524  						makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 1, 2, 2),
   525  					},
   526  				},
   527  				makeRateIndex([]uint64{1, 2}),
   528  				ascending,
   529  			),
   530  			n: 10,
   531  			expected: []*Order{
   532  				makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 5, 1, 2),
   533  				makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 3, 1, 5),
   534  				makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 1, 2, 2),
   535  			},
   536  		},
   537  		{
   538  			label: "Fetch best N orders from sell book side sorted in descending order",
   539  			side: makeBookSide(
   540  				map[uint64][]*Order{
   541  					1: {
   542  						makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 5, 1, 2),
   543  						makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 3, 1, 5),
   544  					},
   545  					2: {
   546  						makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 1, 2, 2),
   547  						makeOrder([32]byte{'d'}, msgjson.SellOrderNum, 5, 2, 5),
   548  					},
   549  				},
   550  				makeRateIndex([]uint64{1, 2}),
   551  				descending,
   552  			),
   553  			n: 10,
   554  			expected: []*Order{
   555  				makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 1, 2, 2),
   556  				makeOrder([32]byte{'d'}, msgjson.SellOrderNum, 5, 2, 5),
   557  				makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 5, 1, 2),
   558  				makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 3, 1, 5),
   559  			},
   560  		},
   561  		{
   562  			label: "Fetch best N orders from empty book side sorted in ascending order",
   563  			side: makeBookSide(
   564  				map[uint64][]*Order{},
   565  				makeRateIndex([]uint64{}),
   566  				ascending,
   567  			),
   568  			n:        3,
   569  			expected: []*Order{},
   570  		},
   571  	}
   572  
   573  	for idx, tc := range tests {
   574  		best, _ := tc.side.BestNOrders(tc.n)
   575  		if len(best) != len(tc.expected) {
   576  			t.Fatalf("[BookSide.BestNOrders] #%d: expected best "+
   577  				"order size of %d, got %d", idx+1, len(tc.expected),
   578  				len(best))
   579  		}
   580  
   581  		for i := 0; i < len(best); i++ {
   582  			if best[i].OrderID != tc.expected[i].OrderID {
   583  				t.Fatalf("[BookSide.BestNOrders] #%d: expected "+
   584  					"order id %x at index of %d, got %x", idx+1,
   585  					tc.expected[i].OrderID[:], idx, best[i].OrderID[:])
   586  			}
   587  
   588  			if best[i].Quantity != tc.expected[i].Quantity {
   589  				t.Fatalf("[BookSide.BestNOrders] #%d: expected "+
   590  					"quantity %d at index of %d, got %d", idx+1,
   591  					tc.expected[i].Quantity, idx, best[i].Quantity)
   592  			}
   593  
   594  			if best[i].Time != tc.expected[i].Time {
   595  				t.Fatalf("[BookSide.BestNOrders] #%d: expected "+
   596  					"timestamp %d at index of %d, got %d", idx+1,
   597  					tc.expected[i].Time, idx, best[i].Time)
   598  			}
   599  		}
   600  	}
   601  }
   602  
   603  func TestBookSideBestFill(t *testing.T) {
   604  	tests := []struct {
   605  		label     string
   606  		side      *bookSide
   607  		quantity  uint64
   608  		orderPref orderPreference
   609  		expected  []*Fill
   610  		filled    bool
   611  		marketBuy bool
   612  	}{
   613  		{
   614  			label: "Fetch best fill from buy book side sorted in ascending order",
   615  			side: makeBookSide(
   616  				map[uint64][]*Order{
   617  					1: {
   618  						makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 5, 1, 2),
   619  						makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 3, 1, 5),
   620  					},
   621  					2: {
   622  						makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 1, 2, 2),
   623  						makeOrder([32]byte{'d'}, msgjson.BuyOrderNum, 5, 2, 5),
   624  					},
   625  				},
   626  				makeRateIndex([]uint64{1, 2}),
   627  				ascending,
   628  			),
   629  			quantity: 9,
   630  			expected: []*Fill{
   631  				{
   632  					Rate:     1,
   633  					Quantity: 5,
   634  				},
   635  				{
   636  					Rate:     1,
   637  					Quantity: 3,
   638  				},
   639  				{
   640  					Rate:     2,
   641  					Quantity: 1,
   642  				},
   643  			},
   644  			filled: true,
   645  		},
   646  		{
   647  			label: "Fetch best fill from buy book side sorted in descending order",
   648  			side: makeBookSide(
   649  				map[uint64][]*Order{
   650  					1: {
   651  						makeOrder([32]byte{'a'}, msgjson.BuyOrderNum, 5, 1, 2),
   652  						makeOrder([32]byte{'b'}, msgjson.BuyOrderNum, 3, 1, 5),
   653  					},
   654  					2: {
   655  						makeOrder([32]byte{'c'}, msgjson.BuyOrderNum, 1, 2, 2),
   656  						makeOrder([32]byte{'d'}, msgjson.BuyOrderNum, 5, 2, 5),
   657  					},
   658  				},
   659  				makeRateIndex([]uint64{1, 2}),
   660  				descending,
   661  			),
   662  			quantity: 7,
   663  			expected: []*Fill{
   664  				{
   665  					Rate:     2,
   666  					Quantity: 1,
   667  				},
   668  				{
   669  					Rate:     2,
   670  					Quantity: 5,
   671  				},
   672  				{
   673  					Rate:     1,
   674  					Quantity: 1,
   675  				},
   676  			},
   677  			filled: true,
   678  		},
   679  		{
   680  			label: "Fetch best fill from sell book side sorted in ascending order",
   681  			side: makeBookSide(
   682  				map[uint64][]*Order{
   683  					1: {
   684  						makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 5, 1, 2),
   685  						makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 3, 1, 5),
   686  					},
   687  					2: {
   688  						makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 1, 2, 2),
   689  						makeOrder([32]byte{'d'}, msgjson.SellOrderNum, 5, 2, 5),
   690  					},
   691  				},
   692  				makeRateIndex([]uint64{1, 2}),
   693  				ascending,
   694  			),
   695  			quantity: 0,
   696  			expected: []*Fill{},
   697  			filled:   true,
   698  		},
   699  		{
   700  			label: "Fetch best fill from sell book side sorted in ascending order",
   701  			side: makeBookSide(
   702  				map[uint64][]*Order{
   703  					1: {
   704  						makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 4, 1, 2),
   705  						makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 3, 1, 5),
   706  					},
   707  					2: {
   708  						makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 1, 2, 2),
   709  					},
   710  				},
   711  				makeRateIndex([]uint64{1, 2}),
   712  				ascending,
   713  			),
   714  			quantity: 9,
   715  			expected: []*Fill{
   716  				{
   717  					Rate:     1,
   718  					Quantity: 4,
   719  				},
   720  				{
   721  					Rate:     1,
   722  					Quantity: 3,
   723  				},
   724  				{
   725  					Rate:     2,
   726  					Quantity: 1,
   727  				},
   728  			},
   729  			filled: false,
   730  		},
   731  		{
   732  			label: "Fetch best fill from sell book side sorted in descending order",
   733  			side: makeBookSide(
   734  				map[uint64][]*Order{
   735  					1: {
   736  						makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 5, 1, 2),
   737  						makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 3, 1, 5),
   738  					},
   739  					2: {
   740  						makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 1, 2, 2),
   741  						makeOrder([32]byte{'d'}, msgjson.SellOrderNum, 5, 2, 5),
   742  					},
   743  				},
   744  				makeRateIndex([]uint64{1, 2}),
   745  				descending,
   746  			),
   747  			quantity: 50,
   748  			expected: []*Fill{
   749  				{
   750  					Rate:     2,
   751  					Quantity: 1,
   752  				},
   753  				{
   754  					Rate:     2,
   755  					Quantity: 5,
   756  				},
   757  				{
   758  					Rate:     1,
   759  					Quantity: 5,
   760  				},
   761  				{
   762  					Rate:     1,
   763  					Quantity: 3,
   764  				},
   765  			},
   766  			filled: false,
   767  		},
   768  		{
   769  			label: "Fetch market buy fill",
   770  			side: makeBookSide(
   771  				map[uint64][]*Order{
   772  					2e8: {
   773  						makeOrder([32]byte{'a'}, msgjson.SellOrderNum, 5, 2e8, 2), // 10 Quote asset
   774  						makeOrder([32]byte{'b'}, msgjson.SellOrderNum, 3, 2e8, 5), // 6
   775  					},
   776  					3e8: {
   777  						makeOrder([32]byte{'c'}, msgjson.SellOrderNum, 1, 3e8, 2), // 3
   778  						makeOrder([32]byte{'d'}, msgjson.SellOrderNum, 5, 3e8, 5), // 15
   779  					},
   780  				},
   781  				makeRateIndex([]uint64{2e8, 3e8}),
   782  				ascending,
   783  			),
   784  			quantity: 30, // 16 @ 2e8, 14 @ 3e8
   785  			expected: []*Fill{
   786  				{
   787  					Rate:     2e8,
   788  					Quantity: 5,
   789  				},
   790  				{
   791  					Rate:     2e8,
   792  					Quantity: 3,
   793  				},
   794  				{
   795  					Rate:     3e8,
   796  					Quantity: 1,
   797  				},
   798  				{
   799  					Rate:     3e8,
   800  					Quantity: 3, // 11 remaining only covers 3 units at rate = 3.
   801  				},
   802  			},
   803  			marketBuy: true,
   804  			filled:    true,
   805  		},
   806  	}
   807  
   808  	for _, tc := range tests {
   809  		var best []*Fill
   810  		var filled bool
   811  		if tc.marketBuy {
   812  			best, filled = tc.side.bestFill(tc.quantity, true, 1)
   813  		} else {
   814  			best, filled = tc.side.BestFill(tc.quantity)
   815  		}
   816  
   817  		if filled != tc.filled {
   818  			t.Fatalf("[BookSide.BestFill] %q: wrong fill. wanted %v, got %v", tc.label, tc.filled, filled)
   819  		}
   820  
   821  		if len(best) != len(tc.expected) {
   822  			t.Fatalf("[BookSide.BestFill] %q: expected best "+
   823  				"order size of %d, got %d", tc.label, len(tc.expected),
   824  				len(best))
   825  		}
   826  
   827  		for i := 0; i < len(best); i++ {
   828  			if best[i].Rate != tc.expected[i].Rate {
   829  				t.Fatalf("[BookSide.BestFill] %q: expected fill at "+
   830  					"index %d to have rate %d, got %d", tc.label, i,
   831  					tc.expected[i].Rate, best[i].Rate)
   832  			}
   833  			if best[i].Quantity != tc.expected[i].Quantity {
   834  				t.Fatalf("[BookSide.BestFill] %q: expected fill at "+
   835  					"index %d to have quantity %d, got %d", tc.label, i,
   836  					tc.expected[i].Quantity, best[i].Quantity)
   837  			}
   838  		}
   839  	}
   840  }