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 }