decred.org/dcrdex@v1.0.5/server/matcher/match_test.go (about)

     1  package matcher
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/hex"
     6  	"fmt"
     7  	"math/rand"
     8  	"reflect"
     9  	"testing"
    10  	"time"
    11  
    12  	"decred.org/dcrdex/dex"
    13  	"decred.org/dcrdex/dex/order"
    14  	"decred.org/dcrdex/server/account"
    15  )
    16  
    17  // An arbitrary account ID for test orders.
    18  var acct0 = account.AccountID{
    19  	0x22, 0x4c, 0xba, 0xaa, 0xfa, 0x80, 0xbf, 0x3b,
    20  	0xd1, 0xff, 0x73, 0x15, 0x90, 0xbc, 0xbd, 0xda,
    21  	0x5a, 0x76, 0xf9, 0x1e, 0x60, 0xa1, 0x56, 0x99,
    22  	0x46, 0x34, 0xe9, 0x1c, 0xec, 0x25, 0xd5, 0x40, // 32 bytes
    23  }
    24  
    25  const (
    26  	AssetDCR uint32 = iota
    27  	AssetBTC
    28  
    29  	LotSize = uint64(10 * 1e8)
    30  )
    31  
    32  var rnd = rand.New(rand.NewSource(1))
    33  
    34  func randomPreimage() (pe order.Preimage) {
    35  	rnd.Read(pe[:])
    36  	return
    37  }
    38  
    39  func startLogger() {
    40  	logger := dex.StdOutLogger("MATCHTEST", dex.LevelTrace)
    41  	UseLogger(logger)
    42  }
    43  
    44  var (
    45  	marketPreimages = []order.Preimage{
    46  		randomPreimage(),
    47  		randomPreimage(),
    48  	}
    49  	marketOrders = []*OrderRevealed{
    50  		{ // market BUY of 4 lots
    51  			&order.MarketOrder{
    52  				P: order.Prefix{
    53  					AccountID:  acct0,
    54  					BaseAsset:  AssetDCR,
    55  					QuoteAsset: AssetBTC,
    56  					OrderType:  order.MarketOrderType,
    57  					ClientTime: time.Unix(1566497653, 0),
    58  					ServerTime: time.Unix(1566497656, 0),
    59  					Commit:     marketPreimages[0].Commit(),
    60  				},
    61  				T: order.Trade{
    62  					Coins:    []order.CoinID{},
    63  					Sell:     false,
    64  					Quantity: 4 * LotSize,
    65  					Address:  "DcqXswjTPnUcd4FRCkX4vRJxmVtfgGVa5ui",
    66  				},
    67  			},
    68  			marketPreimages[0],
    69  		},
    70  		{ // market SELL of 2 lots
    71  			&order.MarketOrder{
    72  				P: order.Prefix{
    73  					AccountID:  acct0,
    74  					BaseAsset:  AssetDCR,
    75  					QuoteAsset: AssetBTC,
    76  					OrderType:  order.MarketOrderType,
    77  					ClientTime: time.Unix(1566497654, 0),
    78  					ServerTime: time.Unix(1566497656, 0),
    79  					Commit:     marketPreimages[1].Commit(),
    80  				},
    81  				T: order.Trade{
    82  					Coins:    []order.CoinID{},
    83  					Sell:     true,
    84  					Quantity: 2 * LotSize,
    85  					Address:  "149RQGLaHf2gGiL4NXZdH7aA8nYEuLLrgm",
    86  				},
    87  			},
    88  			marketPreimages[1],
    89  		},
    90  	}
    91  
    92  	limitPreimages = []order.Preimage{
    93  		randomPreimage(),
    94  		randomPreimage(),
    95  		randomPreimage(),
    96  		randomPreimage(),
    97  	}
    98  	limitOrders = []*OrderRevealed{
    99  		{ // limit BUY of 2 lots at 0.043
   100  			&order.LimitOrder{
   101  				P: order.Prefix{
   102  					AccountID:  acct0,
   103  					BaseAsset:  AssetDCR,
   104  					QuoteAsset: AssetBTC,
   105  					OrderType:  order.LimitOrderType,
   106  					ClientTime: time.Unix(1566497653, 0),
   107  					ServerTime: time.Unix(1566497656, 0),
   108  					Commit:     limitPreimages[0].Commit(),
   109  				},
   110  				T: order.Trade{
   111  					Coins:    []order.CoinID{},
   112  					Sell:     false,
   113  					Quantity: 2 * LotSize,
   114  					Address:  "DcqXswjTPnUcd4FRCkX4vRJxmVtfgGVa5ui",
   115  				},
   116  				Rate:  4300000,
   117  				Force: order.StandingTiF,
   118  			},
   119  			limitPreimages[0],
   120  		},
   121  		{ // limit SELL of 3 lots at 0.045
   122  			&order.LimitOrder{
   123  				P: order.Prefix{
   124  					AccountID:  acct0,
   125  					BaseAsset:  AssetDCR,
   126  					QuoteAsset: AssetBTC,
   127  					OrderType:  order.LimitOrderType,
   128  					ClientTime: time.Unix(1566497651, 0),
   129  					ServerTime: time.Unix(1566497652, 0),
   130  					Commit:     limitPreimages[1].Commit(),
   131  				},
   132  				T: order.Trade{
   133  					Coins:    []order.CoinID{},
   134  					Sell:     true,
   135  					Quantity: 3 * LotSize,
   136  					Address:  "149RQGLaHf2gGiL4NXZdH7aA8nYEuLLrgm",
   137  				},
   138  				Rate:  4500000,
   139  				Force: order.StandingTiF,
   140  			},
   141  			limitPreimages[1],
   142  		},
   143  		{ // limit BUY of 1 lot at 0.046
   144  			&order.LimitOrder{
   145  				P: order.Prefix{
   146  					AccountID:  acct0,
   147  					BaseAsset:  AssetDCR,
   148  					QuoteAsset: AssetBTC,
   149  					OrderType:  order.LimitOrderType,
   150  					ClientTime: time.Unix(1566497655, 0),
   151  					ServerTime: time.Unix(1566497656, 0),
   152  					Commit:     limitPreimages[2].Commit(),
   153  				},
   154  				T: order.Trade{
   155  					Coins:    []order.CoinID{},
   156  					Sell:     false,
   157  					Quantity: 1 * LotSize,
   158  					Address:  "DcqXswjTPnUcd4FRCkX4vRJxmVtfgGVa5ui",
   159  				},
   160  				Rate:  4600000,
   161  				Force: order.StandingTiF,
   162  			},
   163  			limitPreimages[2],
   164  		},
   165  		{ // limit BUY of 1 lot at 0.045
   166  			&order.LimitOrder{
   167  				P: order.Prefix{
   168  					AccountID:  acct0,
   169  					BaseAsset:  AssetDCR,
   170  					QuoteAsset: AssetBTC,
   171  					OrderType:  order.LimitOrderType,
   172  					ClientTime: time.Unix(1566497649, 0),
   173  					ServerTime: time.Unix(1566497651, 0),
   174  					Commit:     limitPreimages[3].Commit(),
   175  				},
   176  				T: order.Trade{
   177  					Coins:    []order.CoinID{},
   178  					Sell:     false,
   179  					Quantity: 1 * LotSize,
   180  					Address:  "DcqXswjTPnUcd4FRCkX4vRJxmVtfgGVa5ui",
   181  				},
   182  				Rate:  4500000,
   183  				Force: order.StandingTiF,
   184  			},
   185  			limitPreimages[3],
   186  		},
   187  	}
   188  )
   189  
   190  type BookStub struct {
   191  	lotSize    uint64
   192  	sellOrders []*order.LimitOrder // sorted descending in this stub
   193  	buyOrders  []*order.LimitOrder // sorted ascending
   194  }
   195  
   196  func (b *BookStub) LotSize() uint64 {
   197  	return b.lotSize
   198  }
   199  
   200  func (b *BookStub) BestSell() *order.LimitOrder {
   201  	if len(b.sellOrders) == 0 {
   202  		return nil
   203  	}
   204  	return b.sellOrders[len(b.sellOrders)-1]
   205  }
   206  
   207  func (b *BookStub) BestBuy() *order.LimitOrder {
   208  	if len(b.buyOrders) == 0 {
   209  		return nil
   210  	}
   211  	return b.buyOrders[len(b.buyOrders)-1]
   212  }
   213  
   214  func (b *BookStub) SellCount() int {
   215  	return len(b.sellOrders)
   216  }
   217  
   218  func (b *BookStub) BuyCount() int {
   219  	return len(b.buyOrders)
   220  }
   221  
   222  func (b *BookStub) Insert(ord *order.LimitOrder) bool {
   223  	// Only "inserts" by making it the best order.
   224  	if ord.Sell {
   225  		b.sellOrders = append(b.sellOrders, ord)
   226  	} else {
   227  		b.buyOrders = append(b.buyOrders, ord)
   228  	}
   229  	return true
   230  }
   231  
   232  func (b *BookStub) Remove(orderID order.OrderID) (*order.LimitOrder, bool) {
   233  	for i := range b.buyOrders {
   234  		if b.buyOrders[i].ID() == orderID {
   235  			//fmt.Println("Removing", orderID)
   236  			removed := b.buyOrders[i]
   237  			b.buyOrders = append(b.buyOrders[:i], b.buyOrders[i+1:]...)
   238  			return removed, true
   239  		}
   240  	}
   241  	for i := range b.sellOrders {
   242  		if b.sellOrders[i].ID() == orderID {
   243  			//fmt.Println("Removing", orderID)
   244  			removed := b.sellOrders[i]
   245  			b.sellOrders = append(b.sellOrders[:i], b.sellOrders[i+1:]...)
   246  			return removed, true
   247  		}
   248  	}
   249  	return nil, false
   250  }
   251  
   252  func (b *BookStub) BuyOrders() []*order.LimitOrder  { return b.buyOrders }
   253  func (b *BookStub) SellOrders() []*order.LimitOrder { return b.sellOrders }
   254  
   255  var _ Booker = (*BookStub)(nil)
   256  
   257  func newLimitOrder(sell bool, rate, quantityLots uint64, force order.TimeInForce, timeOffset int64) *order.LimitOrder {
   258  	return newLimit(sell, rate, quantityLots, force, timeOffset).Order.(*order.LimitOrder)
   259  }
   260  
   261  func newLimit(sell bool, rate, quantityLots uint64, force order.TimeInForce, timeOffset int64) *OrderRevealed {
   262  	addr := "DcqXswjTPnUcd4FRCkX4vRJxmVtfgGVa5ui"
   263  	if sell {
   264  		addr = "149RQGLaHf2gGiL4NXZdH7aA8nYEuLLrgm"
   265  	}
   266  	pe := randomPreimage()
   267  	return &OrderRevealed{
   268  		&order.LimitOrder{
   269  			P: order.Prefix{
   270  				AccountID:  acct0,
   271  				BaseAsset:  AssetDCR,
   272  				QuoteAsset: AssetBTC,
   273  				OrderType:  order.LimitOrderType,
   274  				ClientTime: time.Unix(1566497653+timeOffset, 0),
   275  				ServerTime: time.Unix(1566497656+timeOffset, 0),
   276  				Commit:     pe.Commit(),
   277  			},
   278  			T: order.Trade{
   279  				Coins:    []order.CoinID{},
   280  				Sell:     sell,
   281  				Quantity: quantityLots * LotSize,
   282  				Address:  addr,
   283  			},
   284  			Rate:  rate,
   285  			Force: force,
   286  		},
   287  		pe,
   288  	}
   289  }
   290  
   291  func newMarketSellOrder(quantityLots uint64, timeOffset int64) *OrderRevealed {
   292  	pe := randomPreimage()
   293  	return &OrderRevealed{
   294  		&order.MarketOrder{
   295  			P: order.Prefix{
   296  				AccountID:  acct0,
   297  				BaseAsset:  AssetDCR,
   298  				QuoteAsset: AssetBTC,
   299  				OrderType:  order.MarketOrderType,
   300  				ClientTime: time.Unix(1566497653+timeOffset, 0),
   301  				ServerTime: time.Unix(1566497656+timeOffset, 0),
   302  				Commit:     pe.Commit(),
   303  			},
   304  			T: order.Trade{
   305  				Coins:    []order.CoinID{},
   306  				Sell:     true,
   307  				Quantity: quantityLots * LotSize,
   308  				Address:  "149RQGLaHf2gGiL4NXZdH7aA8nYEuLLrgm",
   309  			},
   310  		},
   311  		pe,
   312  	}
   313  }
   314  
   315  func newMarketBuyOrder(quantityQuoteAsset uint64, timeOffset int64) *OrderRevealed {
   316  	pe := randomPreimage()
   317  	return &OrderRevealed{
   318  		&order.MarketOrder{
   319  			P: order.Prefix{
   320  				AccountID:  acct0,
   321  				BaseAsset:  AssetDCR,
   322  				QuoteAsset: AssetBTC,
   323  				OrderType:  order.MarketOrderType,
   324  				ClientTime: time.Unix(1566497653+timeOffset, 0),
   325  				ServerTime: time.Unix(1566497656+timeOffset, 0),
   326  				Commit:     pe.Commit(),
   327  			},
   328  			T: order.Trade{
   329  				Coins:    []order.CoinID{},
   330  				Sell:     false,
   331  				Quantity: quantityQuoteAsset,
   332  				Address:  "DcqXswjTPnUcd4FRCkX4vRJxmVtfgGVa5ui",
   333  			},
   334  		},
   335  		pe,
   336  	}
   337  }
   338  
   339  var (
   340  	// Create a coherent order book of standing orders and sorted rates.
   341  	bookBuyOrders = []*order.LimitOrder{
   342  		newLimitOrder(false, 2500000, 2, order.StandingTiF, 0),
   343  		newLimitOrder(false, 2700000, 2, order.StandingTiF, 0),
   344  		newLimitOrder(false, 3200000, 2, order.StandingTiF, 0),
   345  		newLimitOrder(false, 3300000, 1, order.StandingTiF, 2), // newer
   346  		newLimitOrder(false, 3300000, 2, order.StandingTiF, 0), // older
   347  		newLimitOrder(false, 3600000, 4, order.StandingTiF, 0),
   348  		newLimitOrder(false, 3900000, 2, order.StandingTiF, 0),
   349  		newLimitOrder(false, 4000000, 10, order.StandingTiF, 0),
   350  		newLimitOrder(false, 4300000, 4, order.StandingTiF, 1), // newer
   351  		newLimitOrder(false, 4300000, 2, order.StandingTiF, 0), // older
   352  		newLimitOrder(false, 4500000, 1, order.StandingTiF, 0),
   353  	}
   354  	bookSellOrders = []*order.LimitOrder{
   355  		newLimitOrder(true, 6200000, 2, order.StandingTiF, 1), // newer
   356  		newLimitOrder(true, 6200000, 2, order.StandingTiF, 0), // older
   357  		newLimitOrder(true, 6100000, 2, order.StandingTiF, 0),
   358  		newLimitOrder(true, 6000000, 2, order.StandingTiF, 0),
   359  		newLimitOrder(true, 5500000, 1, order.StandingTiF, 0),
   360  		newLimitOrder(true, 5400000, 4, order.StandingTiF, 0),
   361  		newLimitOrder(true, 5000000, 2, order.StandingTiF, 0),
   362  		newLimitOrder(true, 4700000, 4, order.StandingTiF, 1),  // newer
   363  		newLimitOrder(true, 4700000, 10, order.StandingTiF, 0), // older
   364  		newLimitOrder(true, 4600000, 2, order.StandingTiF, 0),
   365  		newLimitOrder(true, 4550000, 1, order.StandingTiF, 0),
   366  	}
   367  )
   368  
   369  const (
   370  	bookBuyLots   = 32
   371  	bookSellLots  = 32
   372  	initialMidGap = (4550000 + 4500000) / 2 // 4525000
   373  )
   374  
   375  func newBooker() Booker {
   376  	resetMakers()
   377  	buyOrders := make([]*order.LimitOrder, len(bookBuyOrders))
   378  	copy(buyOrders, bookBuyOrders)
   379  	sellOrders := make([]*order.LimitOrder, len(bookSellOrders))
   380  	copy(sellOrders, bookSellOrders)
   381  	return &BookStub{
   382  		lotSize:    LotSize,
   383  		buyOrders:  buyOrders,
   384  		sellOrders: sellOrders,
   385  	}
   386  }
   387  
   388  func resetMakers() {
   389  	for _, o := range bookBuyOrders {
   390  		o.FillAmt = 0
   391  	}
   392  	for _, o := range bookSellOrders {
   393  		o.FillAmt = 0
   394  	}
   395  }
   396  
   397  func newMatchSet(taker order.Order, makers []*order.LimitOrder, lastPartialAmount ...uint64) *order.MatchSet {
   398  	amounts := make([]uint64, len(makers))
   399  	rates := make([]uint64, len(makers))
   400  	var total uint64
   401  	for i := range makers {
   402  		total += makers[i].Quantity
   403  		amounts[i] = makers[i].Quantity
   404  		rates[i] = makers[i].Rate
   405  	}
   406  	if len(lastPartialAmount) > 0 {
   407  		amounts[len(makers)-1] = lastPartialAmount[0]
   408  		total -= makers[len(makers)-1].Quantity - lastPartialAmount[0]
   409  	}
   410  	return &order.MatchSet{
   411  		Taker:   taker,
   412  		Makers:  makers,
   413  		Amounts: amounts,
   414  		Rates:   rates,
   415  		Total:   total,
   416  	}
   417  }
   418  
   419  func Test_matchLimitOrder(t *testing.T) {
   420  	// Setup the match package's logger.
   421  	startLogger()
   422  
   423  	takers := []*order.LimitOrder{
   424  		newLimitOrder(false, 4550000, 1, order.ImmediateTiF, 0), // buy, 1 lot, immediate, equal rate
   425  		newLimitOrder(true, 4450000, 1, order.ImmediateTiF, 0),  // sell, 1 lot, immediate, overlapping rate
   426  		newLimitOrder(true, 4300000, 3, order.StandingTiF, 0),   // sell, 3 lots, immediate, multiple makers
   427  		newLimitOrder(true, 4300000, 2, order.StandingTiF, 0),   // sell, 4 lots, immediate, multiple makers, partial last maker
   428  		newLimitOrder(true, 4300000, 8, order.StandingTiF, 0),   // sell, 8 lots, immediate, multiple makers, partial taker remaining
   429  	}
   430  	resetTakers := func() {
   431  		for _, o := range takers {
   432  			o.FillAmt = 0
   433  		}
   434  	}
   435  
   436  	nSell := len(bookSellOrders)
   437  	nBuy := len(bookBuyOrders)
   438  
   439  	type args struct {
   440  		book Booker
   441  		ord  *order.LimitOrder
   442  	}
   443  	tests := []struct {
   444  		name           string
   445  		args           args
   446  		doesMatch      bool
   447  		wantMatch      *order.MatchSet
   448  		takerRemaining uint64
   449  	}{
   450  		{
   451  			"OK limit buy immediate rate match",
   452  			args{
   453  				book: newBooker(),
   454  				ord:  takers[0],
   455  			},
   456  			true,
   457  			newMatchSet(takers[0], []*order.LimitOrder{bookSellOrders[nSell-1]}),
   458  			0,
   459  		},
   460  		{
   461  			"OK limit sell immediate rate overlap",
   462  			args{
   463  				book: newBooker(),
   464  				ord:  takers[1],
   465  			},
   466  			true,
   467  			newMatchSet(takers[1], []*order.LimitOrder{bookBuyOrders[nBuy-1]}),
   468  			0,
   469  		},
   470  		{
   471  			"OK limit sell immediate multiple makers",
   472  			args{
   473  				book: newBooker(),
   474  				ord:  takers[2],
   475  			},
   476  			true,
   477  			newMatchSet(takers[2], []*order.LimitOrder{bookBuyOrders[nBuy-1], bookBuyOrders[nBuy-2]}),
   478  			0,
   479  		},
   480  		{
   481  			"OK limit sell immediate multiple makers partial maker fill",
   482  			args{
   483  				book: newBooker(),
   484  				ord:  takers[3],
   485  			},
   486  			true,
   487  			newMatchSet(takers[3], []*order.LimitOrder{bookBuyOrders[nBuy-1], bookBuyOrders[nBuy-2]}, 1*LotSize),
   488  			0,
   489  		},
   490  		{
   491  			"OK limit sell immediate multiple makers partial taker fill",
   492  			args{
   493  				book: newBooker(),
   494  				ord:  takers[4],
   495  			},
   496  			true,
   497  			newMatchSet(takers[4], []*order.LimitOrder{bookBuyOrders[nBuy-1], bookBuyOrders[nBuy-2], bookBuyOrders[nBuy-3]}),
   498  			1 * LotSize,
   499  		},
   500  	}
   501  	for _, tt := range tests {
   502  		t.Run(tt.name, func(t *testing.T) {
   503  			// Reset Filled amounts of all pre-defined orders before each test.
   504  			resetTakers()
   505  			resetMakers()
   506  
   507  			gotMatch := matchLimitOrder(tt.args.book, tt.args.ord)
   508  			matchMade := gotMatch != nil
   509  			if tt.doesMatch != matchMade {
   510  				t.Errorf("Match expected = %v, got = %v", tt.doesMatch, matchMade)
   511  			}
   512  			if !reflect.DeepEqual(gotMatch, tt.wantMatch) {
   513  				t.Errorf("matchLimitOrder() = %v, want %v", gotMatch, tt.wantMatch)
   514  			}
   515  			if tt.takerRemaining != tt.args.ord.Remaining() {
   516  				t.Errorf("Taker remaining incorrect. Expected %d, got %d.",
   517  					tt.takerRemaining, tt.args.ord.Remaining())
   518  			}
   519  		})
   520  	}
   521  }
   522  
   523  func newCancelOrder(targetOrderID order.OrderID, serverTime time.Time) *OrderRevealed {
   524  	pe := randomPreimage()
   525  	return &OrderRevealed{
   526  		&order.CancelOrder{
   527  			P: order.Prefix{
   528  				ServerTime: serverTime,
   529  				Commit:     pe.Commit(),
   530  			},
   531  			TargetOrderID: targetOrderID,
   532  		}, pe}
   533  }
   534  
   535  func TestMatch_cancelOnly(t *testing.T) {
   536  	// Setup the match package's logger.
   537  	startLogger()
   538  
   539  	// New matching engine.
   540  	me := New()
   541  
   542  	rnd.Seed(1212121)
   543  
   544  	fakeOrder := newLimitOrder(false, 4550000, 1, order.ImmediateTiF, 0)
   545  	fakeOrder.ServerTime = time.Now()
   546  
   547  	// takers is heterogenous w.r.t. type
   548  	takers := []*OrderRevealed{
   549  		newCancelOrder(bookBuyOrders[3].ID(), fakeOrder.ServerTime.Add(time.Second)),
   550  		newCancelOrder(fakeOrder.ID(), fakeOrder.ServerTime.Add(time.Second)),
   551  	}
   552  
   553  	//nSell := len(bookSellOrders)
   554  	//nBuy := len(bookBuyOrders)
   555  
   556  	type args struct {
   557  		book  Booker
   558  		queue []*OrderRevealed
   559  	}
   560  	tests := []struct {
   561  		name                   string
   562  		args                   args
   563  		doesMatch              bool
   564  		wantSeed               []byte
   565  		wantMatches            []*order.MatchSet
   566  		wantNumPassed          int
   567  		wantNumFailed          int
   568  		wantDoneOK             int
   569  		wantNumPartial         int
   570  		wantNumInserted        int
   571  		wantNumUnbooked        int
   572  		wantNumCancelsExecuted int
   573  		wantNumTradesCanceled  int
   574  		wantNumNomatched       int
   575  	}{
   576  		{
   577  			name: "cancel standing ok",
   578  			args: args{
   579  				book:  newBooker(),
   580  				queue: []*OrderRevealed{takers[0]},
   581  			},
   582  			doesMatch: true,
   583  			wantSeed: []byte{
   584  				0xbb, 0x20, 0x85, 0x39, 0x78, 0xd5, 0x09, 0xa9,
   585  				0xa0, 0x56, 0x78, 0x4c, 0x50, 0xb1, 0xb4, 0x3d,
   586  				0xe3, 0xe9, 0x9b, 0x2b, 0x88, 0x35, 0x2c, 0x71,
   587  				0xed, 0xff, 0x5d, 0xc2, 0x1e, 0x87, 0xcb, 0xf8},
   588  			wantMatches: []*order.MatchSet{
   589  				{
   590  					Taker:   takers[0].Order,
   591  					Makers:  []*order.LimitOrder{bookBuyOrders[3]},
   592  					Amounts: []uint64{bookBuyOrders[3].Remaining()},
   593  					Rates:   []uint64{bookBuyOrders[3].Rate},
   594  					Total:   0,
   595  				},
   596  			},
   597  			wantNumPassed:          1,
   598  			wantNumFailed:          0,
   599  			wantDoneOK:             1,
   600  			wantNumPartial:         0,
   601  			wantNumInserted:        0,
   602  			wantNumUnbooked:        1,
   603  			wantNumCancelsExecuted: 1,
   604  			wantNumTradesCanceled:  1,
   605  		},
   606  		{
   607  			name: "cancel non-existent standing",
   608  			args: args{
   609  				book:  newBooker(),
   610  				queue: []*OrderRevealed{takers[1]},
   611  			},
   612  			doesMatch: false,
   613  			wantSeed: []byte{
   614  				0x9a, 0xdb, 0xbb, 0x7a, 0x9f, 0x7f, 0xe1, 0x72,
   615  				0xf0, 0x90, 0x38, 0x7a, 0xa6, 0xb9, 0x8a, 0x83,
   616  				0x5a, 0x75, 0x05, 0x19, 0x66, 0x6d, 0x53, 0x12,
   617  				0x7e, 0xc8, 0x9d, 0xe3, 0x87, 0x5d, 0xdc, 0x8a},
   618  			wantMatches:            nil,
   619  			wantNumPassed:          0,
   620  			wantNumFailed:          1,
   621  			wantDoneOK:             0,
   622  			wantNumPartial:         0,
   623  			wantNumInserted:        0,
   624  			wantNumUnbooked:        0,
   625  			wantNumCancelsExecuted: 0,
   626  			wantNumTradesCanceled:  0,
   627  			wantNumNomatched:       1,
   628  		},
   629  	}
   630  	for _, tt := range tests {
   631  		t.Run(tt.name, func(t *testing.T) {
   632  			// Reset Filled amounts of all pre-defined orders before each test.
   633  			resetMakers()
   634  
   635  			numBuys0 := tt.args.book.BuyCount()
   636  
   637  			seed, matches, passed, failed, doneOK, partial, booked, nomatched, unbooked, updates, _ := me.Match(tt.args.book, tt.args.queue)
   638  			matchMade := len(matches) > 0 && matches[0] != nil
   639  			if tt.doesMatch != matchMade {
   640  				t.Errorf("Match expected = %v, got = %v", tt.doesMatch, matchMade)
   641  			}
   642  			if len(matches) != len(tt.wantMatches) {
   643  				t.Errorf("number of matches %d, expected %d", len(matches), len(tt.wantMatches))
   644  			}
   645  			for i := range matches {
   646  				if !reflect.DeepEqual(matches[i], tt.wantMatches[i]) {
   647  					t.Errorf("matches[%d] = %v, want %v", i, matches[i], tt.wantMatches[i])
   648  				}
   649  			}
   650  			if !bytes.Equal(seed, tt.wantSeed) {
   651  				t.Errorf("got seed %#v, expected %x", seed, tt.wantSeed)
   652  			}
   653  			if len(passed) != tt.wantNumPassed {
   654  				t.Errorf("number passed %d, expected %d", len(passed), tt.wantNumPassed)
   655  			}
   656  			if len(failed) != tt.wantNumFailed {
   657  				t.Errorf("number failed %d, expected %d", len(failed), tt.wantNumFailed)
   658  			}
   659  			if len(doneOK) != tt.wantDoneOK {
   660  				t.Errorf("number doneOK %d, expected %d", len(doneOK), tt.wantDoneOK)
   661  			}
   662  			if len(partial) != tt.wantNumPartial {
   663  				t.Errorf("number partial %d, expected %d", len(partial), tt.wantNumPartial)
   664  			}
   665  			if len(booked) != tt.wantNumInserted {
   666  				t.Errorf("number booked %d, expected %d", len(booked), tt.wantNumInserted)
   667  			}
   668  			if len(unbooked) != tt.wantNumUnbooked {
   669  				t.Errorf("number unbooked %d, expected %d", len(unbooked), tt.wantNumUnbooked)
   670  			}
   671  			if len(nomatched) != tt.wantNumNomatched {
   672  				t.Errorf("number nomatched %d, expected %d", len(nomatched), tt.wantNumNomatched)
   673  			}
   674  
   675  			if len(updates.CancelsExecuted) != tt.wantNumCancelsExecuted {
   676  				t.Errorf("number of cancels executed %d, expected %d", len(updates.CancelsExecuted), tt.wantNumCancelsExecuted)
   677  			}
   678  			if len(updates.TradesCanceled) != tt.wantNumTradesCanceled {
   679  				t.Errorf("number of trades canceled %d, expected %d", len(updates.CancelsExecuted), tt.wantNumCancelsExecuted)
   680  			}
   681  
   682  			numBuys1 := tt.args.book.BuyCount()
   683  			if numBuys0-len(passed) != numBuys1 {
   684  				t.Errorf("Buy side order book size %d, expected %d", numBuys1, numBuys0-len(passed))
   685  			}
   686  		})
   687  	}
   688  }
   689  
   690  func TestMatch_limitsOnly(t *testing.T) {
   691  	// Setup the match package's logger.
   692  	startLogger()
   693  
   694  	// New matching engine.
   695  	me := New()
   696  
   697  	rnd.Seed(1212121)
   698  
   699  	badLotsizeOrder := newLimit(false, 05000000, 1, order.ImmediateTiF, 0)
   700  	badLotsizeOrder.Order.(*order.LimitOrder).Quantity /= 2
   701  
   702  	// takers is heterogenous w.r.t. type
   703  	takers := []*OrderRevealed{
   704  		newLimit(false, 4550000, 1, order.ImmediateTiF, 0), // buy, 1 lot, immediate, equal rate
   705  		newLimit(false, 4550000, 2, order.StandingTiF, 0),  // buy, 2 lot, standing, equal rate, partial taker insert to book
   706  		newLimit(false, 4550000, 2, order.ImmediateTiF, 0), // buy, 2 lot, immediate, equal rate, partial taker unfilled
   707  		newLimit(false, 4100000, 1, order.ImmediateTiF, 0), // buy, 1 lot, immediate, unfilled fail
   708  		newLimit(true, 4540000, 1, order.ImmediateTiF, 0),  // sell, 1 lot, immediate
   709  	}
   710  
   711  	// tweak the preimage in takers[4] for the 2-element queue test.
   712  	takers[4].Preimage[0] += 1
   713  	takers[4].Order.(*order.LimitOrder).Commit = takers[4].Preimage.Commit()
   714  
   715  	resetTakers := func() {
   716  		for _, o := range takers {
   717  			switch ot := o.Order.(type) {
   718  			case *order.MarketOrder:
   719  				ot.FillAmt = 0
   720  			case *order.LimitOrder:
   721  				ot.FillAmt = 0
   722  			}
   723  		}
   724  	}
   725  
   726  	nSell := len(bookSellOrders)
   727  
   728  	type args struct {
   729  		book  Booker
   730  		queue []*OrderRevealed
   731  	}
   732  	tests := []struct {
   733  		name                   string
   734  		args                   args
   735  		doesMatch              bool
   736  		wantSeed               []byte
   737  		wantMatches            []*order.MatchSet
   738  		wantNumPassed          int
   739  		wantNumFailed          int
   740  		wantDoneOK             int
   741  		wantNumPartial         int
   742  		wantNumInserted        int
   743  		wantNumUnbooked        int
   744  		wantNumTradesPartial   int
   745  		wantNumTradesBooked    int
   746  		wantNumTradesCanceled  int
   747  		wantNumTradesCompleted int
   748  		wantNumTradesFailed    int
   749  		wantNumNomatched       int
   750  		matchStats             *MatchCycleStats
   751  	}{
   752  		{
   753  			name: "limit buy immediate rate match",
   754  			args: args{
   755  				book:  newBooker(),
   756  				queue: []*OrderRevealed{takers[0]},
   757  			},
   758  			doesMatch: true,
   759  			wantSeed: []byte{
   760  				0xbb, 0x20, 0x85, 0x39, 0x78, 0xd5, 0x09, 0xa9,
   761  				0xa0, 0x56, 0x78, 0x4c, 0x50, 0xb1, 0xb4, 0x3d,
   762  				0xe3, 0xe9, 0x9b, 0x2b, 0x88, 0x35, 0x2c, 0x71,
   763  				0xed, 0xff, 0x5d, 0xc2, 0x1e, 0x87, 0xcb, 0xf8},
   764  			wantMatches: []*order.MatchSet{
   765  				// 1 lot @ 4550000, laves best sell of 4600000
   766  				newMatchSet(takers[0].Order, []*order.LimitOrder{bookSellOrders[nSell-1]}),
   767  			},
   768  			wantNumPassed:          1,
   769  			wantNumFailed:          0,
   770  			wantDoneOK:             1,
   771  			wantNumPartial:         0,
   772  			wantNumInserted:        0,
   773  			wantNumUnbooked:        1,
   774  			wantNumTradesCompleted: 2,
   775  			wantNumNomatched:       0,
   776  			matchStats: &MatchCycleStats{
   777  				BookBuys:    bookBuyLots * LotSize,
   778  				BookSells:   (bookSellLots - 1) * LotSize,
   779  				MatchVolume: LotSize,
   780  				HighRate:    bookSellOrders[nSell-1].Rate,
   781  				LowRate:     bookSellOrders[nSell-1].Rate,
   782  				StartRate:   bookSellOrders[nSell-1].Rate,
   783  				EndRate:     bookSellOrders[nSell-1].Rate,
   784  			},
   785  		},
   786  		{
   787  			name: "limit buy standing partial taker inserted to book",
   788  			args: args{
   789  				book:  newBooker(),
   790  				queue: []*OrderRevealed{takers[1]},
   791  			},
   792  			doesMatch: true,
   793  			wantSeed: []byte{
   794  				0x9a, 0xdb, 0xbb, 0x7a, 0x9f, 0x7f, 0xe1, 0x72,
   795  				0xf0, 0x90, 0x38, 0x7a, 0xa6, 0xb9, 0x8a, 0x83,
   796  				0x5a, 0x75, 0x05, 0x19, 0x66, 0x6d, 0x53, 0x12,
   797  				0x7e, 0xc8, 0x9d, 0xe3, 0x87, 0x5d, 0xdc, 0x8a},
   798  			wantMatches: []*order.MatchSet{
   799  				// 1 lot @ 4550000. bookvol + 1 - 1 = 0
   800  				newMatchSet(takers[1].Order, []*order.LimitOrder{bookSellOrders[nSell-1]}),
   801  			},
   802  			wantNumPassed:          1,
   803  			wantNumFailed:          0,
   804  			wantDoneOK:             0,
   805  			wantNumPartial:         1,
   806  			wantNumInserted:        1,
   807  			wantNumUnbooked:        1,
   808  			wantNumTradesBooked:    1,
   809  			wantNumTradesCompleted: 1,
   810  			wantNumNomatched:       0,
   811  			matchStats: &MatchCycleStats{
   812  				BookSells:   (bookSellLots - 1) * LotSize,
   813  				BookBuys:    (bookBuyLots + 1) * LotSize,
   814  				MatchVolume: LotSize,
   815  				HighRate:    bookSellOrders[nSell-1].Rate,
   816  				LowRate:     bookSellOrders[nSell-1].Rate,
   817  				StartRate:   bookSellOrders[nSell-1].Rate,
   818  				EndRate:     bookSellOrders[nSell-1].Rate,
   819  			},
   820  		},
   821  		{
   822  			name: "limit buy immediate partial taker unfilled",
   823  			args: args{
   824  				book:  newBooker(),
   825  				queue: []*OrderRevealed{takers[2]},
   826  			},
   827  			doesMatch: true,
   828  			wantSeed: []byte{
   829  				0x4e, 0xec, 0x23, 0x4f, 0xfd, 0xb1, 0xd8, 0xd9,
   830  				0x14, 0xcd, 0xae, 0x34, 0x3c, 0xa2, 0x14, 0x5d,
   831  				0x6e, 0x2d, 0x92, 0xf3, 0xbf, 0xb2, 0x04, 0xfc,
   832  				0xee, 0x39, 0x13, 0xbb, 0x55, 0xed, 0x98, 0x81},
   833  			wantMatches: []*order.MatchSet{
   834  				// 1 lot @ 4550000. bookvol - 1
   835  				newMatchSet(takers[2].Order, []*order.LimitOrder{bookSellOrders[nSell-1]}),
   836  			},
   837  			wantNumPassed:          1,
   838  			wantNumFailed:          0,
   839  			wantDoneOK:             1,
   840  			wantNumPartial:         1,
   841  			wantNumInserted:        0,
   842  			wantNumUnbooked:        1,
   843  			wantNumTradesCompleted: 2, // not in partial since they are done
   844  			wantNumNomatched:       0,
   845  			matchStats: &MatchCycleStats{
   846  				BookSells:   (bookSellLots - 1) * LotSize,
   847  				BookBuys:    bookBuyLots * LotSize,
   848  				MatchVolume: LotSize,
   849  				HighRate:    bookSellOrders[nSell-1].Rate,
   850  				LowRate:     bookSellOrders[nSell-1].Rate,
   851  				StartRate:   bookSellOrders[nSell-1].Rate,
   852  				EndRate:     bookSellOrders[nSell-1].Rate,
   853  			},
   854  		},
   855  		{
   856  			name: "limit buy immediate unfilled fail",
   857  			args: args{
   858  				book:  newBooker(),
   859  				queue: []*OrderRevealed{takers[3]},
   860  			},
   861  			doesMatch: false,
   862  			wantSeed: []byte{
   863  				0x6f, 0x3d, 0x0f, 0xc8, 0x33, 0xab, 0x4d, 0xc4,
   864  				0xaf, 0xb7, 0x32, 0xcc, 0xd1, 0x77, 0xe9, 0x3e,
   865  				0x41, 0xdb, 0x0b, 0x7a, 0x9e, 0x6f, 0x34, 0xbe,
   866  				0xc9, 0xab, 0x6b, 0xb5, 0x17, 0xb2, 0x89, 0x82},
   867  			wantMatches:         nil,
   868  			wantNumPassed:       0,
   869  			wantNumFailed:       1,
   870  			wantDoneOK:          0,
   871  			wantNumPartial:      0,
   872  			wantNumInserted:     0,
   873  			wantNumUnbooked:     0,
   874  			wantNumTradesFailed: 1,
   875  			wantNumNomatched:    1,
   876  			matchStats: &MatchCycleStats{
   877  				BookSells: bookSellLots * LotSize,
   878  				BookBuys:  bookBuyLots * LotSize,
   879  				HighRate:  0,
   880  				LowRate:   0,
   881  				StartRate: 0,
   882  				EndRate:   0,
   883  			},
   884  		},
   885  		{
   886  			name: "bad lot size order",
   887  			args: args{
   888  				book:  newBooker(),
   889  				queue: []*OrderRevealed{badLotsizeOrder},
   890  			},
   891  			doesMatch: false,
   892  			wantSeed: []byte{
   893  				0x76, 0x9b, 0xd2, 0x8a, 0x7c, 0x2d, 0xa2, 0x56,
   894  				0x97, 0xa3, 0xc6, 0x8b, 0x43, 0x0c, 0x9e, 0x9a,
   895  				0xfa, 0x6c, 0xf6, 0x86, 0x70, 0x97, 0xc7, 0x74,
   896  				0xb, 0xfb, 0x29, 0x19, 0xbf, 0x86, 0x6a, 0xc8},
   897  			wantMatches:         nil,
   898  			wantNumPassed:       0,
   899  			wantNumFailed:       1,
   900  			wantDoneOK:          0,
   901  			wantNumPartial:      0,
   902  			wantNumInserted:     0,
   903  			wantNumUnbooked:     0,
   904  			wantNumTradesFailed: 1,
   905  			wantNumNomatched:    0,
   906  		},
   907  		{
   908  			name: "limit buy standing partial taker inserted to book, then filled by down-queue sell",
   909  			args: args{
   910  				book: newBooker(),
   911  				queue: []*OrderRevealed{
   912  					takers[1],
   913  					takers[4],
   914  				},
   915  			},
   916  			doesMatch: true,
   917  			wantSeed: []byte{
   918  				0xb4, 0xc0, 0xcc, 0xc2, 0x3e, 0x50, 0x1d, 0xf5,
   919  				0x15, 0xe1, 0x29, 0xd7, 0x59, 0xd6, 0x7a, 0xb0,
   920  				0xd1, 0x24, 0xfa, 0x3c, 0x9d, 0x37, 0x6e, 0x1a,
   921  				0x8b, 0x15, 0xc5, 0x84, 0xa4, 0xc0, 0x80, 0x98},
   922  			wantMatches: []*order.MatchSet{
   923  				newMatchSet(takers[1].Order, []*order.LimitOrder{bookSellOrders[nSell-1]}),
   924  				{ // the maker is reduced by matching first item in the queue
   925  					Taker:   takers[4].Order,
   926  					Makers:  []*order.LimitOrder{takers[1].Order.(*order.LimitOrder)},
   927  					Amounts: []uint64{1 * LotSize}, // 2 - 1
   928  					Rates:   []uint64{4550000},
   929  					Total:   1 * LotSize,
   930  				},
   931  			},
   932  			wantNumPassed:          2,
   933  			wantNumFailed:          0,
   934  			wantDoneOK:             1,
   935  			wantNumPartial:         1,
   936  			wantNumInserted:        1,
   937  			wantNumUnbooked:        2,
   938  			wantNumTradesBooked:    1,
   939  			wantNumTradesCompleted: 3, // both queue orders and one book sell order fully filled
   940  		},
   941  	}
   942  	for _, tt := range tests {
   943  		t.Run(tt.name, func(t *testing.T) {
   944  			// Reset Filled amounts of all pre-defined orders before each test.
   945  			resetTakers()
   946  			resetMakers()
   947  
   948  			seed, matches, passed, failed, doneOK, partial, booked, nomatched, unbooked, updates, stats := me.Match(tt.args.book, tt.args.queue)
   949  			matchMade := len(matches) > 0 && matches[0] != nil
   950  			if tt.doesMatch != matchMade {
   951  				t.Errorf("Match expected = %v, got = %v", tt.doesMatch, matchMade)
   952  			}
   953  			if len(matches) != len(tt.wantMatches) {
   954  				t.Errorf("number of matches %d, expected %d", len(matches), len(tt.wantMatches))
   955  			}
   956  			for i := range matches {
   957  				if !reflect.DeepEqual(matches[i], tt.wantMatches[i]) {
   958  					t.Errorf("matches[%d] = %v, want %v", i, matches[i], tt.wantMatches[i])
   959  				}
   960  			}
   961  			if !bytes.Equal(seed, tt.wantSeed) {
   962  				t.Errorf("got seed %#v, expected %x", seed, tt.wantSeed)
   963  			}
   964  			if len(passed) != tt.wantNumPassed {
   965  				t.Errorf("number passed %d, expected %d", len(passed), tt.wantNumPassed)
   966  			}
   967  			if len(failed) != tt.wantNumFailed {
   968  				t.Errorf("number failed %d, expected %d", len(failed), tt.wantNumFailed)
   969  			}
   970  			if len(doneOK) != tt.wantDoneOK {
   971  				t.Errorf("number doneOK %d, expected %d", len(doneOK), tt.wantDoneOK)
   972  			}
   973  			if len(partial) != tt.wantNumPartial {
   974  				t.Errorf("number partial %d, expected %d", len(partial), tt.wantNumPartial)
   975  			}
   976  			if len(booked) != tt.wantNumInserted {
   977  				t.Errorf("number booked %d, expected %d", len(booked), tt.wantNumInserted)
   978  			}
   979  			if len(unbooked) != tt.wantNumUnbooked {
   980  				t.Errorf("number unbooked %d, expected %d", len(unbooked), tt.wantNumUnbooked)
   981  			}
   982  
   983  			if len(updates.TradesCompleted) != tt.wantNumTradesCompleted {
   984  				t.Errorf("number of trades completed %d, expected %d", len(updates.TradesCompleted), tt.wantNumTradesCompleted)
   985  			}
   986  			if len(updates.TradesBooked) != tt.wantNumTradesBooked {
   987  				t.Errorf("number of trades booked %d, expected %d", len(updates.TradesBooked), tt.wantNumTradesBooked)
   988  			}
   989  			if len(updates.TradesFailed) != tt.wantNumTradesFailed {
   990  				t.Errorf("number of trades failed %d, expected %d", len(updates.TradesFailed), tt.wantNumTradesFailed)
   991  			}
   992  			if len(nomatched) != tt.wantNumNomatched {
   993  				t.Errorf("number of trades nomatched %d, expected %d", len(nomatched), tt.wantNumNomatched)
   994  			}
   995  			if tt.matchStats != nil {
   996  				compareMatchStats(t, tt.matchStats, stats)
   997  			}
   998  		})
   999  	}
  1000  }
  1001  
  1002  func TestMatch_marketSellsOnly(t *testing.T) {
  1003  	// Setup the match package's logger.
  1004  	startLogger()
  1005  
  1006  	// New matching engine.
  1007  	me := New()
  1008  
  1009  	rnd.Seed(1212121)
  1010  
  1011  	badLotsizeOrder := newMarketSellOrder(1, 0)
  1012  	badLotsizeOrder.Order.(*order.MarketOrder).Quantity /= 2
  1013  
  1014  	// takers is heterogenous w.r.t. type
  1015  	takers := []*OrderRevealed{
  1016  		newMarketSellOrder(1, 0),  // sell, 1 lot
  1017  		newMarketSellOrder(3, 0),  // sell, 3 lot
  1018  		newMarketSellOrder(6, 0),  // sell, 6 lot, partial maker fill
  1019  		newMarketSellOrder(99, 0), // sell, 99 lot, partial taker fill
  1020  	}
  1021  
  1022  	partialRate := uint64(2500000)
  1023  	partialWithRemainder := newLimitOrder(false, partialRate, 2, order.StandingTiF, 0)
  1024  	partialThenGone := newLimitOrder(false, partialRate, 4, order.StandingTiF, 0)
  1025  
  1026  	resetTakers := func() {
  1027  		for _, o := range takers {
  1028  			switch ot := o.Order.(type) {
  1029  			case *order.MarketOrder:
  1030  				ot.FillAmt = 0
  1031  			case *order.LimitOrder:
  1032  				ot.FillAmt = 0
  1033  			}
  1034  		}
  1035  	}
  1036  
  1037  	nBuy := len(bookBuyOrders)
  1038  
  1039  	type args struct {
  1040  		book  Booker
  1041  		queue []*OrderRevealed
  1042  	}
  1043  	tests := []struct {
  1044  		name                   string
  1045  		args                   args
  1046  		doesMatch              bool
  1047  		wantSeed               []byte
  1048  		wantMatches            []*order.MatchSet
  1049  		wantNumPassed          int
  1050  		wantNumFailed          int
  1051  		wantDoneOK             int
  1052  		wantNumPartial         int
  1053  		wantNumInserted        int
  1054  		wantNumUnbooked        int
  1055  		wantNumTradesCompleted int
  1056  		wantNumTradesPartial   int
  1057  		wantNumTradesFailed    int
  1058  	}{
  1059  		{
  1060  			name: "market sell, 1 maker match",
  1061  			args: args{
  1062  				book:  newBooker(),
  1063  				queue: []*OrderRevealed{takers[0]},
  1064  			},
  1065  			doesMatch: true,
  1066  			wantSeed: []byte{
  1067  				0xbb, 0x20, 0x85, 0x39, 0x78, 0xd5, 0x09, 0xa9,
  1068  				0xa0, 0x56, 0x78, 0x4c, 0x50, 0xb1, 0xb4, 0x3d,
  1069  				0xe3, 0xe9, 0x9b, 0x2b, 0x88, 0x35, 0x2c, 0x71,
  1070  				0xed, 0xff, 0x5d, 0xc2, 0x1e, 0x87, 0xcb, 0xf8},
  1071  			wantMatches: []*order.MatchSet{
  1072  				newMatchSet(takers[0].Order, []*order.LimitOrder{bookBuyOrders[nBuy-1]}),
  1073  			},
  1074  			wantNumPassed:          1,
  1075  			wantNumFailed:          0,
  1076  			wantDoneOK:             1,
  1077  			wantNumPartial:         0,
  1078  			wantNumInserted:        0,
  1079  			wantNumUnbooked:        1,
  1080  			wantNumTradesCompleted: 2, // queued market and already booked limit
  1081  		},
  1082  		{
  1083  			name: "market sell, 2 maker match",
  1084  			args: args{
  1085  				book:  newBooker(),
  1086  				queue: []*OrderRevealed{takers[1]},
  1087  			},
  1088  			doesMatch: true,
  1089  			wantSeed: []byte{
  1090  				0x9a, 0xdb, 0xbb, 0x7a, 0x9f, 0x7f, 0xe1, 0x72,
  1091  				0xf0, 0x90, 0x38, 0x7a, 0xa6, 0xb9, 0x8a, 0x83,
  1092  				0x5a, 0x75, 0x05, 0x19, 0x66, 0x6d, 0x53, 0x12,
  1093  				0x7e, 0xc8, 0x9d, 0xe3, 0x87, 0x5d, 0xdc, 0x8a},
  1094  			wantMatches: []*order.MatchSet{
  1095  				newMatchSet(takers[1].Order, []*order.LimitOrder{bookBuyOrders[nBuy-1], bookBuyOrders[nBuy-2]}),
  1096  			},
  1097  			wantNumPassed:          1,
  1098  			wantNumFailed:          0,
  1099  			wantDoneOK:             1,
  1100  			wantNumPartial:         0,
  1101  			wantNumInserted:        0,
  1102  			wantNumUnbooked:        2,
  1103  			wantNumTradesCompleted: 3, // queued market and two already booked limits
  1104  		},
  1105  		{
  1106  			name: "market sell, 3 maker match, 1 partial maker fill",
  1107  			args: args{
  1108  				book:  newBooker(),
  1109  				queue: []*OrderRevealed{takers[2]},
  1110  			},
  1111  			doesMatch: true,
  1112  			wantSeed: []byte{
  1113  				0x4e, 0xec, 0x23, 0x4f, 0xfd, 0xb1, 0xd8, 0xd9,
  1114  				0x14, 0xcd, 0xae, 0x34, 0x3c, 0xa2, 0x14, 0x5d,
  1115  				0x6e, 0x2d, 0x92, 0xf3, 0xbf, 0xb2, 0x04, 0xfc,
  1116  				0xee, 0x39, 0x13, 0xbb, 0x55, 0xed, 0x98, 0x81},
  1117  			wantMatches: []*order.MatchSet{
  1118  				newMatchSet(takers[2].Order, []*order.LimitOrder{bookBuyOrders[nBuy-1], bookBuyOrders[nBuy-2], bookBuyOrders[nBuy-3]}, 3*LotSize),
  1119  			},
  1120  			wantNumPassed:          1,
  1121  			wantNumFailed:          0,
  1122  			wantDoneOK:             1,
  1123  			wantNumPartial:         0,
  1124  			wantNumInserted:        0,
  1125  			wantNumUnbooked:        2,
  1126  			wantNumTradesCompleted: 3, // taker and two already booked limits
  1127  			wantNumTradesPartial:   1, // partial fill of one already booked limit, which consumed the taker
  1128  		},
  1129  		{
  1130  			name: "market sell bad lot size",
  1131  			args: args{
  1132  				book:  newBooker(),
  1133  				queue: []*OrderRevealed{badLotsizeOrder},
  1134  			},
  1135  			doesMatch: false,
  1136  			wantSeed: []byte{
  1137  				0x76, 0x9b, 0xd2, 0x8a, 0x7c, 0x2d, 0xa2, 0x56,
  1138  				0x97, 0xa3, 0xc6, 0x8b, 0x43, 0x0c, 0x9e, 0x9a,
  1139  				0xfa, 0x6c, 0xf6, 0x86, 0x70, 0x97, 0xc7, 0x74,
  1140  				0x0b, 0xfb, 0x29, 0x19, 0xbf, 0x86, 0x6a, 0xc8},
  1141  			wantMatches:         nil,
  1142  			wantNumPassed:       0,
  1143  			wantNumFailed:       1,
  1144  			wantDoneOK:          0,
  1145  			wantNumPartial:      0,
  1146  			wantNumInserted:     0,
  1147  			wantNumUnbooked:     0,
  1148  			wantNumTradesFailed: 1,
  1149  		},
  1150  		{
  1151  			name: "market sell partial fill with remainder",
  1152  			args: args{
  1153  				book: &BookStub{
  1154  					lotSize:   LotSize,
  1155  					buyOrders: []*order.LimitOrder{partialWithRemainder},
  1156  				},
  1157  				queue: []*OrderRevealed{takers[0]},
  1158  			},
  1159  			doesMatch: true,
  1160  			wantSeed: []byte{
  1161  				0xbb, 0x20, 0x85, 0x39, 0x78, 0xd5, 0x09, 0xa9,
  1162  				0xa0, 0x56, 0x78, 0x4c, 0x50, 0xb1, 0xb4, 0x3d,
  1163  				0xe3, 0xe9, 0x9b, 0x2b, 0x88, 0x35, 0x2c, 0x71,
  1164  				0xed, 0xff, 0x5d, 0xc2, 0x1e, 0x87, 0xcb, 0xf8},
  1165  			wantMatches: []*order.MatchSet{
  1166  				{
  1167  					Taker:   takers[0].Order,
  1168  					Makers:  []*order.LimitOrder{partialWithRemainder},
  1169  					Amounts: []uint64{LotSize},
  1170  					Rates:   []uint64{partialRate},
  1171  					Total:   LotSize,
  1172  				},
  1173  			},
  1174  			wantNumPassed:          1,
  1175  			wantNumFailed:          0,
  1176  			wantDoneOK:             1,
  1177  			wantNumTradesPartial:   1,
  1178  			wantNumTradesCompleted: 1,
  1179  			wantNumUnbooked:        0,
  1180  			wantNumTradesFailed:    0,
  1181  		},
  1182  		// If a booked order partially matches, but then completes on a subsequent
  1183  		// order, the updates.TradesPartial should not contain the order.
  1184  		{
  1185  			name: "market sell partial fill before complete fill",
  1186  			args: args{
  1187  				book: &BookStub{
  1188  					lotSize:   LotSize,
  1189  					buyOrders: []*order.LimitOrder{partialThenGone},
  1190  				},
  1191  				queue: []*OrderRevealed{takers[0], takers[1]},
  1192  			},
  1193  			doesMatch: true,
  1194  			wantSeed: []byte{
  1195  				0x29, 0x39, 0xf3, 0x0e, 0x9e, 0x00, 0xc6, 0xe5,
  1196  				0xc7, 0x1d, 0x56, 0x82, 0x2c, 0x89, 0x92, 0x3c,
  1197  				0x59, 0x97, 0x84, 0x43, 0xaf, 0x7f, 0x7b, 0x67,
  1198  				0xeb, 0x54, 0xd0, 0x88, 0x01, 0x2b, 0xf0, 0x58},
  1199  			wantMatches: []*order.MatchSet{
  1200  				{
  1201  					Taker:   takers[0].Order,
  1202  					Makers:  []*order.LimitOrder{partialThenGone},
  1203  					Amounts: []uint64{LotSize},
  1204  					Rates:   []uint64{partialRate},
  1205  					Total:   LotSize,
  1206  				},
  1207  				{
  1208  					Taker:   takers[1].Order,
  1209  					Makers:  []*order.LimitOrder{partialThenGone},
  1210  					Amounts: []uint64{LotSize * 3},
  1211  					Rates:   []uint64{partialRate},
  1212  					Total:   LotSize * 3,
  1213  				},
  1214  			},
  1215  			wantNumPassed:          2,
  1216  			wantNumFailed:          0,
  1217  			wantDoneOK:             2,
  1218  			wantNumTradesPartial:   0,
  1219  			wantNumTradesCompleted: 3,
  1220  			wantNumUnbooked:        1,
  1221  			wantNumTradesFailed:    0,
  1222  		},
  1223  	}
  1224  	for _, tt := range tests {
  1225  		t.Run(tt.name, func(t *testing.T) {
  1226  			// Reset Filled amounts of all pre-defined orders before each test.
  1227  			resetTakers()
  1228  			resetMakers()
  1229  
  1230  			fmt.Printf("%v\n", takers)
  1231  
  1232  			seed, matches, passed, failed, doneOK, partial, booked, _, unbooked, updates, _ := me.Match(tt.args.book, tt.args.queue)
  1233  			matchMade := len(matches) > 0 && matches[0] != nil
  1234  			if tt.doesMatch != matchMade {
  1235  				t.Errorf("Match expected = %v, got = %v", tt.doesMatch, matchMade)
  1236  			}
  1237  			if len(matches) != len(tt.wantMatches) {
  1238  				t.Errorf("number of matches %d, expected %d", len(matches), len(tt.wantMatches))
  1239  			}
  1240  			for i := range matches {
  1241  				if !reflect.DeepEqual(matches[i], tt.wantMatches[i]) {
  1242  					t.Errorf("matches[%d] = %v, want %v", i, matches[i], tt.wantMatches[i])
  1243  				}
  1244  			}
  1245  			if !bytes.Equal(seed, tt.wantSeed) {
  1246  				t.Errorf("got seed %x, expected %x", seed, tt.wantSeed)
  1247  			}
  1248  			if len(passed) != tt.wantNumPassed {
  1249  				t.Errorf("number passed %d, expected %d", len(passed), tt.wantNumPassed)
  1250  			}
  1251  			if len(failed) != tt.wantNumFailed {
  1252  				t.Errorf("number failed %d, expected %d", len(failed), tt.wantNumFailed)
  1253  			}
  1254  			if len(doneOK) != tt.wantDoneOK {
  1255  				t.Errorf("number doneOK %d, expected %d", len(doneOK), tt.wantDoneOK)
  1256  			}
  1257  			if len(partial) != tt.wantNumPartial {
  1258  				t.Errorf("number partial %d, expected %d", len(partial), tt.wantNumPartial)
  1259  			}
  1260  			if len(booked) != tt.wantNumInserted {
  1261  				t.Errorf("number booked %d, expected %d", len(booked), tt.wantNumInserted)
  1262  			}
  1263  			if len(unbooked) != tt.wantNumUnbooked {
  1264  				t.Errorf("number unbooked %d, expected %d", len(unbooked), tt.wantNumUnbooked)
  1265  			}
  1266  
  1267  			if len(updates.TradesCompleted) != tt.wantNumTradesCompleted {
  1268  				t.Errorf("number of trades completed %d, expected %d", len(updates.TradesCompleted), tt.wantNumTradesCompleted)
  1269  			}
  1270  			if len(updates.TradesPartial) != tt.wantNumTradesPartial {
  1271  				t.Errorf("number of book trades partially filled %d, expected %d", len(updates.TradesPartial), tt.wantNumTradesPartial)
  1272  			}
  1273  			if len(updates.TradesFailed) != tt.wantNumTradesFailed {
  1274  				t.Errorf("number of trades failed %d, expected %d", len(updates.TradesFailed), tt.wantNumTradesFailed)
  1275  			}
  1276  			t.Log(updates)
  1277  		})
  1278  	}
  1279  }
  1280  
  1281  // marketBuyQuoteAmt gives the exact amount in the quote asset require to
  1282  // purchase lots worth of the base asset given the current sell order book.
  1283  func marketBuyQuoteAmt(lots uint64) uint64 {
  1284  	var amt uint64
  1285  	var i int
  1286  	nSell := len(bookSellOrders)
  1287  	for lots > 0 && i < nSell {
  1288  		sellOrder := bookSellOrders[nSell-1-i]
  1289  		orderLots := sellOrder.Quantity / LotSize
  1290  		if orderLots > lots {
  1291  			orderLots = lots
  1292  		}
  1293  		lots -= orderLots
  1294  
  1295  		amt += BaseToQuote(sellOrder.Rate, orderLots*LotSize)
  1296  		i++
  1297  	}
  1298  	return amt
  1299  }
  1300  
  1301  // quoteAmt computes the required amount of the quote asset required to purchase
  1302  // the specified number of lots given the current order book and required amount
  1303  // buffering in the single lot case.
  1304  func quoteAmt(lots uint64) uint64 {
  1305  	amt := marketBuyQuoteAmt(lots)
  1306  	if lots == 1 {
  1307  		amt *= 3
  1308  		amt /= 2
  1309  	}
  1310  	return amt
  1311  }
  1312  
  1313  func TestMatch_marketBuysOnly(t *testing.T) {
  1314  	// Setup the match package's logger.
  1315  	startLogger()
  1316  
  1317  	// New matching engine.
  1318  	me := New()
  1319  
  1320  	rnd.Seed(1212121)
  1321  
  1322  	nSell := len(bookSellOrders)
  1323  
  1324  	// takers is heterogenous w.r.t. type
  1325  	takers := []*OrderRevealed{
  1326  		newMarketBuyOrder(quoteAmt(1), 0),  // buy, 1 lot
  1327  		newMarketBuyOrder(quoteAmt(2), 0),  // buy, 2 lot
  1328  		newMarketBuyOrder(quoteAmt(3), 0),  // buy, 3 lot
  1329  		newMarketBuyOrder(quoteAmt(99), 0), // buy, 99 lot
  1330  	}
  1331  
  1332  	resetTakers := func() {
  1333  		for _, o := range takers {
  1334  			switch ot := o.Order.(type) {
  1335  			case *order.MarketOrder:
  1336  				ot.FillAmt = 0
  1337  			case *order.LimitOrder:
  1338  				ot.FillAmt = 0
  1339  			}
  1340  		}
  1341  	}
  1342  
  1343  	bookSellOrdersReverse := make([]*order.LimitOrder, len(bookSellOrders))
  1344  	for i := range bookSellOrders {
  1345  		bookSellOrdersReverse[len(bookSellOrders)-1-i] = bookSellOrders[i]
  1346  	}
  1347  
  1348  	type args struct {
  1349  		book  Booker
  1350  		queue []*OrderRevealed
  1351  	}
  1352  	tests := []struct {
  1353  		name                   string
  1354  		args                   args
  1355  		doesMatch              bool
  1356  		wantSeed               []byte
  1357  		wantMatches            []*order.MatchSet
  1358  		remaining              []uint64
  1359  		wantNumPassed          int
  1360  		wantNumFailed          int
  1361  		wantDoneOK             int
  1362  		wantNumPartial         int
  1363  		wantNumInserted        int
  1364  		wantNumUnbooked        int
  1365  		wantNumTradesCompleted int
  1366  		wantNumTradesPartial   int
  1367  		wantNumTradesFailed    int
  1368  		matchStats             *MatchCycleStats
  1369  	}{
  1370  		{
  1371  			name: "market buy, 1 maker match",
  1372  			args: args{
  1373  				book:  newBooker(),
  1374  				queue: []*OrderRevealed{takers[0]},
  1375  			},
  1376  			doesMatch: true,
  1377  			wantSeed: []byte{
  1378  				0x76, 0x9b, 0xd2, 0x8a, 0x7c, 0x2d, 0xa2, 0x56,
  1379  				0x97, 0xa3, 0xc6, 0x8b, 0x43, 0x0c, 0x9e, 0x9a,
  1380  				0xfa, 0x6c, 0xf6, 0x86, 0x70, 0x97, 0xc7, 0x74,
  1381  				0x0b, 0xfb, 0x29, 0x19, 0xbf, 0x86, 0x6a, 0xc8},
  1382  			wantMatches: []*order.MatchSet{
  1383  				// 1 lot @ 4550000, leaving best sell as 4600000
  1384  				newMatchSet(takers[0].Order, []*order.LimitOrder{bookSellOrders[nSell-1]}),
  1385  			},
  1386  			remaining:              []uint64{quoteAmt(1) - marketBuyQuoteAmt(1)},
  1387  			wantNumPassed:          1,
  1388  			wantNumFailed:          0,
  1389  			wantDoneOK:             1,
  1390  			wantNumPartial:         0,
  1391  			wantNumInserted:        0,
  1392  			wantNumUnbooked:        1,
  1393  			wantNumTradesCompleted: 2, // the taker and the maker are completed
  1394  			matchStats: &MatchCycleStats{
  1395  				BookSells:   (bookSellLots - 1) * LotSize,
  1396  				BookBuys:    bookBuyLots * LotSize,
  1397  				MatchVolume: LotSize,
  1398  				HighRate:    bookSellOrders[nSell-1].Rate,
  1399  				LowRate:     bookSellOrders[nSell-1].Rate,
  1400  				StartRate:   bookSellOrders[nSell-1].Rate,
  1401  				EndRate:     bookSellOrders[nSell-1].Rate,
  1402  			},
  1403  		},
  1404  		{
  1405  			name: "market buy, 2 maker match",
  1406  			args: args{
  1407  				book:  newBooker(),
  1408  				queue: []*OrderRevealed{takers[1]},
  1409  			},
  1410  			doesMatch: true,
  1411  			wantSeed: []byte{
  1412  				0xbb, 0x20, 0x85, 0x39, 0x78, 0xd5, 0x09, 0xa9,
  1413  				0xa0, 0x56, 0x78, 0x4c, 0x50, 0xb1, 0xb4, 0x3d,
  1414  				0xe3, 0xe9, 0x9b, 0x2b, 0x88, 0x35, 0x2c, 0x71,
  1415  				0xed, 0xff, 0x5d, 0xc2, 0x1e, 0x87, 0xcb, 0xf8},
  1416  			wantMatches: []*order.MatchSet{
  1417  				// 1 lot @ 4550000, 1 @ 4600000
  1418  				newMatchSet(takers[1].Order, []*order.LimitOrder{bookSellOrders[nSell-1], bookSellOrders[nSell-2]}, 1*LotSize),
  1419  			},
  1420  			remaining:              []uint64{0},
  1421  			wantNumPassed:          1,
  1422  			wantNumFailed:          0,
  1423  			wantDoneOK:             1,
  1424  			wantNumPartial:         0,
  1425  			wantNumInserted:        0,
  1426  			wantNumUnbooked:        1,
  1427  			wantNumTradesCompleted: 2, // taker, and one maker
  1428  			wantNumTradesPartial:   1, // the second maker
  1429  			matchStats: &MatchCycleStats{
  1430  				BookSells:   (bookSellLots - 2) * LotSize,
  1431  				BookBuys:    bookBuyLots * LotSize,
  1432  				MatchVolume: 2 * LotSize,
  1433  				HighRate:    bookSellOrders[nSell-2].Rate,
  1434  				LowRate:     bookSellOrders[nSell-1].Rate,
  1435  				StartRate:   bookSellOrders[nSell-1].Rate,
  1436  				EndRate:     bookSellOrders[nSell-2].Rate,
  1437  			},
  1438  		},
  1439  		{
  1440  			name: "market buy, 3 maker match",
  1441  			args: args{
  1442  				book:  newBooker(),
  1443  				queue: []*OrderRevealed{takers[2]},
  1444  			},
  1445  			doesMatch: true,
  1446  			wantSeed: []byte{
  1447  				0x9a, 0xdb, 0xbb, 0x7a, 0x9f, 0x7f, 0xe1, 0x72,
  1448  				0xf0, 0x90, 0x38, 0x7a, 0xa6, 0xb9, 0x8a, 0x83,
  1449  				0x5a, 0x75, 0x05, 0x19, 0x66, 0x6d, 0x53, 0x12,
  1450  				0x7e, 0xc8, 0x9d, 0xe3, 0x87, 0x5d, 0xdc, 0x8a},
  1451  			wantMatches: []*order.MatchSet{
  1452  				// 1 @ 4550000, 2 @ 4600000, leaves best sell of 4700000 behind/
  1453  				newMatchSet(takers[2].Order, []*order.LimitOrder{bookSellOrders[nSell-1], bookSellOrders[nSell-2]}),
  1454  			},
  1455  			remaining:              []uint64{0},
  1456  			wantNumPassed:          1,
  1457  			wantNumFailed:          0,
  1458  			wantDoneOK:             1,
  1459  			wantNumPartial:         0,
  1460  			wantNumInserted:        0,
  1461  			wantNumUnbooked:        2,
  1462  			wantNumTradesCompleted: 3, // taker, 2 makers
  1463  			matchStats: &MatchCycleStats{
  1464  				BookSells:   (bookSellLots - 3) * LotSize,
  1465  				BookBuys:    bookBuyLots * LotSize,
  1466  				MatchVolume: 3 * LotSize,
  1467  				HighRate:    bookSellOrders[nSell-2].Rate,
  1468  				LowRate:     bookSellOrders[nSell-1].Rate,
  1469  				StartRate:   bookSellOrders[nSell-1].Rate,
  1470  				EndRate:     bookSellOrders[nSell-2].Rate,
  1471  			},
  1472  		},
  1473  		{
  1474  			name: "market buy, 99 maker match",
  1475  			args: args{
  1476  				book:  newBooker(),
  1477  				queue: []*OrderRevealed{takers[3]},
  1478  			},
  1479  			doesMatch: true,
  1480  			wantSeed: []byte{
  1481  				0x4e, 0xec, 0x23, 0x4f, 0xfd, 0xb1, 0xd8, 0xd9,
  1482  				0x14, 0xcd, 0xae, 0x34, 0x3c, 0xa2, 0x14, 0x5d,
  1483  				0x6e, 0x2d, 0x92, 0xf3, 0xbf, 0xb2, 0x04, 0xfc,
  1484  				0xee, 0x39, 0x13, 0xbb, 0x55, 0xed, 0x98, 0x81},
  1485  			wantMatches: []*order.MatchSet{
  1486  				newMatchSet(takers[3].Order, bookSellOrdersReverse),
  1487  			},
  1488  			remaining:              []uint64{0},
  1489  			wantNumPassed:          1,
  1490  			wantNumFailed:          0,
  1491  			wantDoneOK:             1,
  1492  			wantNumPartial:         0,
  1493  			wantNumInserted:        0,
  1494  			wantNumUnbooked:        11,
  1495  			wantNumTradesCompleted: 12, // taker, 11 makers
  1496  		},
  1497  	}
  1498  	for _, tt := range tests {
  1499  		t.Run(tt.name, func(t *testing.T) {
  1500  			// Reset Filled amounts of all pre-defined orders before each test.
  1501  			resetTakers()
  1502  			resetMakers()
  1503  
  1504  			seed, matches, passed, failed, doneOK, partial, booked, _, unbooked, updates, stats := me.Match(tt.args.book, tt.args.queue)
  1505  			matchMade := len(matches) > 0 && matches[0] != nil
  1506  			if tt.doesMatch != matchMade {
  1507  				t.Errorf("Match expected = %v, got = %v", tt.doesMatch, matchMade)
  1508  			}
  1509  			if len(matches) != len(tt.wantMatches) {
  1510  				t.Errorf("number of matches %d, expected %d", len(matches), len(tt.wantMatches))
  1511  			}
  1512  
  1513  			for i := range matches {
  1514  				if !reflect.DeepEqual(matches[i], tt.wantMatches[i]) {
  1515  					t.Errorf("matches[%d] = %v, want %v", i, matches[i], tt.wantMatches[i])
  1516  				}
  1517  				if matches[i].Taker.Trade().Remaining() != tt.remaining[i] {
  1518  					t.Errorf("Incorrect taker order amount remaining. Expected %d, got %d",
  1519  						tt.remaining[i], matches[i].Taker.Trade().Remaining())
  1520  				}
  1521  			}
  1522  			if !bytes.Equal(seed, tt.wantSeed) {
  1523  				t.Errorf("got seed %x, expected %x", seed, tt.wantSeed)
  1524  			}
  1525  			if len(passed) != tt.wantNumPassed {
  1526  				t.Errorf("number passed %d, expected %d", len(passed), tt.wantNumPassed)
  1527  			}
  1528  			if len(failed) != tt.wantNumFailed {
  1529  				t.Errorf("number failed %d, expected %d", len(failed), tt.wantNumFailed)
  1530  			}
  1531  			if len(doneOK) != tt.wantDoneOK {
  1532  				t.Errorf("number doneOK %d, expected %d", len(doneOK), tt.wantDoneOK)
  1533  			}
  1534  			if len(partial) != tt.wantNumPartial {
  1535  				t.Errorf("number partial %d, expected %d", len(partial), tt.wantNumPartial)
  1536  			}
  1537  			if len(booked) != tt.wantNumInserted {
  1538  				t.Errorf("number booked %d, expected %d", len(booked), tt.wantNumInserted)
  1539  			}
  1540  			if len(unbooked) != tt.wantNumUnbooked {
  1541  				t.Errorf("number unbooked %d, expected %d", len(unbooked), tt.wantNumUnbooked)
  1542  			}
  1543  
  1544  			if len(updates.TradesCompleted) != tt.wantNumTradesCompleted {
  1545  				t.Errorf("number of trades completed %d, expected %d", len(updates.TradesCompleted), tt.wantNumTradesCompleted)
  1546  			}
  1547  			if len(updates.TradesPartial) != tt.wantNumTradesPartial {
  1548  				t.Errorf("number of book trades partially filled %d, expected %d", len(updates.TradesPartial), tt.wantNumTradesPartial)
  1549  			}
  1550  			if len(updates.TradesFailed) != tt.wantNumTradesFailed {
  1551  				t.Errorf("number of trades failed %d, expected %d", len(updates.TradesFailed), tt.wantNumTradesFailed)
  1552  			}
  1553  			if tt.matchStats != nil {
  1554  				compareMatchStats(t, tt.matchStats, stats)
  1555  			}
  1556  		})
  1557  	}
  1558  }
  1559  func Test_shuffleQueue(t *testing.T) {
  1560  	// Setup the match package's logger.
  1561  	startLogger()
  1562  
  1563  	// order queues to be shuffled
  1564  	q3_1 := []*OrderRevealed{
  1565  		limitOrders[0],
  1566  		marketOrders[0],
  1567  		marketOrders[1],
  1568  	}
  1569  
  1570  	// q3_2 has same orders as q1 in different order
  1571  	q3_2 := []*OrderRevealed{
  1572  		marketOrders[0],
  1573  		limitOrders[0],
  1574  		marketOrders[1],
  1575  	}
  1576  
  1577  	// q3Shuffled is the expected result of sorting q3_1 and q3_2
  1578  	q3Shuffled := []*OrderRevealed{
  1579  		marketOrders[1],
  1580  		limitOrders[0],
  1581  		marketOrders[0],
  1582  	}
  1583  	q3Seed, _ := hex.DecodeString("18ad6e0777c50ce4ea64efcc1a39b8aea26bc96f78ebf163470e0495edd9fb80")
  1584  
  1585  	// shuffleQueue should work with nil slice
  1586  	var qNil []*OrderRevealed
  1587  
  1588  	// shuffleQueue should work with empty slice
  1589  	qEmpty := []*OrderRevealed{}
  1590  
  1591  	// shuffleQueue should work with single element slice
  1592  	q1 := []*OrderRevealed{marketOrders[0]}
  1593  	q1Seed, _ := hex.DecodeString("d5546f7fbbc4c7761c9a0fa986956f28208d55cb0bf2e132eae01362f51ee1ed")
  1594  
  1595  	// shuffleQueue should work with two element slice
  1596  	q2_a := []*OrderRevealed{
  1597  		limitOrders[0],
  1598  		marketOrders[0],
  1599  	}
  1600  
  1601  	// ... with same output regardless of input order
  1602  	q2_b := []*OrderRevealed{
  1603  		marketOrders[0],
  1604  		limitOrders[0],
  1605  	}
  1606  
  1607  	q2Shuffled := []*OrderRevealed{
  1608  		marketOrders[0],
  1609  		limitOrders[0],
  1610  	}
  1611  	q2Seed, _ := hex.DecodeString("fe51266c189dcc2f5413b62f5e7ef5015bb0d7305188813c5881c1747996ad6b")
  1612  
  1613  	tests := []struct {
  1614  		name     string
  1615  		inOut    []*OrderRevealed
  1616  		want     []*OrderRevealed
  1617  		wantSeed []byte
  1618  	}{
  1619  		{
  1620  			"q3_1 iter 1",
  1621  			q3_1,
  1622  			q3Shuffled,
  1623  			q3Seed,
  1624  		},
  1625  		{
  1626  			"q3_1 iter 2",
  1627  			q3_1,
  1628  			q3Shuffled,
  1629  			q3Seed,
  1630  		},
  1631  		{
  1632  			"q3_1 iter 3",
  1633  			q3_1,
  1634  			q3Shuffled,
  1635  			q3Seed,
  1636  		},
  1637  		{
  1638  			"q3_2",
  1639  			q3_2,
  1640  			q3Shuffled,
  1641  			q3Seed,
  1642  		},
  1643  		{
  1644  			"qEmpty",
  1645  			qEmpty,
  1646  			[]*OrderRevealed{},
  1647  			[]byte{},
  1648  		},
  1649  		{
  1650  			"qNil",
  1651  			qNil,
  1652  			[]*OrderRevealed(nil),
  1653  			[]byte{},
  1654  		},
  1655  		{
  1656  			"q1",
  1657  			q1,
  1658  			[]*OrderRevealed{q1[0]},
  1659  			q1Seed,
  1660  		},
  1661  		{
  1662  			"q2_a",
  1663  			q2_a,
  1664  			q2Shuffled,
  1665  			q2Seed,
  1666  		},
  1667  		{
  1668  			"q2_b",
  1669  			q2_b,
  1670  			q2Shuffled,
  1671  			q2Seed,
  1672  		},
  1673  	}
  1674  	for _, tt := range tests {
  1675  		t.Run(tt.name, func(t *testing.T) {
  1676  			seed := shuffleQueue(tt.inOut)
  1677  			if !reflect.DeepEqual(tt.inOut, tt.want) {
  1678  				t.Errorf("shuffleQueue(q): q = %#v, want %#v", tt.inOut, tt.want)
  1679  			}
  1680  			if !bytes.Equal(seed, tt.wantSeed) {
  1681  				t.Errorf("got seed %x, expected %x", seed, tt.wantSeed)
  1682  			}
  1683  		})
  1684  	}
  1685  }
  1686  
  1687  func Test_sortQueue(t *testing.T) {
  1688  	// Setup the match package's logger.
  1689  	startLogger()
  1690  
  1691  	// order queues to be sorted
  1692  	q3_1 := []*OrderRevealed{
  1693  		limitOrders[0],  // df8b93e65c03a7ec20f8ae8dce42ba132a613ec3d3848900209d46f515f2d46e
  1694  		marketOrders[0], // d793e1b4e34d86800ce6a207478d17b4cba73b2b65987b293a361b971697d45c
  1695  		marketOrders[1], // edf28bf4dc05fce563b1c2725aecec5edef5098c3a9d76fc71d6473ee2dc3fa5
  1696  	}
  1697  
  1698  	// q3_2 has same orders as q1 in different order
  1699  	q3_2 := []*OrderRevealed{
  1700  		marketOrders[0],
  1701  		limitOrders[0],
  1702  		marketOrders[1],
  1703  	}
  1704  
  1705  	// q3Sorted is the expected result of sorting q3_1 and q3_2
  1706  	q3Sorted := []*OrderRevealed{
  1707  		marketOrders[0], // #1: d793e1b4e34d86800ce6a207478d17b4cba73b2b65987b293a361b971697d45c
  1708  		limitOrders[0],  // #2: df8b93e65c03a7ec20f8ae8dce42ba132a613ec3d3848900209d46f515f2d46e
  1709  		marketOrders[1], // #3: edf28bf4dc05fce563b1c2725aecec5edef5098c3a9d76fc71d6473ee2dc3fa5
  1710  	}
  1711  
  1712  	// sortQueue should work with nil slice
  1713  	var qNil []*OrderRevealed
  1714  
  1715  	// sortQueue should work with empty slice
  1716  	qEmpty := []*OrderRevealed{}
  1717  
  1718  	// sortQueue should work with single element slice
  1719  	q1 := []*OrderRevealed{marketOrders[0]}
  1720  
  1721  	// sortQueue should work with two element slice
  1722  	q2_a := []*OrderRevealed{
  1723  		limitOrders[0],
  1724  		marketOrders[0],
  1725  	}
  1726  
  1727  	// ... with same output regardless of input order
  1728  	q2_b := []*OrderRevealed{
  1729  		marketOrders[0],
  1730  		limitOrders[0],
  1731  	}
  1732  
  1733  	q2Sorted := []*OrderRevealed{
  1734  		marketOrders[0],
  1735  		limitOrders[0],
  1736  	}
  1737  
  1738  	tests := []struct {
  1739  		name  string
  1740  		inOut []*OrderRevealed
  1741  		want  []*OrderRevealed
  1742  	}{
  1743  		{
  1744  			"q3_1 iter 1",
  1745  			q3_1,
  1746  			q3Sorted,
  1747  		},
  1748  		{
  1749  			"q3_1 iter 2",
  1750  			q3_1,
  1751  			q3Sorted,
  1752  		},
  1753  		{
  1754  			"q3_1 iter 3",
  1755  			q3_1,
  1756  			q3Sorted,
  1757  		},
  1758  		{
  1759  			"q3_2",
  1760  			q3_2,
  1761  			q3Sorted,
  1762  		},
  1763  		{
  1764  			"qEmpty",
  1765  			qEmpty,
  1766  			[]*OrderRevealed{},
  1767  		},
  1768  		{
  1769  			"qNil",
  1770  			qNil,
  1771  			[]*OrderRevealed(nil),
  1772  		},
  1773  		{
  1774  			"q1",
  1775  			q1,
  1776  			[]*OrderRevealed{q1[0]},
  1777  		},
  1778  		{
  1779  			"q2_a",
  1780  			q2_a,
  1781  			q2Sorted,
  1782  		},
  1783  		{
  1784  			"q2_b",
  1785  			q2_b,
  1786  			q2Sorted,
  1787  		},
  1788  	}
  1789  	for _, tt := range tests {
  1790  		t.Run(tt.name, func(t *testing.T) {
  1791  			sortQueueByID(tt.inOut)
  1792  			if !reflect.DeepEqual(tt.inOut, tt.want) {
  1793  				t.Errorf("sortQueueByID(q): q = %#v, want %#v", tt.inOut, tt.want)
  1794  			}
  1795  		})
  1796  	}
  1797  }
  1798  
  1799  func TestOrdersMatch(t *testing.T) {
  1800  	// Setup the match package's logger.
  1801  	startLogger()
  1802  
  1803  	type args struct {
  1804  		a order.Order
  1805  		b order.Order
  1806  	}
  1807  	tests := []struct {
  1808  		name string
  1809  		args args
  1810  		want bool
  1811  	}{
  1812  		{
  1813  			"MATCH market buy : limit sell",
  1814  			args{
  1815  				marketOrders[0].Order,
  1816  				limitOrders[1].Order,
  1817  			},
  1818  			true,
  1819  		},
  1820  		{
  1821  			"MATCH market sell : limit buy",
  1822  			args{
  1823  				marketOrders[1].Order,
  1824  				limitOrders[0].Order,
  1825  			},
  1826  			true,
  1827  		},
  1828  		{
  1829  			"MATCH limit sell : market buy",
  1830  			args{
  1831  				limitOrders[1].Order,
  1832  				marketOrders[0].Order,
  1833  			},
  1834  			true,
  1835  		},
  1836  		{
  1837  			"MATCH limit buy : market sell",
  1838  			args{
  1839  				limitOrders[0].Order,
  1840  				marketOrders[1].Order,
  1841  			},
  1842  			true,
  1843  		},
  1844  		{
  1845  			"NO MATCH market sell : market buy",
  1846  			args{
  1847  				marketOrders[1].Order,
  1848  				marketOrders[0].Order,
  1849  			},
  1850  			false,
  1851  		},
  1852  		{
  1853  			"NO MATCH (rates) limit sell : limit buy",
  1854  			args{
  1855  				limitOrders[0].Order,
  1856  				limitOrders[1].Order,
  1857  			},
  1858  			false,
  1859  		},
  1860  		{
  1861  			"NO MATCH (rates) limit buy : limit sell",
  1862  			args{
  1863  				limitOrders[1].Order,
  1864  				limitOrders[0].Order,
  1865  			},
  1866  			false,
  1867  		},
  1868  		{
  1869  			"MATCH (overlapping rates) limit sell : limit buy",
  1870  			args{
  1871  				limitOrders[1].Order,
  1872  				limitOrders[2].Order,
  1873  			},
  1874  			true,
  1875  		},
  1876  		{
  1877  			"MATCH (same rates) limit sell : limit buy",
  1878  			args{
  1879  				limitOrders[1].Order,
  1880  				limitOrders[3].Order,
  1881  			},
  1882  			true,
  1883  		},
  1884  		{
  1885  			"NO MATCH (same side) limit buy : limit buy",
  1886  			args{
  1887  				limitOrders[2].Order,
  1888  				limitOrders[3].Order,
  1889  			},
  1890  			false,
  1891  		},
  1892  		{
  1893  			"NO MATCH (cancel) market buy : cancel",
  1894  			args{
  1895  				marketOrders[0].Order,
  1896  				&order.CancelOrder{},
  1897  			},
  1898  			false,
  1899  		},
  1900  		{
  1901  			"NO MATCH (cancel) cancel : market sell",
  1902  			args{
  1903  				&order.CancelOrder{},
  1904  				marketOrders[1].Order,
  1905  			},
  1906  			false,
  1907  		},
  1908  		{
  1909  			"NO MATCH (cancel) limit sell : cancel",
  1910  			args{
  1911  				limitOrders[1].Order,
  1912  				&order.CancelOrder{},
  1913  			},
  1914  			false,
  1915  		},
  1916  	}
  1917  	for _, tt := range tests {
  1918  		t.Run(tt.name, func(t *testing.T) {
  1919  			if got := OrdersMatch(tt.args.a, tt.args.b); got != tt.want {
  1920  				t.Errorf("OrdersMatch() = %v, want %v", got, tt.want)
  1921  			}
  1922  		})
  1923  	}
  1924  }
  1925  
  1926  func TestBaseToQuote(t *testing.T) {
  1927  	type args struct {
  1928  		rate      uint64
  1929  		rateFloat float64
  1930  		base      uint64
  1931  	}
  1932  	tests := []struct {
  1933  		name      string
  1934  		args      args
  1935  		wantQuote uint64
  1936  	}{
  1937  		{
  1938  			name: "ok <1",
  1939  			args: args{
  1940  				rate:      1234132,
  1941  				rateFloat: 0.01234132,
  1942  				base:      4200000000,
  1943  			},
  1944  			wantQuote: 51833544,
  1945  		},
  1946  		{
  1947  			name: "ok 1",
  1948  			args: args{
  1949  				rate:      100000000,
  1950  				rateFloat: 1.0,
  1951  				base:      4200000000,
  1952  			},
  1953  			wantQuote: 4200000000,
  1954  		},
  1955  		{
  1956  			name: "ok >1",
  1957  			args: args{
  1958  				rate:      100000022,
  1959  				rateFloat: 1.00000022,
  1960  				base:      4200000000,
  1961  			},
  1962  			wantQuote: 4200000924,
  1963  		},
  1964  		{
  1965  			name: "ok >>1",
  1966  			args: args{
  1967  				rate:      19900000022,
  1968  				rateFloat: 199.00000022,
  1969  				base:      4200000000,
  1970  			},
  1971  			wantQuote: 835800000924,
  1972  		},
  1973  	}
  1974  	for _, tt := range tests {
  1975  		t.Run(tt.name, func(t *testing.T) {
  1976  			gotQuote := BaseToQuote(tt.args.rate, tt.args.base)
  1977  			if gotQuote != tt.wantQuote {
  1978  				t.Errorf("BaseToQuote() = %v, want %v", gotQuote, tt.wantQuote)
  1979  			}
  1980  			// quote2 := uint64(tt.args.rateFloat * float64(tt.args.base))
  1981  			// t.Logf("quote from integer rate = %d, from float rate = %d, diff = %d",
  1982  			// 	gotQuote, quote2, int64(gotQuote-quote2))
  1983  		})
  1984  	}
  1985  }
  1986  
  1987  func TestQuoteToBase(t *testing.T) {
  1988  	type args struct {
  1989  		rate      uint64
  1990  		rateFloat float64
  1991  		quote     uint64
  1992  	}
  1993  	tests := []struct {
  1994  		name     string
  1995  		args     args
  1996  		wantBase uint64
  1997  	}{
  1998  		{
  1999  			name: "ok <1",
  2000  			args: args{
  2001  				rate:      1234132,
  2002  				rateFloat: 0.01234132,
  2003  				quote:     51833544,
  2004  			},
  2005  			wantBase: 4200000000,
  2006  		},
  2007  		{
  2008  			name: "ok 1",
  2009  			args: args{
  2010  				rate:      100000000,
  2011  				rateFloat: 1.0,
  2012  				quote:     4200000000,
  2013  			},
  2014  			wantBase: 4200000000,
  2015  		},
  2016  		{
  2017  			name: "ok >1",
  2018  			args: args{
  2019  				rate:      100000022,
  2020  				rateFloat: 1.00000022,
  2021  				quote:     4200000924,
  2022  			},
  2023  			wantBase: 4200000000,
  2024  		},
  2025  		{
  2026  			name: "don't panic on 0 rate",
  2027  			args: args{
  2028  				rate:      0,
  2029  				rateFloat: 0,
  2030  				quote:     51833544,
  2031  			},
  2032  			wantBase: 0,
  2033  		},
  2034  		{
  2035  			name: "ok >>1",
  2036  			args: args{
  2037  				rate:      19900000022,
  2038  				rateFloat: 199.00000022,
  2039  				quote:     835800000924,
  2040  			},
  2041  			wantBase: 4200000000,
  2042  		},
  2043  	}
  2044  	for _, tt := range tests {
  2045  		t.Run(tt.name, func(t *testing.T) {
  2046  			gotBase := QuoteToBase(tt.args.rate, tt.args.quote)
  2047  			if gotBase != tt.wantBase {
  2048  				t.Errorf("QuoteToBase() = %v, want %v", gotBase, tt.wantBase)
  2049  			}
  2050  			// base2 := uint64(float64(tt.args.quote) / tt.args.rateFloat)
  2051  			// t.Logf("base2 from integer rate = %d, from float rate = %d, diff = %d",
  2052  			// 	gotBase, base2, int64(gotBase-base2))
  2053  		})
  2054  	}
  2055  }
  2056  
  2057  func TestQuoteToBaseToQuote(t *testing.T) {
  2058  	type args struct {
  2059  		rate      uint64
  2060  		rateFloat float64
  2061  		quote     uint64
  2062  	}
  2063  	tests := []struct {
  2064  		name string
  2065  		args args
  2066  	}{
  2067  		{
  2068  			name: "ok <1",
  2069  			args: args{
  2070  				rate:      1234132,
  2071  				rateFloat: 0.01234132,
  2072  				quote:     51833544,
  2073  			},
  2074  		},
  2075  		{
  2076  			name: "ok 1",
  2077  			args: args{
  2078  				rate:      100000000,
  2079  				rateFloat: 1.0,
  2080  				quote:     4200000000,
  2081  			},
  2082  		},
  2083  		{
  2084  			name: "ok >1",
  2085  			args: args{
  2086  				rate:      100000022,
  2087  				rateFloat: 1.00000022,
  2088  				quote:     4200000924,
  2089  			},
  2090  		},
  2091  		{
  2092  			name: "ok >>1",
  2093  			args: args{
  2094  				rate:      19900000022,
  2095  				rateFloat: 199.00000022,
  2096  				quote:     835800000924,
  2097  			},
  2098  		},
  2099  	}
  2100  	for _, tt := range tests {
  2101  		t.Run(tt.name, func(t *testing.T) {
  2102  			gotBase := QuoteToBase(tt.args.rate, tt.args.quote)
  2103  			gotQuote := BaseToQuote(tt.args.rate, gotBase)
  2104  			if gotQuote != tt.args.quote {
  2105  				t.Errorf("Failed quote->base->quote round trip. %d != %d",
  2106  					gotQuote, tt.args.quote)
  2107  			}
  2108  
  2109  			// baseFlt := float64(tt.args.quote) / tt.args.rateFloat
  2110  			// quoteFlt := baseFlt * tt.args.rateFloat
  2111  
  2112  			// t.Logf("expected quote = %d, final quote = %d, float quote = %f",
  2113  			// 	tt.args.quote, gotQuote, quoteFlt)
  2114  		})
  2115  	}
  2116  }
  2117  
  2118  func TestBaseToQuoteToBase(t *testing.T) {
  2119  	type args struct {
  2120  		rate      uint64
  2121  		rateFloat float64
  2122  		base      uint64
  2123  	}
  2124  	tests := []struct {
  2125  		name string
  2126  		args args
  2127  	}{
  2128  		{
  2129  			name: "ok <1",
  2130  			args: args{
  2131  				rate:      1234132,
  2132  				rateFloat: 0.01234132,
  2133  				base:      4200000000,
  2134  			},
  2135  		},
  2136  		{
  2137  			name: "ok 1",
  2138  			args: args{
  2139  				rate:      100000000,
  2140  				rateFloat: 1.0,
  2141  				base:      4200000000,
  2142  			},
  2143  		},
  2144  		{
  2145  			name: "ok >1",
  2146  			args: args{
  2147  				rate:      100000022,
  2148  				rateFloat: 1.00000022,
  2149  				base:      4200000000,
  2150  			},
  2151  		},
  2152  		{
  2153  			name: "ok >>1",
  2154  			args: args{
  2155  				rate:      19900000022,
  2156  				rateFloat: 199.00000022,
  2157  				base:      4200000000,
  2158  			},
  2159  		},
  2160  	}
  2161  	for _, tt := range tests {
  2162  		t.Run(tt.name, func(t *testing.T) {
  2163  			gotQuote := BaseToQuote(tt.args.rate, tt.args.base)
  2164  			gotBase := QuoteToBase(tt.args.rate, gotQuote)
  2165  			if gotBase != tt.args.base {
  2166  				t.Errorf("Failed base->quote->base round trip. %d != %d",
  2167  					gotBase, tt.args.base)
  2168  			}
  2169  
  2170  			// quoteFlt := float64(tt.args.base) * tt.args.rateFloat
  2171  			// baseFlt := quoteFlt / tt.args.rateFloat
  2172  
  2173  			// t.Logf("expected quote = %d, final quote = %d, float quote = %f",
  2174  			// 	tt.args.base, gotBase, baseFlt)
  2175  		})
  2176  	}
  2177  }
  2178  
  2179  func compareMatchStats(t *testing.T, sWant, sHave *MatchCycleStats) {
  2180  	t.Helper()
  2181  	if sWant.BookBuys != sHave.BookBuys {
  2182  		t.Errorf("wrong BookBuys. wanted %d, got %d", sWant.BookBuys, sHave.BookBuys)
  2183  	}
  2184  	// if sWant.BookBuys5 != sHave.BookBuys5 {
  2185  	// 	t.Errorf("wrong BookBuys5. wanted %d, got %d", sWant.BookBuys5, sHave.BookBuys5)
  2186  	// }
  2187  	// if sWant.BookBuys25 != sHave.BookBuys25 {
  2188  	// 	t.Errorf("wrong BookBuys25. wanted %d, got %d", sWant.BookBuys25, sHave.BookBuys25)
  2189  	// }
  2190  	if sWant.BookSells != sHave.BookSells {
  2191  		t.Errorf("wrong BookSells. wanted %d, got %d", sWant.BookSells, sHave.BookSells)
  2192  	}
  2193  	// if sWant.BookSells5 != sHave.BookSells5 {
  2194  	// 	t.Errorf("wrong BookSells5. wanted %d, got %d", sWant.BookSells5, sHave.BookSells5)
  2195  	// }
  2196  	// if sWant.BookSells25 != sHave.BookSells25 {
  2197  	// 	t.Errorf("wrong BookSells25. wanted %d, got %d", sWant.BookSells25, sHave.BookSells25)
  2198  	// }
  2199  	if sWant.MatchVolume != sHave.MatchVolume {
  2200  		t.Errorf("wrong MatchVolume. wanted %d, got %d", sWant.MatchVolume, sHave.MatchVolume)
  2201  	}
  2202  	if sWant.HighRate != sHave.HighRate {
  2203  		t.Errorf("wrong HighRate. wanted %d, got %d", sWant.HighRate, sHave.HighRate)
  2204  	}
  2205  	if sWant.LowRate != sHave.LowRate {
  2206  		t.Errorf("wrong LowRate. wanted %d, got %d", sWant.LowRate, sHave.LowRate)
  2207  	}
  2208  	if sWant.StartRate != sHave.StartRate {
  2209  		t.Errorf("wrong StartRate. wanted %d, got %d", sWant.StartRate, sHave.StartRate)
  2210  	}
  2211  	if sWant.EndRate != sHave.EndRate {
  2212  		t.Errorf("wrong EndRate. wanted %d, got %d", sWant.EndRate, sHave.EndRate)
  2213  	}
  2214  }
  2215  
  2216  func TestCSum(t *testing.T) {
  2217  	// This is just a smoke test to ensure a known result does not change. It
  2218  	// also corresponds to a csum used in market_test:
  2219  	//   a64ee6372a49f9465910ca0b556818dbc765f3c7fa21d5f40ab25bf4b73f45ed
  2220  	// and in client's epochqueue_test:
  2221  	//   8c743c3225b89ffbb50b5d766d3e078cd8e2658fa8cb6e543c4101e1d59a8e8e.
  2222  
  2223  	pi0B, _ := hex.DecodeString("e1f796fa0fc16ba7bb90be2a33e87c3d60ab628471a420834383661801bb0bfd")
  2224  	var pi0 order.Preimage
  2225  	copy(pi0[:], pi0B)
  2226  	fmt.Printf("%x\n", pi0)
  2227  	com0 := pi0.Commit() // aba75140b1f6edf26955a97e1b09d7b17abdc9c0b099fc73d9729501652fbf66
  2228  	lo0 := &order.LimitOrder{
  2229  		P: order.Prefix{
  2230  			Commit: com0,
  2231  		},
  2232  	}
  2233  
  2234  	pi1B, _ := hex.DecodeString("8e6c140071db1eb2f7a18194f1a045a94c078835c75dff2f3e836180baad9e95")
  2235  	var pi1 order.Preimage
  2236  	copy(pi1[:], pi1B)
  2237  	com1 := pi1.Commit() // 0f4bc030d392cef3f44d0781870ab7fcb78a0cda36c73e50b88c741b4f851600
  2238  	lo1 := &order.LimitOrder{
  2239  		P: order.Prefix{
  2240  			Commit: com1,
  2241  		},
  2242  	}
  2243  
  2244  	wantCSum, _ := hex.DecodeString("a64ee6372a49f9465910ca0b556818dbc765f3c7fa21d5f40ab25bf4b73f45ed")
  2245  	csum := CSum([]order.Order{lo0, lo1})
  2246  	if !bytes.Equal(wantCSum, csum) {
  2247  		t.Errorf("got csum %x, wanted %x", csum, wantCSum)
  2248  	}
  2249  
  2250  	// a third for the matching client-side test in epochqueue_test
  2251  	pi2B, _ := hex.DecodeString("e1f796fa0fc16ba7bb90be2a33e87c3d60ab628471a420834383661801bb0bfd")
  2252  	var pi2 order.Preimage
  2253  	copy(pi2[:], pi2B)
  2254  	com2 := pi2.Commit() // aba75140b1f6edf26955a97e1b09d7b17abdc9c0b099fc73d9729501652fbf66
  2255  	lo2 := &order.LimitOrder{
  2256  		P: order.Prefix{
  2257  			Commit: com2,
  2258  		},
  2259  	}
  2260  
  2261  	wantCSum, _ = hex.DecodeString("8c743c3225b89ffbb50b5d766d3e078cd8e2658fa8cb6e543c4101e1d59a8e8e")
  2262  	csum = CSum([]order.Order{lo0, lo1, lo2})
  2263  	if !bytes.Equal(wantCSum, csum) {
  2264  		t.Errorf("got csum %x, wanted %x", csum, wantCSum)
  2265  	}
  2266  }