decred.org/dcrdex@v1.0.5/dex/order/test/helpers.go (about)

     1  package test
     2  
     3  import (
     4  	"bytes"
     5  	crand "crypto/rand"
     6  	"encoding/binary"
     7  	"fmt"
     8  	"math/rand"
     9  	"time"
    10  
    11  	"decred.org/dcrdex/dex/order"
    12  	"decred.org/dcrdex/server/account"
    13  )
    14  
    15  const (
    16  	baseClientTime = 1566497653
    17  	baseServerTime = 1566497656
    18  	b58Set         = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
    19  	addressLength  = 34
    20  )
    21  
    22  var rnd = rand.New(CryptoSource)
    23  
    24  func UseRand(use *rand.Rand) {
    25  	rnd = use
    26  }
    27  
    28  var CryptoSource cryptoSource
    29  
    30  type cryptoSource struct{}
    31  
    32  func (cs cryptoSource) Uint64() uint64 {
    33  	var b [8]byte
    34  	crand.Read(b[:])
    35  	return binary.LittleEndian.Uint64(b[:])
    36  }
    37  
    38  func (cs cryptoSource) Int63() int64 {
    39  	const rngMask = 1<<63 - 1
    40  	return int64(cs.Uint64() & rngMask)
    41  }
    42  
    43  func (cs cryptoSource) Seed(seed int64) { /* no-op */ }
    44  
    45  var _ rand.Source = cryptoSource{}
    46  var _ rand.Source = (*cryptoSource)(nil)
    47  var _ rand.Source64 = cryptoSource{}
    48  var _ rand.Source64 = (*cryptoSource)(nil)
    49  
    50  var (
    51  	acctTemplate = account.AccountID{
    52  		0x22, 0x4c, 0xba, 0xaa, 0xfa, 0x80, 0xbf, 0x3b, 0xd1, 0xff, 0x73, 0x15,
    53  		0x90, 0xbc, 0xbd, 0xda, 0x5a, 0x76, 0xf9, 0x1e, 0x60, 0xa1, 0x56, 0x99,
    54  		0x46, 0x34, 0xe9, 0x1c, 0xaa, 0xaa, 0xaa, 0xaa,
    55  	}
    56  	acctCounter uint32
    57  )
    58  
    59  func randBytes(l int) []byte {
    60  	b := make([]byte, l)
    61  	rnd.Read(b)
    62  	return b
    63  }
    64  
    65  func randUint32() uint32 { return uint32(rnd.Int31()) }
    66  func randUint64() uint64 { return uint64(rnd.Int63()) }
    67  
    68  func randBool() bool {
    69  	return rnd.Intn(2) == 1
    70  }
    71  
    72  // A random base-58 string.
    73  func RandomAddress() string {
    74  	b := make([]byte, addressLength)
    75  	for i := range b {
    76  		b[i] = b58Set[rnd.Intn(58)]
    77  	}
    78  	return string(b)
    79  }
    80  
    81  // NextAccount gets a unique account ID.
    82  func NextAccount() account.AccountID {
    83  	acctCounter++
    84  	intBytes := make([]byte, 4)
    85  	binary.BigEndian.PutUint32(intBytes, acctCounter)
    86  	acctID := account.AccountID{}
    87  	copy(acctID[:], acctTemplate[:])
    88  	copy(acctID[account.HashSize-4:], intBytes)
    89  	return acctID
    90  }
    91  
    92  // RandomOrderID creates a random order ID.
    93  func RandomOrderID() order.OrderID {
    94  	var oid order.OrderID
    95  	copy(oid[:], randBytes(order.OrderIDSize))
    96  	return oid
    97  }
    98  
    99  // RandomMatchID creates a random match ID.
   100  func RandomMatchID() order.MatchID {
   101  	var mid order.MatchID
   102  	copy(mid[:], randBytes(order.MatchIDSize))
   103  	return mid
   104  }
   105  
   106  // RandomPreimage creates a random order preimage.
   107  func RandomPreimage() (pi order.Preimage) {
   108  	rnd.Read(pi[:])
   109  	return
   110  }
   111  
   112  // RandomCommitment creates a random order commitment. Use RandomPreimage
   113  // followed by Preimage.Commit() if you require a matching commitment.
   114  func RandomCommitment() (com order.Commitment) {
   115  	rnd.Read(com[:])
   116  	return
   117  }
   118  
   119  // Writer represents a client that places orders on one side of a market.
   120  type Writer struct {
   121  	Addr   string
   122  	Acct   account.AccountID
   123  	Sell   bool
   124  	Market *Market
   125  }
   126  
   127  // RandomWriter creates a random Writer.
   128  func RandomWriter() *Writer {
   129  	return &Writer{
   130  		Addr: RandomAddress(),
   131  		Acct: NextAccount(),
   132  		Sell: randBool(),
   133  		Market: &Market{
   134  			Base:    randUint32(),
   135  			Quote:   randUint32(),
   136  			LotSize: randUint64(),
   137  		},
   138  	}
   139  }
   140  
   141  // Market is a exchange market.
   142  type Market struct {
   143  	Base    uint32
   144  	Quote   uint32
   145  	LotSize uint64
   146  }
   147  
   148  // WriteLimitOrder creates a limit order with the specified writer and order
   149  // values.
   150  func WriteLimitOrder(writer *Writer, rate, lots uint64, force order.TimeInForce, timeOffset int64) (*order.LimitOrder, order.Preimage) {
   151  	pi := RandomPreimage()
   152  	return &order.LimitOrder{
   153  		P: order.Prefix{
   154  			AccountID:  writer.Acct,
   155  			BaseAsset:  writer.Market.Base,
   156  			QuoteAsset: writer.Market.Quote,
   157  			OrderType:  order.LimitOrderType,
   158  			ClientTime: time.Unix(baseClientTime+timeOffset, 0),
   159  			ServerTime: time.Unix(baseServerTime+timeOffset, 0),
   160  			Commit:     pi.Commit(),
   161  		},
   162  		T: order.Trade{
   163  			Coins:    []order.CoinID{},
   164  			Sell:     writer.Sell,
   165  			Quantity: lots * writer.Market.LotSize,
   166  			Address:  writer.Addr,
   167  		},
   168  		Rate:  rate,
   169  		Force: force,
   170  	}, pi
   171  }
   172  
   173  // RandomLimitOrder creates a random limit order with a random writer.
   174  func RandomLimitOrder() (*order.LimitOrder, order.Preimage) {
   175  	return WriteLimitOrder(RandomWriter(), randUint64(), randUint64(), order.TimeInForce(rnd.Intn(2)), 0)
   176  }
   177  
   178  // WriteMarketOrder creates a market order with the specified writer and
   179  // quantity.
   180  func WriteMarketOrder(writer *Writer, lots uint64, timeOffset int64) (*order.MarketOrder, order.Preimage) {
   181  	if writer.Sell {
   182  		lots *= writer.Market.LotSize
   183  	}
   184  	pi := RandomPreimage()
   185  	return &order.MarketOrder{
   186  		P: order.Prefix{
   187  			AccountID:  writer.Acct,
   188  			BaseAsset:  writer.Market.Base,
   189  			QuoteAsset: writer.Market.Quote,
   190  			OrderType:  order.MarketOrderType,
   191  			ClientTime: time.Unix(baseClientTime+timeOffset, 0).UTC(),
   192  			ServerTime: time.Unix(baseServerTime+timeOffset, 0).UTC(),
   193  			Commit:     pi.Commit(),
   194  		},
   195  		T: order.Trade{
   196  			Coins:    []order.CoinID{},
   197  			Sell:     writer.Sell,
   198  			Quantity: lots,
   199  			Address:  writer.Addr,
   200  		},
   201  	}, pi
   202  }
   203  
   204  // RandomMarketOrder creates a random market order with a random writer.
   205  func RandomMarketOrder() (*order.MarketOrder, order.Preimage) {
   206  	return WriteMarketOrder(RandomWriter(), randUint64(), 0)
   207  }
   208  
   209  // WriteCancelOrder creates a cancel order with the specified order ID.
   210  func WriteCancelOrder(writer *Writer, targetOrderID order.OrderID, timeOffset int64) (*order.CancelOrder, order.Preimage) {
   211  	pi := RandomPreimage()
   212  	return &order.CancelOrder{
   213  		P: order.Prefix{
   214  			AccountID:  writer.Acct,
   215  			BaseAsset:  writer.Market.Base,
   216  			QuoteAsset: writer.Market.Quote,
   217  			OrderType:  order.CancelOrderType,
   218  			ClientTime: time.Unix(baseClientTime+timeOffset, 0).UTC(),
   219  			ServerTime: time.Unix(baseServerTime+timeOffset, 0).UTC(),
   220  			Commit:     pi.Commit(),
   221  		},
   222  		TargetOrderID: targetOrderID,
   223  	}, pi
   224  }
   225  
   226  // RandomCancelOrder creates a random cancel order with a random writer.
   227  func RandomCancelOrder() (*order.CancelOrder, order.Preimage) {
   228  	return WriteCancelOrder(RandomWriter(), RandomOrderID(), 0)
   229  }
   230  
   231  // ComparePrefix compares the prefixes field-by-field and returns an error if a
   232  // mismatch is found.
   233  func ComparePrefix(p1, p2 *order.Prefix) error {
   234  	if !bytes.Equal(p1.AccountID[:], p2.AccountID[:]) {
   235  		return fmt.Errorf("account ID mismatch. %x != %x", p1.AccountID[:], p2.AccountID[:])
   236  	}
   237  	if p1.BaseAsset != p2.BaseAsset {
   238  		return fmt.Errorf("base asset mismatch. %d != %d", p1.BaseAsset, p2.BaseAsset)
   239  	}
   240  	if p1.QuoteAsset != p2.QuoteAsset {
   241  		return fmt.Errorf("quote asset mismatch. %d != %d", p1.QuoteAsset, p2.QuoteAsset)
   242  	}
   243  	if p1.OrderType != p2.OrderType {
   244  		return fmt.Errorf("order type mismatch. %d != %d", p1.OrderType, p2.OrderType)
   245  	}
   246  	if !p1.ClientTime.Equal(p2.ClientTime) {
   247  		return fmt.Errorf("client time mismatch. %s != %s", p1.ClientTime, p2.ClientTime)
   248  	}
   249  	if !p1.ServerTime.Equal(p2.ServerTime) {
   250  		return fmt.Errorf("server time mismatch. %s != %s", p1.ServerTime, p2.ServerTime)
   251  	}
   252  	return nil
   253  }
   254  
   255  // CompareTrade compares the MarketOrders field-by-field and returns an error if
   256  // a mismatch is found.
   257  func CompareTrade(t1, t2 *order.Trade) error {
   258  	if len(t1.Coins) != len(t2.Coins) {
   259  		return fmt.Errorf("coin length mismatch. %d != %d", len(t1.Coins), len(t2.Coins))
   260  	}
   261  	for i, coin := range t2.Coins {
   262  		reCoin := t1.Coins[i]
   263  		if !bytes.Equal(reCoin, coin) {
   264  			return fmt.Errorf("coins %d not equal. %x != %x", i, coin, reCoin)
   265  		}
   266  	}
   267  	if t1.Sell != t2.Sell {
   268  		return fmt.Errorf("sell mismatch. %t != %t", t1.Sell, t2.Sell)
   269  	}
   270  	if t1.Quantity != t2.Quantity {
   271  		return fmt.Errorf("quantity mismatch. %d != %d", t1.Quantity, t2.Quantity)
   272  	}
   273  	if t1.Address != t2.Address {
   274  		return fmt.Errorf("address mismatch. %s != %s", t1.Address, t2.Address)
   275  	}
   276  	return nil
   277  }
   278  
   279  // RandomUserMatch creates a random UserMatch.
   280  func RandomUserMatch() *order.UserMatch {
   281  	return &order.UserMatch{
   282  		OrderID:     RandomOrderID(),
   283  		MatchID:     RandomMatchID(),
   284  		Quantity:    randUint64(),
   285  		Rate:        randUint64(),
   286  		Address:     RandomAddress(),
   287  		Status:      order.MatchStatus(rnd.Intn(5)),
   288  		Side:        order.MatchSide(rnd.Intn(2)),
   289  		FeeRateSwap: randUint64(),
   290  	}
   291  }
   292  
   293  // CompareUserMatch compares the UserMatches field-by-field and returns an error
   294  // if a mismatch is found.
   295  func CompareUserMatch(m1, m2 *order.UserMatch) error {
   296  	if !bytes.Equal(m1.OrderID[:], m2.OrderID[:]) {
   297  		return fmt.Errorf("OrderID mismatch. %s != %s", m1.OrderID, m2.OrderID)
   298  	}
   299  	if !bytes.Equal(m1.MatchID[:], m2.MatchID[:]) {
   300  		return fmt.Errorf("MatchID mismatch. %s != %s", m1.MatchID, m2.MatchID)
   301  	}
   302  	if m1.Quantity != m2.Quantity {
   303  		return fmt.Errorf("Quantity mismatch. %d != %d", m1.Quantity, m2.Quantity)
   304  	}
   305  	if m1.Rate != m2.Rate {
   306  		return fmt.Errorf("Rate mismatch. %d != %d", m1.Rate, m2.Rate)
   307  	}
   308  	if m1.Address != m2.Address {
   309  		return fmt.Errorf("Address mismatch. %s != %s", m1.Address, m2.Address)
   310  	}
   311  	if m1.Status != m2.Status {
   312  		return fmt.Errorf("Status mismatch. %d != %d", m1.Status, m2.Status)
   313  	}
   314  	if m1.Side != m2.Side {
   315  		return fmt.Errorf("Side mismatch. %d != %d", m1.Side, m2.Side)
   316  	}
   317  	if m1.FeeRateSwap != m2.FeeRateSwap {
   318  		return fmt.Errorf("FeeRateSwap mismatch. %d != %d", m1.FeeRateSwap, m2.FeeRateSwap)
   319  	}
   320  	return nil
   321  }
   322  
   323  type testKiller interface {
   324  	Helper()
   325  	Fatalf(string, ...any)
   326  }
   327  
   328  // MustComparePrefix compares the Prefix field-by-field and calls the Fatalf
   329  // method on the supplied testKiller if a mismatch is encountered.
   330  func MustComparePrefix(t testKiller, p1, p2 *order.Prefix) {
   331  	t.Helper()
   332  	err := ComparePrefix(p1, p2)
   333  	if err != nil {
   334  		t.Fatalf("%v", err)
   335  	}
   336  }
   337  
   338  // MustCompareTrade compares the MarketOrders field-by-field and calls the
   339  // Fatalf method on the supplied testKiller if a mismatch is encountered.
   340  func MustCompareTrade(t testKiller, t1, t2 *order.Trade) {
   341  	t.Helper()
   342  	err := CompareTrade(t1, t2)
   343  	if err != nil {
   344  		t.Fatalf("%v", err)
   345  	}
   346  }
   347  
   348  // MustCompareUserMatch compares the UserMatches field-by-field and calls the
   349  // Fatalf method on the supplied testKiller if a mismatch is encountered.
   350  func MustCompareUserMatch(t testKiller, m1, m2 *order.UserMatch) {
   351  	t.Helper()
   352  	err := CompareUserMatch(m1, m2)
   353  	if err != nil {
   354  		t.Fatalf("%v", err)
   355  	}
   356  }
   357  
   358  // MustCompareUserMatch compares the LimitOrders field-by-field and calls the
   359  // Fatalf method on the supplied testKiller if a mismatch is encountered.
   360  func MustCompareLimitOrders(t testKiller, l1, l2 *order.LimitOrder) {
   361  	t.Helper()
   362  	MustComparePrefix(t, &l1.P, &l2.P)
   363  	MustCompareTrade(t, &l1.T, &l2.T)
   364  	if l1.Rate != l2.Rate {
   365  		t.Fatalf("rate mismatch. %d != %d", l1.Rate, l2.Rate)
   366  	}
   367  	if l1.Force != l2.Force {
   368  		t.Fatalf("time-in-force mismatch. %d != %d", l1.Force, l2.Force)
   369  	}
   370  }
   371  
   372  // MustCompareMarketOrders compares the MarketOrders field-by-field and calls
   373  // the Fatalf method on the supplied testKiller if a mismatch is encountered.
   374  func MustCompareMarketOrders(t testKiller, m1, m2 *order.MarketOrder) {
   375  	MustComparePrefix(t, &m1.P, &m2.P)
   376  	MustCompareTrade(t, &m1.T, &m2.T)
   377  }
   378  
   379  // MustCompareCancelOrders compares the CancelOrders field-by-field and calls
   380  // the Fatalf method on the supplied testKiller if a mismatch is encountered.
   381  func MustCompareCancelOrders(t testKiller, c1, c2 *order.CancelOrder) {
   382  	t.Helper()
   383  	MustComparePrefix(t, &c1.P, &c2.P)
   384  	if !bytes.Equal(c1.TargetOrderID[:], c2.TargetOrderID[:]) {
   385  		t.Fatalf("wrong target order ID. wanted %s, got %s", c1.TargetOrderID, c2.TargetOrderID)
   386  	}
   387  }
   388  
   389  // MustCompareOrders compares the Orders field-by-field and calls
   390  // the Fatalf method on the supplied testKiller if a mismatch is encountered.
   391  func MustCompareOrders(t testKiller, o1, o2 order.Order) {
   392  	t.Helper()
   393  	switch ord1 := o1.(type) {
   394  	case *order.LimitOrder:
   395  		ord2, ok := o2.(*order.LimitOrder)
   396  		if !ok {
   397  			t.Fatalf("first order was a limit order, but second order was not")
   398  		}
   399  		MustCompareLimitOrders(t, ord1, ord2)
   400  	case *order.MarketOrder:
   401  		ord2, ok := o2.(*order.MarketOrder)
   402  		if !ok {
   403  			t.Fatalf("first order was a market order, but second order was not")
   404  		}
   405  		MustCompareMarketOrders(t, ord1, ord2)
   406  	case *order.CancelOrder:
   407  		ord2, ok := o2.(*order.CancelOrder)
   408  		if !ok {
   409  			t.Fatalf("first order was a cancel order, but second order was not")
   410  		}
   411  		MustCompareCancelOrders(t, ord1, ord2)
   412  	default:
   413  		t.Fatalf("Unknown order type")
   414  	}
   415  
   416  }