@@ -367,3 +367,169 @@ impl fmt::Debug for Contract {
367367 write ! ( f, "Contract({:?})" , Content :: from_bytes( self . as_bytes( ) ) . expect( "invariant" ) )
368368 }
369369}
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