Skip to content

Commit dfc278f

Browse files
committed
Unit tests for contract module
1 parent 4adaa8f commit dfc278f

File tree

2 files changed

+167
-0
lines changed

2 files changed

+167
-0
lines changed

Diff for: Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,4 @@ serde_json = { version = "<=1.0.44", optional = true }
3434
[dev-dependencies]
3535
rand = "0.6.5"
3636
serde_json = "<=1.0.44"
37+
bitcoin = { version = "0.23", features = ["use-serde"] }

Diff for: src/contracts.rs

+166
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)