@@ -367,3 +367,169 @@ impl fmt::Debug for Contract {
367
367
write ! ( f, "Contract({:?})" , Content :: from_bytes( self . as_bytes( ) ) . expect( "invariant" ) )
368
368
}
369
369
}
370
+
371
+ #[ cfg( test) ]
372
+ mod test {
373
+ use super :: * ;
374
+ use bitcoin:: hashes:: hex:: FromHex ;
375
+ use std:: str:: FromStr ;
376
+
377
+ /// A shorthand method for testing tether properties.
378
+ fn assert_has_tether_properties ( contract : & Contract ) {
379
+ assert_eq ! ( contract. precision( ) , 8 ) ;
380
+ assert_eq ! ( contract. ticker( ) , "USDt" . to_owned( ) ) ;
381
+
382
+ assert_eq ! ( contract. property( "name" ) . unwrap( ) , Some ( "Tether USD" . to_owned( ) ) ) ;
383
+ assert_eq ! ( contract. property( "issuer_pubkey" ) . unwrap( ) ,
384
+ Some ( bitcoin:: PublicKey :: from_str( "0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904" ) . unwrap( ) ) ,
385
+ ) ;
386
+
387
+ #[ derive( Debug , PartialEq , Eq , Deserialize ) ]
388
+ struct Entity {
389
+ pub domain : String ,
390
+ }
391
+ assert_eq ! ( contract. property( "entity" ) . unwrap( ) ,
392
+ Some ( Entity { domain: "tether.to" . into( ) } ) ,
393
+ ) ;
394
+ }
395
+
396
+ #[ test]
397
+ fn test_legacy_parsing ( ) {
398
+ let correct = r#"{"entity":{"domain":"tether.to"},"issuer_pubkey":"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","name":"Tether USD","precision":8,"ticker":"USDt","version":0}"# ;
399
+ assert ! ( Contract :: from_bytes( correct. as_bytes( ) ) . is_ok( ) ) ;
400
+
401
+ let invalid = [
402
+ // missing precision
403
+ r#"{"entity":{"domain":"tether.to"},"issuer_pubkey":"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","name":"Tether USD","ticker":"USDt","version":0}"# ,
404
+ // precision is string
405
+ r#"{"entity":{"domain":"tether.to"},"issuer_pubkey":"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","name":"Tether USD","precision":"no","ticker":"USDt","version":0}"# ,
406
+ // negative precision
407
+ r#"{"entity":{"domain":"tether.to"},"issuer_pubkey":"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","name":"Tether USD","precision":-2,"ticker":"USDt","version":0}"# ,
408
+ // too high precision
409
+ r#"{"entity":{"domain":"tether.to"},"issuer_pubkey":"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","name":"Tether USD","precision":9,"ticker":"USDt","version":0}"# ,
410
+ // missing ticker
411
+ r#"{"entity":{"domain":"tether.to"},"issuer_pubkey":"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","name":"Tether USD","precision":8,"version":0}"# ,
412
+ // ticker is int
413
+ r#"{"entity":{"domain":"tether.to"},"issuer_pubkey":"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","name":"Tether USD","precision":8,"ticker":8,"version":0}"# ,
414
+ // ticker too long
415
+ r#"{"entity":{"domain":"tether.to"},"issuer_pubkey":"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","name":"Tether USD","precision":8,"ticker":"USDtether","version":0}"# ,
416
+ ] ;
417
+ for json in & invalid {
418
+ assert ! ( Contract :: from_bytes( json. as_bytes( ) ) . is_err( ) , "invalid JSON was accepted: {}" , json) ;
419
+ }
420
+ }
421
+
422
+ #[ test]
423
+ fn test_tether ( ) {
424
+ let json = r#"{"entity":{"domain":"tether.to"},"issuer_pubkey":"0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904","name":"Tether USD","precision":8,"ticker":"USDt","version":0}"# ;
425
+ let tether_id = AssetId :: from_str ( "ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2" ) . unwrap ( ) ;
426
+ let tether_prevout = OutPoint :: from_str ( "9596d259270ef5bac0020435e6d859aea633409483ba64e232b8ba04ce288668:0" ) . unwrap ( ) ;
427
+ let tether_contract_hash = ContractHash :: from_hex ( "3c7f0a53c2ff5b99590620d7f6604a7a3a7bfbaaa6aa61f7bfc7833ca03cde82" ) . unwrap ( ) ;
428
+
429
+ let contract = Contract :: from_bytes ( json. as_bytes ( ) ) . unwrap ( ) ;
430
+ assert_eq ! ( contract. contract_hash( ) , tether_contract_hash) ;
431
+ assert_eq ! ( contract. asset_id( tether_prevout) , tether_id) ;
432
+ assert_has_tether_properties ( & contract) ;
433
+ }
434
+
435
+ #[ test]
436
+ fn test_create_cbor ( ) {
437
+ let details = ContractDetails {
438
+ precision : 8 ,
439
+ ticker : "USDt" . into ( ) ,
440
+ name : Some ( "Tether USD" . into ( ) ) ,
441
+ entity : Some ( ContractDetailsEntity {
442
+ domain : Some ( "tether.to" . into ( ) ) ,
443
+ } ) ,
444
+ issuer_pubkey : Some ( "0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904" . parse ( ) . unwrap ( ) ) ,
445
+ } ;
446
+ let mut extra = BTreeMap :: new ( ) ;
447
+ extra. insert ( "foo" . to_owned ( ) , "bar" . to_owned ( ) . into ( ) ) ;
448
+ let contract = Contract :: from_details ( details. clone ( ) , extra. clone ( ) ) . unwrap ( ) ;
449
+
450
+ assert_has_tether_properties ( & contract) ;
451
+ assert_eq ! ( contract. property( "foo" ) . unwrap( ) , Some ( "bar" . to_owned( ) ) ) ;
452
+
453
+ // Some wrong values
454
+ let mut det = details. clone ( ) ;
455
+ det. precision = 9 ;
456
+ assert ! ( Contract :: from_details( det, extra. clone( ) ) . is_err( ) ) ;
457
+
458
+ let mut det = details. clone ( ) ;
459
+ det. ticker = "TICKER" . into ( ) ;
460
+ assert ! ( Contract :: from_details( det, extra. clone( ) ) . is_err( ) ) ;
461
+
462
+ let mut ex = extra. clone ( ) ;
463
+ ex. insert ( "name" . to_owned ( ) , "Not Tether USD" . to_owned ( ) . into ( ) ) ;
464
+ assert ! ( Contract :: from_details( details. clone( ) , ex) . is_err( ) ) ;
465
+ }
466
+
467
+ #[ test]
468
+ #[ allow( deprecated) ]
469
+ fn test_create_legacy ( ) {
470
+ let details = ContractDetails {
471
+ precision : 8 ,
472
+ ticker : "USDt" . into ( ) ,
473
+ name : Some ( "Tether USD" . into ( ) ) ,
474
+ entity : Some ( ContractDetailsEntity {
475
+ domain : Some ( "tether.to" . into ( ) ) ,
476
+ } ) ,
477
+ issuer_pubkey : Some ( "0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904" . parse ( ) . unwrap ( ) ) ,
478
+ } ;
479
+ let mut extra = BTreeMap :: new ( ) ;
480
+ extra. insert ( "foo" . to_owned ( ) , "bar" . into ( ) ) ;
481
+ let contract = Contract :: legacy_from_details ( details. clone ( ) , extra. clone ( ) ) . unwrap ( ) ;
482
+
483
+ assert_has_tether_properties ( & contract) ;
484
+ assert_eq ! ( contract. property( "foo" ) . unwrap( ) , Some ( "bar" . to_owned( ) ) ) ;
485
+
486
+ // Some wrong values
487
+ let mut det = details. clone ( ) ;
488
+ det. precision = 9 ;
489
+ assert ! ( Contract :: legacy_from_details( det, extra. clone( ) ) . is_err( ) ) ;
490
+
491
+ let mut det = details. clone ( ) ;
492
+ det. ticker = "TICKER" . into ( ) ;
493
+ assert ! ( Contract :: legacy_from_details( det, extra. clone( ) ) . is_err( ) ) ;
494
+
495
+ let mut ex = extra. clone ( ) ;
496
+ ex. insert ( "name" . to_owned ( ) , "Not Tether USD" . into ( ) ) ;
497
+ assert ! ( Contract :: legacy_from_details( details. clone( ) , ex) . is_err( ) ) ;
498
+ }
499
+
500
+ #[ test]
501
+ fn test_cbor_wip ( ) {
502
+ #[ derive( Debug , PartialEq , Eq , Deserialize , Serialize ) ]
503
+ struct Entity {
504
+ pub domain : String ,
505
+ }
506
+ #[ derive( Debug , Serialize ) ]
507
+ struct ContractExtraContent {
508
+ pub entity : Entity ,
509
+ pub name : String ,
510
+ pub issuer_pubkey : bitcoin:: PublicKey ,
511
+ }
512
+
513
+ let extra = ContractExtraContent {
514
+ entity : Entity {
515
+ domain : "tether.to" . into ( ) ,
516
+ } ,
517
+ name : "Tether USD" . into ( ) ,
518
+ issuer_pubkey : "0337cceec0beea0232ebe14cba0197a9fbd45fcf2ec946749de920e71434c2b904" . parse ( ) . unwrap ( ) ,
519
+ } ;
520
+ let cbor_content: Vec < serde_cbor:: Value > = vec ! [
521
+ 8 . into( ) ,
522
+ "USDt" . to_owned( ) . into( ) ,
523
+ //TODO(stevenroose) optimize this as serde_cbor gets to_value
524
+ serde_cbor:: from_slice:: <serde_cbor:: Value >( & serde_cbor:: to_vec( & extra) . unwrap( ) ) . unwrap( ) ,
525
+ ] ;
526
+
527
+ // version byte
528
+ let mut buffer = vec ! [ CONTRACT_VERSION_CBOR ] ;
529
+ serde_cbor:: to_writer ( & mut buffer, & cbor_content) . unwrap ( ) ;
530
+ let contract = Contract :: from_bytes ( & buffer) . unwrap ( ) ;
531
+
532
+ assert_eq ! ( contract. contract_hash( ) , ContractHash :: hash( & buffer) ) ;
533
+ assert_has_tether_properties ( & contract) ;
534
+ }
535
+ }
0 commit comments