use std::sync::Arc;
use freenet_stdlib::prelude::{
ContractInstanceId, RelatedContracts, State, UpdateData, UpdateModification, ValidateResult,
};
use super::evidence::{
ConformanceEvidence, EvidenceRejected, MAX_EVIDENCE_INPUT_BYTES, MAX_EVIDENCE_RELATED,
};
use super::generator::{Corpus, GeneratorConfig, generate_cases};
use super::oracle::{ConformanceOracle, OracleError};
use super::property::{
ConformanceProperty, Inconclusive, PremiseSource, PropertyOutcome, Severity,
};
use super::verifier::{Bytes, ConformanceCase, verify_case};
type ValidateFn = Box<dyn FnMut(&[u8]) -> Result<ValidateResult, OracleError>>;
type BinaryFn = Box<dyn FnMut(&[u8], &[u8]) -> Result<Vec<u8>, OracleError>>;
type UnaryFn = Box<dyn FnMut(&[u8]) -> Result<Vec<u8>, OracleError>>;
struct Fake {
validate: ValidateFn,
merge: BinaryFn,
apply: BinaryFn,
summarize: UnaryFn,
delta: BinaryFn,
}
impl Fake {
fn conforming() -> Self {
Self {
validate: Box::new(|state| {
if is_canonical(state) {
Ok(ValidateResult::Valid)
} else {
Ok(ValidateResult::Invalid)
}
}),
merge: Box::new(|a, b| Ok(union(a, b))),
apply: Box::new(|a, d| Ok(union(a, d))),
summarize: Box::new(|a| Ok(a.to_vec())),
delta: Box::new(|a, summary| Ok(difference(a, summary))),
}
}
fn merging(
mut self,
f: impl FnMut(&[u8], &[u8]) -> Result<Vec<u8>, OracleError> + 'static,
) -> Self {
self.merge = Box::new(f);
self
}
fn applying(
mut self,
f: impl FnMut(&[u8], &[u8]) -> Result<Vec<u8>, OracleError> + 'static,
) -> Self {
self.apply = Box::new(f);
self
}
fn summarizing(
mut self,
f: impl FnMut(&[u8]) -> Result<Vec<u8>, OracleError> + 'static,
) -> Self {
self.summarize = Box::new(f);
self
}
fn deltaing(
mut self,
f: impl FnMut(&[u8], &[u8]) -> Result<Vec<u8>, OracleError> + 'static,
) -> Self {
self.delta = Box::new(f);
self
}
fn validating(
mut self,
f: impl FnMut(&[u8]) -> Result<ValidateResult, OracleError> + 'static,
) -> Self {
self.validate = Box::new(f);
self
}
}
impl ConformanceOracle for Fake {
fn validate_state(
&mut self,
state: &[u8],
_related: &RelatedContracts<'_>,
) -> Result<ValidateResult, OracleError> {
(self.validate)(state)
}
fn update_state(
&mut self,
state: &[u8],
updates: &[UpdateData<'_>],
) -> Result<UpdateModification<'static>, OracleError> {
let mut current = state.to_vec();
for update in updates {
current = match update {
UpdateData::State(incoming) => (self.merge)(¤t, incoming.as_ref())?,
UpdateData::Delta(delta) => (self.apply)(¤t, delta.as_ref())?,
other @ (UpdateData::StateAndDelta { .. }
| UpdateData::RelatedState { .. }
| UpdateData::RelatedDelta { .. }
| UpdateData::RelatedStateAndDelta { .. })
| other => {
return Err(OracleError::runtime(format!(
"fake contract does not handle {other:?}"
)));
}
};
}
Ok(UpdateModification::valid(State::from(current)))
}
fn summarize_state(&mut self, state: &[u8]) -> Result<Vec<u8>, OracleError> {
(self.summarize)(state)
}
fn get_state_delta(&mut self, state: &[u8], summary: &[u8]) -> Result<Vec<u8>, OracleError> {
(self.delta)(state, summary)
}
}
fn is_canonical(state: &[u8]) -> bool {
state.windows(2).all(|w| w[0] < w[1])
}
fn union(a: &[u8], b: &[u8]) -> Vec<u8> {
let mut out: Vec<u8> = a.iter().chain(b.iter()).copied().collect();
out.sort_unstable();
out.dedup();
out
}
fn difference(a: &[u8], b: &[u8]) -> Vec<u8> {
a.iter().copied().filter(|x| !b.contains(x)).collect()
}
fn bytes(values: &[u8]) -> Bytes {
Arc::from(values)
}
fn case(property: ConformanceProperty, states: &[&[u8]]) -> ConformanceCase {
ConformanceCase::new(property, states.iter().map(|s| bytes(s)).collect())
}
#[track_caller]
fn assert_violates(outcome: PropertyOutcome, property: ConformanceProperty) {
match outcome {
PropertyOutcome::Violated(v) => assert_eq!(v.property, property, "wrong property flagged"),
other @ (PropertyOutcome::Holds | PropertyOutcome::Inconclusive(_)) => {
panic!("expected a {property} violation, got {other:?}")
}
}
}
#[track_caller]
fn assert_holds(outcome: PropertyOutcome) {
assert_eq!(outcome, PropertyOutcome::Holds, "expected the law to hold");
}
#[track_caller]
fn assert_inconclusive(outcome: PropertyOutcome, expected: Inconclusive) {
match outcome {
PropertyOutcome::Inconclusive(reason) => assert_eq!(reason, expected),
other @ (PropertyOutcome::Holds | PropertyOutcome::Violated(_)) => {
panic!("expected inconclusive ({expected}), got {other:?}")
}
}
}
#[test]
fn conforming_contract_satisfies_every_state_law() {
let mut fake = Fake::conforming();
let (a, b, c): (&[u8], &[u8], &[u8]) = (&[1, 2], &[2, 3], &[4]);
assert_holds(verify_case(
&mut fake,
&case(ConformanceProperty::StateIdempotence, &[a]),
));
assert_holds(verify_case(
&mut fake,
&case(ConformanceProperty::StateCommutativity, &[a, b]),
));
assert_holds(verify_case(
&mut fake,
&case(ConformanceProperty::StateAssociativity, &[a, b, c]),
));
assert_holds(verify_case(
&mut fake,
&case(ConformanceProperty::EmittedStateValidity, &[a, b]),
));
assert_holds(verify_case(
&mut fake,
&case(ConformanceProperty::UpdateDeterminism, &[a, b]),
));
assert_holds(verify_case(
&mut fake,
&case(ConformanceProperty::SummaryDeterminism, &[a]),
));
assert_holds(verify_case(
&mut fake,
&case(ConformanceProperty::DeltaDeterminism, &[a]),
));
assert_holds(verify_case(
&mut fake,
&case(ConformanceProperty::SelfDeltaEmpty, &[a]),
));
assert_holds(verify_case(
&mut fake,
&case(ConformanceProperty::ReconciliationCycle, &[a, b]),
));
assert_holds(verify_case(
&mut fake,
&case(ConformanceProperty::PathAgreement, &[a, b]),
));
assert_holds(verify_case(
&mut fake,
&case(
ConformanceProperty::TransitionPathAgreement,
&[a, &[1, 2, 3]],
),
));
assert_holds(verify_case(
&mut fake,
&case(ConformanceProperty::DeltaIdempotence, &[a]).with_deltas(vec![bytes(&[9])]),
));
assert_holds(verify_case(
&mut fake,
&case(ConformanceProperty::DeltaPermutationInvariance, &[a])
.with_deltas(vec![bytes(&[9]), bytes(&[7])]),
));
}
#[test]
fn last_write_wins_merge_fails_commutativity() {
let mut fake = Fake::conforming().merging(|_a, b| Ok(b.to_vec()));
assert_violates(
verify_case(
&mut fake,
&case(ConformanceProperty::StateCommutativity, &[&[1, 2], &[2, 3]]),
),
ConformanceProperty::StateCommutativity,
);
}
#[test]
fn mutual_rejection_fails_commutativity() {
let mut fake = Fake::conforming().merging(|a, _b| Ok(a.to_vec()));
assert_violates(
verify_case(
&mut fake,
&case(ConformanceProperty::StateCommutativity, &[&[1, 2], &[2, 3]]),
),
ConformanceProperty::StateCommutativity,
);
}
#[test]
fn mutual_rejection_is_a_reconciliation_cycle() {
let mut fake = Fake::conforming()
.merging(|a, _b| Ok(a.to_vec()))
.applying(|a, _d| Ok(a.to_vec()));
assert_violates(
verify_case(
&mut fake,
&case(
ConformanceProperty::ReconciliationCycle,
&[&[1, 2], &[3, 4]],
),
),
ConformanceProperty::ReconciliationCycle,
);
}
#[test]
fn non_idempotent_merge_is_caught() {
let mut fake = Fake::conforming().merging(|a, b| {
let mut out = a.to_vec();
out.extend_from_slice(b);
out.push(out.len() as u8);
Ok(out)
});
assert_violates(
verify_case(
&mut fake,
&case(ConformanceProperty::StateIdempotence, &[&[1, 2]]),
),
ConformanceProperty::StateIdempotence,
);
}
#[test]
fn associativity_only_defect_needs_the_triple_check() {
fn avg(a: &[u8], b: &[u8]) -> Vec<u8> {
let (x, y) = (
a.first().copied().unwrap_or(0),
b.first().copied().unwrap_or(0),
);
vec![((x as u16 + y as u16) / 2) as u8]
}
let build = || {
Fake::conforming()
.merging(|a, b| Ok(avg(a, b)))
.validating(|state| {
if state.len() == 1 {
Ok(ValidateResult::Valid)
} else {
Ok(ValidateResult::Invalid)
}
})
};
let mut fake = build();
assert_holds(verify_case(
&mut fake,
&case(ConformanceProperty::StateCommutativity, &[&[1], &[3]]),
));
assert_holds(verify_case(
&mut fake,
&case(ConformanceProperty::StateIdempotence, &[&[1]]),
));
let mut fake = build();
assert_violates(
verify_case(
&mut fake,
&case(ConformanceProperty::StateAssociativity, &[&[1], &[3], &[5]]),
),
ConformanceProperty::StateAssociativity,
);
}
#[test]
fn non_idempotent_delta_is_caught() {
let mut fake = Fake::conforming().applying(|a, d| {
let mut out = a.to_vec();
out.extend_from_slice(d);
Ok(out)
});
assert_violates(
verify_case(
&mut fake,
&case(ConformanceProperty::DeltaIdempotence, &[&[1, 2]]).with_deltas(vec![bytes(&[9])]),
),
ConformanceProperty::DeltaIdempotence,
);
}
#[test]
fn order_dependent_deltas_are_caught() {
let mut fake = Fake::conforming().applying(|a, d| {
let mut out = a.to_vec();
out.extend_from_slice(d);
Ok(out)
});
assert_violates(
verify_case(
&mut fake,
&case(ConformanceProperty::DeltaPermutationInvariance, &[&[1, 2]])
.with_deltas(vec![bytes(&[9]), bytes(&[7])]),
),
ConformanceProperty::DeltaPermutationInvariance,
);
}
#[test]
fn nondeterministic_summary_is_caught() {
let mut counter = 0u8;
let mut fake = Fake::conforming().summarizing(move |state| {
counter = counter.wrapping_add(1);
let mut out = state.to_vec();
out.push(counter);
Ok(out)
});
assert_violates(
verify_case(
&mut fake,
&case(ConformanceProperty::SummaryDeterminism, &[&[1, 2]]),
),
ConformanceProperty::SummaryDeterminism,
);
}
#[test]
fn a_merge_that_only_reorders_bytes_is_named_as_an_encoding_problem() {
let mut fake = Fake::conforming().merging(|current, incoming| {
let mut out: Vec<u8> = current.to_vec();
for b in incoming {
if !out.contains(b) {
out.push(*b);
}
}
Ok(out)
});
let outcome = verify_case(
&mut fake,
&case(ConformanceProperty::StateCommutativity, &[&[1, 2], &[3, 4]]),
);
let violation = match outcome {
PropertyOutcome::Violated(v) => v,
other @ (PropertyOutcome::Holds | PropertyOutcome::Inconclusive(_)) => {
panic!("expected a commutativity violation, got {other:?}")
}
};
assert!(
violation.detail.contains("same bytes in a different order"),
"a reordering must be named as an encoding problem; got: {}",
violation.detail
);
assert!(
violation.detail.contains("canonical"),
"the finding should point at the encoding; got: {}",
violation.detail
);
}
#[test]
fn nondeterministic_delta_is_caught() {
let mut counter = 0u8;
let mut fake = Fake::conforming().deltaing(move |state, _summary| {
counter = counter.wrapping_add(1);
let mut out = state.to_vec();
out.push(counter);
Ok(out)
});
assert_violates(
verify_case(
&mut fake,
&case(ConformanceProperty::DeltaDeterminism, &[&[1, 2]]),
),
ConformanceProperty::DeltaDeterminism,
);
}
#[test]
fn nondeterministic_update_is_caught() {
let mut counter = 0u8;
let mut fake = Fake::conforming().merging(move |a, b| {
counter = counter.wrapping_add(1);
let mut out = union(a, b);
out.push(counter);
Ok(out)
});
assert_violates(
verify_case(
&mut fake,
&case(ConformanceProperty::UpdateDeterminism, &[&[1, 2], &[3]]),
),
ConformanceProperty::UpdateDeterminism,
);
}
#[test]
fn emitted_state_the_contract_would_reject_is_caught() {
let mut fake = Fake::conforming().merging(|a, b| {
let mut out = union(a, b);
out.reverse();
out.push(out[0]);
Ok(out)
});
assert_violates(
verify_case(
&mut fake,
&case(
ConformanceProperty::EmittedStateValidity,
&[&[1, 2], &[3, 4]],
),
),
ConformanceProperty::EmittedStateValidity,
);
}
#[test]
fn non_empty_self_delta_is_reported_but_only_as_a_diagnostic() {
let mut fake = Fake::conforming().deltaing(|state, _summary| Ok(state.to_vec()));
let outcome = verify_case(
&mut fake,
&case(ConformanceProperty::SelfDeltaEmpty, &[&[1, 2, 3]]),
);
assert_violates(outcome.clone(), ConformanceProperty::SelfDeltaEmpty);
assert!(
!outcome.is_enforceable_violation(),
"a wasteful self-delta must not be eligible as removal evidence"
);
assert_eq!(outcome.violation().unwrap().severity, Severity::Diagnostic);
let outcome = verify_case(
&mut fake,
&case(ConformanceProperty::WholeStateSelfDelta, &[&[1, 2, 3]]),
);
assert_violates(outcome.clone(), ConformanceProperty::WholeStateSelfDelta);
assert!(!outcome.is_enforceable_violation());
}
#[test]
fn multi_round_convergence_is_not_a_cycle() {
let mut fake = Fake::conforming().deltaing(|state, summary| {
let mut missing = difference(state, summary);
missing.truncate(1);
Ok(missing)
});
assert_holds(verify_case(
&mut fake,
&case(
ConformanceProperty::ReconciliationCycle,
&[&[1, 2], &[3, 4]],
),
));
}
#[test]
fn a_canonicalizing_contract_is_not_flagged() {
let mut fake = Fake::conforming()
.merging(|a, b| Ok(union(a, b)))
.validating(|_| Ok(ValidateResult::Valid));
let raw: &[u8] = &[3, 1, 3, 2];
assert_holds(verify_case(
&mut fake,
&case(ConformanceProperty::StateIdempotence, &[raw]),
));
}
#[test]
fn a_weak_delta_path_with_a_sound_merge_is_not_a_cycle() {
let mut fake = Fake::conforming()
.summarizing(|_| Ok(vec![0]))
.deltaing(|_state, _summary| Ok(Vec::new()));
assert_holds(verify_case(
&mut fake,
&case(
ConformanceProperty::ReconciliationCycle,
&[&[1, 2], &[3, 4]],
),
));
}
#[test]
fn a_violation_that_does_not_reproduce_is_not_reported() {
let mut calls = 0u32;
let mut fake = Fake::conforming().merging(move |a, b| {
calls += 1;
if calls <= 2 {
Ok(b.to_vec()) } else {
Ok(union(a, b))
}
});
assert_inconclusive(
verify_case(
&mut fake,
&case(ConformanceProperty::StateCommutativity, &[&[1, 2], &[2, 3]]),
),
Inconclusive::NotReproducible,
);
}
#[test]
fn a_nondeterministic_merge_is_reported_as_nondeterminism_not_as_a_merge_law_break() {
let mut calls = 0u8;
let mut fake = Fake::conforming().merging(move |a, b| {
calls = calls.wrapping_add(1);
let mut out = union(a, b);
out.push(calls);
Ok(out)
});
assert_violates(
verify_case(
&mut fake,
&case(ConformanceProperty::StateCommutativity, &[&[1, 2], &[2, 3]]),
),
ConformanceProperty::UpdateDeterminism,
);
}
#[test]
fn an_intermediate_the_contract_rejects_is_inconclusive() {
let mut validations = 0u32;
let mut fake = Fake::conforming().validating(move |_state| {
validations += 1;
if validations <= 3 {
Ok(ValidateResult::Valid)
} else {
Ok(ValidateResult::Invalid)
}
});
assert_inconclusive(
verify_case(
&mut fake,
&case(
ConformanceProperty::StateAssociativity,
&[&[1, 2], &[2, 3], &[4]],
),
),
Inconclusive::InputNotValid,
);
}
#[test]
fn a_rejected_update_is_inconclusive_not_a_violation() {
let mut fake = Fake::conforming().merging(|_a, _b| {
Err(OracleError::contract(
"signature does not chain to the owner",
))
});
assert_inconclusive(
verify_case(
&mut fake,
&case(ConformanceProperty::StateCommutativity, &[&[1, 2], &[2, 3]]),
),
Inconclusive::ContractError("signature does not chain to the owner".into()),
);
}
#[test]
fn waiting_on_a_related_contract_is_inconclusive() {
let mut fake = Fake::conforming().validating(|_| Ok(ValidateResult::RequestRelated(vec![])));
assert_inconclusive(
verify_case(
&mut fake,
&case(ConformanceProperty::StateCommutativity, &[&[1, 2], &[2, 3]]),
),
Inconclusive::RelatedRequired,
);
}
#[test]
fn states_the_contract_rejects_are_never_evidence() {
let mut fake = Fake::conforming().merging(|_a, b| Ok(b.to_vec()));
assert_inconclusive(
verify_case(
&mut fake,
&case(ConformanceProperty::StateCommutativity, &[&[3, 1], &[2, 3]]),
),
Inconclusive::InputNotValid,
);
}
#[test]
fn emitted_state_validity_is_gated_on_the_inputs_being_valid() {
let mut fake = Fake::conforming().merging(|_a, _b| Ok(vec![3, 1]));
assert_inconclusive(
verify_case(
&mut fake,
&case(
ConformanceProperty::EmittedStateValidity,
&[&[3, 1], &[2, 3]],
),
),
Inconclusive::InputNotValid,
);
}
#[test]
fn resource_exhaustion_is_inconclusive() {
let mut fake = Fake::conforming().merging(|_a, _b| Err(OracleError::resource("out of gas")));
match verify_case(
&mut fake,
&case(ConformanceProperty::StateCommutativity, &[&[1, 2], &[2, 3]]),
) {
PropertyOutcome::Inconclusive(Inconclusive::ResourceLimit(_)) => {}
other @ (PropertyOutcome::Holds
| PropertyOutcome::Violated(_)
| PropertyOutcome::Inconclusive(_)) => {
panic!("expected a resource-limit inconclusive, got {other:?}")
}
}
}
#[test]
fn a_commutative_but_non_idempotent_delta_is_a_diagnostic_not_a_violation() {
let sum = |a: &[u8], d: &[u8]| -> Vec<u8> {
let total: u32 =
a.iter().map(|b| *b as u32).sum::<u32>() + d.iter().map(|b| *b as u32).sum::<u32>();
vec![(total % 251) as u8]
};
let mut fake = Fake::conforming()
.applying(move |a, d| Ok(sum(a, d)))
.validating(|state| {
if state.len() == 1 {
Ok(ValidateResult::Valid)
} else {
Ok(ValidateResult::Invalid)
}
});
let outcome = verify_case(
&mut fake,
&case(ConformanceProperty::DeltaIdempotence, &[&[10]]).with_deltas(vec![bytes(&[5])]),
);
assert_violates(outcome.clone(), ConformanceProperty::DeltaIdempotence);
assert_eq!(outcome.violation().unwrap().severity, Severity::Diagnostic);
assert!(
!outcome.is_enforceable_violation(),
"a counter-style delta must not be removal-eligible while the question of \
whether any deployed contract relies on it is unanswered"
);
let mut fake = Fake::conforming()
.applying(move |a, d| Ok(sum(a, d)))
.validating(|state| {
if state.len() == 1 {
Ok(ValidateResult::Valid)
} else {
Ok(ValidateResult::Invalid)
}
});
assert_holds(verify_case(
&mut fake,
&case(ConformanceProperty::DeltaPermutationInvariance, &[&[10]])
.with_deltas(vec![bytes(&[5]), bytes(&[7])]),
));
}
#[test]
fn emitted_state_needing_related_context_is_inconclusive() {
let mut seen = 0u32;
let mut fake = Fake::conforming().validating(move |_state| {
seen += 1;
if seen <= 2 {
Ok(ValidateResult::Valid)
} else {
Ok(ValidateResult::RequestRelated(vec![]))
}
});
assert_inconclusive(
verify_case(
&mut fake,
&case(
ConformanceProperty::EmittedStateValidity,
&[&[1, 2], &[2, 3]],
),
),
Inconclusive::RelatedRequired,
);
}
#[test]
fn a_case_with_too_few_states_is_malformed_not_a_violation() {
let mut fake = Fake::conforming();
match verify_case(
&mut fake,
&case(ConformanceProperty::StateAssociativity, &[&[1], &[2]]),
) {
PropertyOutcome::Inconclusive(Inconclusive::MalformedCase(_)) => {}
other @ (PropertyOutcome::Holds
| PropertyOutcome::Violated(_)
| PropertyOutcome::Inconclusive(_)) => {
panic!("expected malformed-case, got {other:?}")
}
}
}
fn disagreeing_paths() -> Fake {
Fake::conforming()
.validating(|state| {
if is_canonical(state) && state.windows(2).all(|w| w[0] >> 4 != w[1] >> 4) {
Ok(ValidateResult::Valid)
} else {
Ok(ValidateResult::Invalid)
}
})
.merging(|a, b| Ok(collapse_by_key(&union(a, b), false)))
.applying(|a, d| Ok(collapse_by_key(&union(a, d), true)))
}
fn collapse_by_key(entries: &[u8], keep_last: bool) -> Vec<u8> {
let mut out: Vec<u8> = Vec::new();
for entry in union(entries, &[]) {
match out.last().copied() {
Some(previous) if previous >> 4 == entry >> 4 => {
if keep_last {
let last = out.len() - 1;
out[last] = entry;
}
}
_ => out.push(entry),
}
}
out
}
#[test]
fn a_contract_whose_two_write_paths_disagree_is_caught() {
let mut fake = disagreeing_paths();
assert_violates(
verify_case(
&mut fake,
&case(
ConformanceProperty::PathAgreement,
&[&[0x10, 0x51], &[0x10, 0x52]],
),
),
ConformanceProperty::PathAgreement,
);
}
#[test]
fn the_same_disagreeing_contract_is_silent_when_no_key_collides() {
let mut fake = disagreeing_paths();
assert_holds(verify_case(
&mut fake,
&case(
ConformanceProperty::PathAgreement,
&[&[0x10, 0x51], &[0x10, 0x62]],
),
));
}
#[test]
fn the_disagreeing_contract_satisfies_every_pre_existing_law() {
let (a, b, c): (&[u8], &[u8], &[u8]) = (&[0x10, 0x51], &[0x10, 0x52], &[0x23, 0x51]);
for (property, states, deltas) in [
(ConformanceProperty::StateIdempotence, vec![a], vec![]),
(ConformanceProperty::StateCommutativity, vec![a, b], vec![]),
(
ConformanceProperty::StateAssociativity,
vec![a, b, c],
vec![],
),
(
ConformanceProperty::EmittedStateValidity,
vec![a, b],
vec![],
),
(ConformanceProperty::UpdateDeterminism, vec![a, b], vec![]),
(ConformanceProperty::SummaryDeterminism, vec![a], vec![]),
(ConformanceProperty::DeltaDeterminism, vec![a], vec![]),
(ConformanceProperty::ReconciliationCycle, vec![a, b], vec![]),
(ConformanceProperty::SelfDeltaEmpty, vec![a], vec![]),
(
ConformanceProperty::DeltaIdempotence,
vec![a],
vec![bytes(&[0x52])],
),
(
ConformanceProperty::DeltaPermutationInvariance,
vec![a],
vec![bytes(&[0x52]), bytes(&[0x63])],
),
] {
let mut fake = disagreeing_paths();
let built = ConformanceCase::new(property, states.iter().map(|s| bytes(s)).collect())
.with_deltas(deltas);
assert_eq!(
verify_case(&mut fake, &built),
PropertyOutcome::Holds,
"{property} did not HOLD on the disagreeing-paths contract, so it no \
longer demonstrates the #5394 gap (a contract that satisfies every \
existing law and still diverges)"
);
}
}
#[test]
fn a_delta_that_carries_only_part_of_the_other_state_is_not_a_disagreement() {
let mut fake = Fake::conforming()
.deltaing(|state, summary| Ok(difference(state, summary).into_iter().take(1).collect()));
let outcome = verify_case(
&mut fake,
&case(ConformanceProperty::PathAgreement, &[&[1], &[2, 3, 4]]),
);
assert_holds(outcome);
let mut same = Fake::conforming()
.deltaing(|state, summary| Ok(difference(state, summary).into_iter().take(1).collect()));
let delta = same.get_state_delta(&[2, 3, 4], &[1]).expect("delta");
let delta_path = union(&[1], &delta);
let merge_path = union(&[1], &[2, 3, 4]);
assert_ne!(
delta_path, merge_path,
"this fixture no longer exercises the partial-delta case the guard exists \
for, so the assertion above passes for the wrong reason"
);
}
#[test]
fn a_contract_with_no_delta_path_is_inconclusive_rather_than_accused() {
let mut fake = Fake::conforming()
.summarizing(|_| Ok(vec![0]))
.deltaing(|_state, _summary| Ok(Vec::new()));
assert_inconclusive(
verify_case(
&mut fake,
&case(ConformanceProperty::PathAgreement, &[&[1, 2], &[3, 4]]),
),
Inconclusive::NoDeltaPath,
);
}
#[test]
fn a_broken_merge_is_left_to_the_property_that_names_it() {
let mut fake = Fake::conforming().merging(|_a, b| Ok(b.to_vec()));
let outcome = verify_case(
&mut fake,
&case(ConformanceProperty::PathAgreement, &[&[1, 2], &[2, 3]]),
);
assert!(
!outcome.is_violation(),
"path agreement must not re-accuse a contract whose merge is the defect: \
{outcome:?}"
);
assert_violates(
verify_case(
&mut fake,
&case(ConformanceProperty::StateCommutativity, &[&[1, 2], &[2, 3]]),
),
ConformanceProperty::StateCommutativity,
);
}
#[test]
fn a_defect_visible_from_only_one_direction_is_still_found() {
const MARKER: u8 = 0xEE;
const EXTRA: u8 = 0xFF;
let make = || {
Fake::conforming().applying(|base, delta| {
let mut out = union(base, delta);
if base.contains(&MARKER) {
out = union(&out, &[EXTRA]);
}
Ok(out)
})
};
assert_violates(
verify_case(
&mut make(),
&case(
ConformanceProperty::PathAgreement,
&[&[0x01, 0x02], &[0x01, MARKER]],
),
),
ConformanceProperty::PathAgreement,
);
let mut fake = make();
let delta = fake
.get_state_delta(&[0x01, MARKER], &[0x01, 0x02])
.expect("delta");
assert_eq!(
union(&[0x01, 0x02], &delta),
union(&[0x01, 0x02], &[0x01, MARKER]),
"the forward direction must AGREE for this to pin the reverse one"
);
}
#[test]
fn a_disagreement_is_found_from_either_order_of_the_pair() {
for states in [
[&[0x10u8, 0x51u8][..], &[0x10, 0x52][..]],
[&[0x10, 0x52][..], &[0x10, 0x51][..]],
] {
let mut fake = disagreeing_paths();
assert_violates(
verify_case(
&mut fake,
&case(ConformanceProperty::PathAgreement, &states),
),
ConformanceProperty::PathAgreement,
);
}
}
fn transition_case(fake: &mut Fake, base: &[u8], delta: &[u8]) -> ConformanceCase {
let result = fake
.update_state(
base,
&[UpdateData::Delta(
freenet_stdlib::prelude::StateDelta::from(delta.to_vec()),
)],
)
.expect("apply")
.new_state
.expect("new state")
.into_bytes();
ConformanceCase::new(
ConformanceProperty::TransitionPathAgreement,
vec![bytes(base), bytes(&result)],
)
}
#[test]
fn a_reached_state_the_merge_path_cannot_reproduce_is_caught() {
let mut fake = disagreeing_paths();
let built = transition_case(&mut fake, &[0x10, 0x51], &[0x52]);
assert_ne!(
built.states[0], built.states[1],
"a transition that changed nothing proves nothing"
);
assert_violates(
verify_case(&mut fake, &built),
ConformanceProperty::TransitionPathAgreement,
);
}
#[test]
fn the_same_contract_is_silent_on_a_transition_whose_key_does_not_collide() {
let mut fake = disagreeing_paths();
let built = transition_case(&mut fake, &[0x10, 0x51], &[0x62]);
assert_ne!(
built.states[0], built.states[1],
"a transition that changed nothing proves nothing"
);
assert_holds(verify_case(&mut fake, &built));
}
#[test]
fn a_sound_bounded_collection_is_not_accused() {
const CAP: usize = 3;
let keep_largest = |a: &[u8], b: &[u8]| {
let mut out = union(a, b);
while out.len() > CAP {
out.remove(0);
}
Ok(out)
};
let mut fake = Fake::conforming()
.merging(keep_largest)
.applying(keep_largest);
let built = transition_case(&mut fake, &[1, 2], &[3, 4, 5]);
assert_eq!(
built.states[1].as_ref(),
&[3, 4, 5],
"the cap must actually have evicted something, or this tests nothing"
);
assert_holds(verify_case(&mut fake, &built));
}
#[test]
fn capped_collection_evicting_outside_the_merge_order_is_caught() {
const CAP: usize = 3;
let evict_by_content = |a: &[u8], b: &[u8]| {
let mut out = union(a, b);
while out.len() > CAP {
let sum: usize = out.iter().map(|byte| *byte as usize).sum();
out.remove(sum % out.len());
}
Ok(out)
};
let mut fake = Fake::conforming()
.merging(evict_by_content)
.applying(evict_by_content);
let built = transition_case(&mut fake, &[1, 2], &[3, 4, 5]);
assert_violates(
verify_case(&mut fake, &built),
ConformanceProperty::TransitionPathAgreement,
);
let mut sound = Fake::conforming()
.merging(|a: &[u8], b: &[u8]| {
let mut out = union(a, b);
while out.len() > CAP {
out.remove(0);
}
Ok(out)
})
.applying(|a: &[u8], b: &[u8]| {
let mut out = union(a, b);
while out.len() > CAP {
out.remove(0);
}
Ok(out)
});
let sound_case = transition_case(&mut sound, &[1, 2], &[3, 4, 5]);
assert_holds(verify_case(&mut sound, &sound_case));
}
#[test]
fn a_partially_ordered_cap_is_already_caught_by_associativity() {
const N: usize = 2;
fn dominates(after: u8, before: u8) -> bool {
after == 1 && before == 5
}
fn cap(a: &[u8], b: &[u8]) -> Result<Vec<u8>, OracleError> {
let all = union(a, b);
let mut out: Vec<u8> = all
.iter()
.copied()
.filter(|x| !all.iter().any(|y| dominates(*y, *x)))
.collect();
while out.len() > N {
out.remove(0);
}
Ok(out)
}
let fake = || {
Fake::conforming()
.merging(cap)
.applying(cap)
.validating(|state| {
let canonical = cap(state, &[]).expect("cap is infallible");
if canonical == state {
Ok(ValidateResult::Valid)
} else {
Ok(ValidateResult::Invalid)
}
})
};
assert_violates(
verify_case(
&mut fake(),
&case(
ConformanceProperty::StateAssociativity,
&[&[1], &[2], &[3, 5]],
),
),
ConformanceProperty::StateAssociativity,
);
for pairwise in [
case(ConformanceProperty::StateCommutativity, &[&[1], &[3, 5]]),
case(ConformanceProperty::StateIdempotence, &[&[2, 5]]),
] {
let property = pairwise.property;
assert_eq!(
verify_case(&mut fake(), &pairwise),
PropertyOutcome::Holds,
"{property} must hold, or this fixture is not the hard case it claims \
to be"
);
}
let mut oracle = fake();
let built = transition_case(&mut oracle, &[2, 5], &[1, 3]);
assert_eq!(
built.states[1].as_ref(),
&[2, 3],
"the op must actually reach the measured state, or the assertion below is \
about something else"
);
assert_violates(
verify_case(&mut oracle, &built),
ConformanceProperty::TransitionPathAgreement,
);
}
#[test]
fn a_canonicalizing_contract_is_not_accused_by_the_transition_law() {
const MARKER: u8 = 0xFF;
let strip = |a: &[u8], b: &[u8]| {
let mut out = union(a, b);
out.retain(|byte| *byte != MARKER);
Ok(out)
};
let mut fake = Fake::conforming().merging(strip).applying(|a, d| {
Ok(union(a, d))
});
let built = transition_case(&mut fake, &[1, 2], &[MARKER]);
assert_eq!(
built.states[1].as_ref(),
&[1, 2, MARKER],
"the recorded result must be non-canonical, or the guard is untested"
);
assert_holds(verify_case(&mut fake, &built));
}
#[test]
fn a_merge_that_emits_an_invalid_state_is_not_reported_under_the_transition_law() {
const MARKER: u8 = 0xFE;
let mut fake = Fake::conforming()
.merging(|a, b| {
let mut out = union(a, b);
if a != b {
out.push(MARKER);
}
Ok(out)
})
.applying(|a, d| Ok(union(a, d)))
.validating(|state| {
if is_canonical(state) && !state.contains(&MARKER) {
Ok(ValidateResult::Valid)
} else {
Ok(ValidateResult::Invalid)
}
});
let built = case(
ConformanceProperty::TransitionPathAgreement,
&[&[1, 2], &[1, 2, 3]],
);
assert_inconclusive(verify_case(&mut fake, &built), Inconclusive::InputNotValid);
}
#[test]
fn a_result_state_that_never_settles_is_inconclusive_not_a_violation() {
let mut fake = Fake::conforming()
.validating(|_| Ok(ValidateResult::Valid))
.merging(|a, b| {
Ok(union(a, b)
.iter()
.map(|byte| byte.wrapping_add(1))
.collect())
});
assert_inconclusive(
verify_case(
&mut fake,
&case(
ConformanceProperty::TransitionPathAgreement,
&[&[1, 2], &[3, 4]],
),
),
Inconclusive::StateNotSettled,
);
}
#[test]
fn deduplicating_deltas_keeps_the_base_whichever_copy_arrives_first() {
let delta = bytes(&[9]);
let base = bytes(&[1, 2]);
for (label, bases) in [
("unprovenanced copy first", vec![None, Some(base.clone())]),
("provenanced copy first", vec![Some(base.clone()), None]),
] {
let corpus = Corpus {
deltas: vec![delta.clone(), delta.clone()],
delta_bases: bases,
..Corpus::from_states(vec![vec![1, 2]])
}
.deduplicated();
assert_eq!(
corpus.deltas.len(),
1,
"{label}: the duplicate must collapse"
);
assert_eq!(
corpus.delta_base(0),
Some(&base),
"{label}: the surviving delta must keep the state it was applied to"
);
}
}
#[test]
fn a_corpus_of_steps_alone_is_not_empty() {
let steps_only = Corpus {
transitions: vec![(bytes(&[1]), bytes(&[1, 2]))],
..Default::default()
};
assert!(
!steps_only.is_empty(),
"a recorded step is material to check, so a corpus holding one is not empty"
);
let config = GeneratorConfig {
properties: vec![ConformanceProperty::TransitionPathAgreement],
..Default::default()
};
assert_eq!(
generate_cases(&steps_only, &config).len(),
1,
"and the early return must not swallow it"
);
assert!(Corpus::default().is_empty());
}
#[test]
fn the_transition_branch_is_bounded_and_strided() {
let config = GeneratorConfig {
properties: vec![ConformanceProperty::TransitionPathAgreement],
max_transitions: 4,
max_cases: 1024,
..Default::default()
};
let corpus = Corpus {
transitions: (0..40u8).map(|i| (bytes(&[i]), bytes(&[i, 200]))).collect(),
..Corpus::from_states(vec![vec![1]])
};
let cases = generate_cases(&corpus, &config);
assert_eq!(cases.len(), 4, "the cap must bind");
let bases: Vec<u8> = cases.iter().map(|c| c.states[0][0]).collect();
assert_eq!(bases, vec![0, 10, 20, 30]);
assert_ne!(
bases,
vec![0, 1, 2, 3],
"truncation is the thing this test exists to exclude, so name it"
);
}
#[test]
fn transition_cases_come_only_from_recorded_provenance() {
let config = GeneratorConfig {
properties: vec![ConformanceProperty::TransitionPathAgreement],
..Default::default()
};
let loose = Corpus::from_states(vec![vec![1], vec![2], vec![1, 2], vec![2, 3]]);
assert!(
generate_cases(&loose, &config).is_empty(),
"states that merely appeared together are not a transition; pairing them \
would accuse every conforming contract of last-write-wins"
);
let witnessed = Corpus {
transitions: vec![(bytes(&[1]), bytes(&[1, 2]))],
..Corpus::from_states(vec![vec![1], vec![1, 2]])
};
let cases = generate_cases(&witnessed, &config);
assert_eq!(cases.len(), 1, "one recorded step is one case");
assert_eq!(cases[0].states[0].as_ref(), &[1], "base comes first");
assert_eq!(cases[0].states[1].as_ref(), &[1, 2], "result comes second");
}
#[test]
fn a_bundle_round_trip_preserves_transition_provenance() {
let mut bundle = super::bundle::ReplayBundle::new(b"code".to_vec(), Vec::new());
bundle.transitions.push(super::bundle::Transition {
base_state: vec![1],
result_state: vec![1, 2],
..Default::default()
});
let decoded =
super::bundle::ReplayBundle::decode(&bundle.encode().expect("encode")).expect("decode");
let corpus = decoded.to_corpus();
assert_eq!(
corpus.transitions,
vec![(bytes(&[1]), bytes(&[1, 2]))],
"a replayed capture must still know which state came first"
);
}
fn instance(seed: u8) -> ContractInstanceId {
ContractInstanceId::new([seed; 32])
}
#[test]
fn evidence_round_trips_through_a_case() {
let original = case(ConformanceProperty::StateCommutativity, &[&[1, 2], &[2, 3]]);
let evidence = ConformanceEvidence::new(instance(7), vec![9, 9], &original, None);
evidence.check_bounds().expect("bounds");
let rebuilt = evidence.to_case().expect("to_case");
assert_eq!(rebuilt.property, original.property);
assert_eq!(rebuilt.states, original.states);
let mut fake = Fake::conforming().merging(|_a, b| Ok(b.to_vec()));
assert_violates(
verify_case(&mut fake, &rebuilt),
ConformanceProperty::StateCommutativity,
);
}
#[test]
fn evidence_id_ignores_observed_output_and_runtime() {
let case = case(ConformanceProperty::StateCommutativity, &[&[1, 2], &[2, 3]]);
let bare = ConformanceEvidence::new(instance(1), vec![], &case, None);
let mut fake = Fake::conforming().merging(|_a, b| Ok(b.to_vec()));
let observed = verify_case(&mut fake, &case).violation().cloned();
let annotated = ConformanceEvidence::new(instance(1), vec![], &case, observed);
assert_eq!(bare.id(), annotated.id());
}
#[test]
fn evidence_id_separates_instances_and_parameters() {
let case = case(ConformanceProperty::StateCommutativity, &[&[1, 2], &[2, 3]]);
let a = ConformanceEvidence::new(instance(1), vec![], &case, None);
let b = ConformanceEvidence::new(instance(2), vec![], &case, None);
let c = ConformanceEvidence::new(instance(1), vec![1], &case, None);
assert_ne!(a.id(), b.id());
assert_ne!(
a.id(),
c.id(),
"same code with different parameters is a different instance"
);
}
#[test]
fn evidence_id_does_not_depend_on_related_contract_ordering() {
let case = case(ConformanceProperty::StateIdempotence, &[&[1]]);
let mut forward = ConformanceEvidence::new(instance(1), vec![], &case, None);
forward.related = vec![
(instance(3), vec![3]),
(instance(1), vec![1]),
(instance(2), vec![2]),
];
let mut reversed = forward.clone();
reversed.related.reverse();
assert_ne!(
forward.related, reversed.related,
"fixture failed: the two orderings are identical, so this proves nothing"
);
assert_eq!(forward.id(), reversed.id());
}
#[test]
fn evidence_id_is_not_confused_by_blob_boundaries() {
let split = case(ConformanceProperty::StateCommutativity, &[&[1], &[2]]);
let joined = ConformanceCase::new(
ConformanceProperty::StateCommutativity,
vec![bytes(&[1, 2]), bytes(&[])],
);
let a = ConformanceEvidence::new(instance(1), vec![], &split, None);
let b = ConformanceEvidence::new(instance(1), vec![], &joined, None);
assert_ne!(a.id(), b.id());
}
#[test]
fn oversized_evidence_is_rejected_before_any_execution() {
let big = vec![0u8; MAX_EVIDENCE_INPUT_BYTES + 1];
let case = ConformanceCase::new(
ConformanceProperty::StateIdempotence,
vec![Arc::from(big.as_slice())],
);
let evidence = ConformanceEvidence::new(instance(1), vec![], &case, None);
assert!(matches!(
evidence.check_bounds(),
Err(EvidenceRejected::TooLarge { .. })
));
}
#[test]
fn evidence_with_wrong_arity_is_rejected() {
let case = ConformanceCase::new(
ConformanceProperty::StateAssociativity,
vec![bytes(&[1]), bytes(&[2])],
);
let evidence = ConformanceEvidence::new(instance(1), vec![], &case, None);
assert!(matches!(
evidence.check_bounds(),
Err(EvidenceRejected::Arity { .. })
));
}
#[test]
fn evidence_with_too_many_related_contracts_is_rejected() {
let case = case(ConformanceProperty::StateIdempotence, &[&[1]]);
let mut evidence = ConformanceEvidence::new(instance(1), vec![], &case, None);
evidence.related = (0..=MAX_EVIDENCE_RELATED)
.map(|i| (instance(i as u8), vec![i as u8]))
.collect();
assert!(
evidence.related.len() > MAX_EVIDENCE_RELATED,
"the fixture must actually exceed the limit or the assertion below is vacuous"
);
assert!(matches!(
evidence.check_bounds(),
Err(EvidenceRejected::TooManyRelated { .. })
));
}
#[test]
fn evidence_at_the_related_contract_limit_is_accepted() {
let case = case(ConformanceProperty::StateIdempotence, &[&[1]]);
let mut evidence = ConformanceEvidence::new(instance(1), vec![], &case, None);
evidence.related = (0..MAX_EVIDENCE_RELATED)
.map(|i| (instance(i as u8), vec![i as u8]))
.collect();
assert!(evidence.check_bounds().is_ok());
}
#[test]
fn evidence_for_a_property_that_is_not_self_verifying_is_refused() {
let case = case(
ConformanceProperty::TransitionPathAgreement,
&[&[1], &[1, 2]],
);
let evidence = ConformanceEvidence::new(instance(1), vec![], &case, None);
assert!(
evidence.input_bytes() < MAX_EVIDENCE_INPUT_BYTES,
"the fixture must be well within every OTHER bound, or this could pass for \
the wrong reason"
);
assert_eq!(
evidence.check_bounds(),
Err(EvidenceRejected::NotSelfVerifying {
property: ConformanceProperty::TransitionPathAgreement,
})
);
}
#[test]
fn evidence_for_a_self_verifying_property_is_accepted() {
let case = case(ConformanceProperty::StateCommutativity, &[&[1], &[1, 2]]);
let evidence = ConformanceEvidence::new(instance(1), vec![], &case, None);
assert_eq!(evidence.check_bounds(), Ok(()));
}
#[test]
fn every_property_declares_whether_it_is_self_verifying() {
let local: Vec<ConformanceProperty> = ConformanceProperty::ALL
.iter()
.copied()
.filter(|p| p.premise_source() == PremiseSource::LocalProvenance)
.collect();
assert_eq!(
local,
vec![
ConformanceProperty::DeltaPermutationInvariance,
ConformanceProperty::TransitionPathAgreement,
],
"the set of properties that cannot travel as evidence changed; if that is \
deliberate, update this pin, and make sure `check_bounds` still refuses \
every one of them"
);
assert_eq!(
ConformanceProperty::ALL.len(),
14,
"a property was added or removed; say explicitly whether it is \
self-verifying (see `ConformanceProperty::premise_source`) rather than \
letting it inherit an answer, then update this count"
);
for property in ConformanceProperty::ALL {
let states = (0..property.state_arity())
.map(|i| bytes(&[i as u8]))
.collect();
let deltas = (0..property.delta_arity())
.map(|i| bytes(&[0x80 | i as u8]))
.collect();
let built = ConformanceCase::new(*property, states).with_deltas(deltas);
let evidence = ConformanceEvidence::new(instance(1), vec![], &built, None);
assert_eq!(
evidence.check_bounds().is_ok(),
property.is_self_verifying(),
"{property}: check_bounds must accept exactly the self-verifying \
properties"
);
}
}
#[test]
fn evidence_for_delta_permutation_invariance_is_refused() {
let built = ConformanceCase::new(
ConformanceProperty::DeltaPermutationInvariance,
vec![bytes(&[1])],
)
.with_deltas(vec![bytes(&[0x80]), bytes(&[0x81])]);
let evidence = ConformanceEvidence::new(instance(1), vec![], &built, None);
assert!(
evidence.input_bytes() < MAX_EVIDENCE_INPUT_BYTES,
"the fixture must be well within every OTHER bound, or this could pass for \
the wrong reason"
);
assert_eq!(
evidence.states.len(),
ConformanceProperty::DeltaPermutationInvariance.state_arity(),
"the fixture must satisfy the arity check, or this could pass for the wrong \
reason"
);
assert_eq!(
evidence.deltas.len(),
ConformanceProperty::DeltaPermutationInvariance.delta_arity(),
"the fixture must satisfy the arity check, or this could pass for the wrong \
reason"
);
assert_eq!(
evidence.check_bounds(),
Err(EvidenceRejected::NotSelfVerifying {
property: ConformanceProperty::DeltaPermutationInvariance,
})
);
assert_eq!(
evidence.to_case().err(),
Some(EvidenceRejected::NotSelfVerifying {
property: ConformanceProperty::DeltaPermutationInvariance,
}),
"and the gate must hold at the point where a case is built, not only where \
someone remembered to call `check_bounds`"
);
}
fn consumes_supplied_summary(property: ConformanceProperty) -> bool {
match property {
ConformanceProperty::DeltaDeterminism => true,
ConformanceProperty::StateIdempotence
| ConformanceProperty::StateCommutativity
| ConformanceProperty::StateAssociativity
| ConformanceProperty::EmittedStateValidity
| ConformanceProperty::UpdateDeterminism
| ConformanceProperty::SummaryDeterminism
| ConformanceProperty::DeltaIdempotence
| ConformanceProperty::DeltaPermutationInvariance
| ConformanceProperty::SelfDeltaEmpty
| ConformanceProperty::WholeStateSelfDelta
| ConformanceProperty::ReconciliationCycle
| ConformanceProperty::PathAgreement
| ConformanceProperty::TransitionPathAgreement => false,
}
}
fn verdict_survives_fabricated_bytes(property: ConformanceProperty) -> bool {
match property {
ConformanceProperty::UpdateDeterminism
| ConformanceProperty::SummaryDeterminism
| ConformanceProperty::DeltaDeterminism => true,
ConformanceProperty::StateIdempotence
| ConformanceProperty::StateCommutativity
| ConformanceProperty::StateAssociativity
| ConformanceProperty::EmittedStateValidity
| ConformanceProperty::DeltaIdempotence
| ConformanceProperty::DeltaPermutationInvariance
| ConformanceProperty::SelfDeltaEmpty
| ConformanceProperty::WholeStateSelfDelta
| ConformanceProperty::ReconciliationCycle
| ConformanceProperty::PathAgreement
| ConformanceProperty::TransitionPathAgreement => false,
}
}
#[test]
fn no_shippable_removal_eligible_property_consumes_unvalidated_bytes() {
let consumes_unvalidated =
|p: ConformanceProperty| p.delta_arity() > 0 || consumes_supplied_summary(p);
let hazardous: Vec<ConformanceProperty> = ConformanceProperty::ALL
.iter()
.copied()
.filter(|p| {
consumes_unvalidated(*p)
&& p.is_self_verifying()
&& p.severity() == Severity::Violation
&& !verdict_survives_fabricated_bytes(*p)
})
.collect();
assert!(
hazardous.is_empty(),
"{hazardous:?}: a property that ships as evidence, runs attacker-chosen \
delta or summary bytes through the WASM, and is removal-eligible is the \
combination the evidence gate exists to prevent. Either mark it \
`PremiseSource::LocalProvenance`, or drop it to `Severity::Diagnostic`, or \
give those bytes a validity check first — do not simply update this test"
);
assert!(
ConformanceProperty::ALL
.iter()
.any(|p| p.delta_arity() > 0 && p.is_self_verifying()),
"no property carries deltas as evidence any more, so the assertion above \
proves nothing; delete it or re-aim it"
);
let carried_only_by_the_exemption: Vec<ConformanceProperty> = ConformanceProperty::ALL
.iter()
.copied()
.filter(|p| {
consumes_unvalidated(*p) && p.is_self_verifying() && p.severity() == Severity::Violation
})
.collect();
assert_eq!(
carried_only_by_the_exemption,
vec![ConformanceProperty::DeltaDeterminism],
"the set of shippable removal-eligible properties consuming unvalidated \
bytes has changed. `DeltaDeterminism` is here because a fabricated summary \
cannot manufacture a nondeterminism verdict; anything joining it needs that \
argument made for it in `verdict_survives_fabricated_bytes`, and anything \
leaving means the summary arm of the filter above now tests nothing"
);
}
#[test]
fn to_case_refuses_what_check_bounds_refuses() {
let big = vec![0u8; MAX_EVIDENCE_INPUT_BYTES + 1];
let oversized = ConformanceCase::new(
ConformanceProperty::StateIdempotence,
vec![Arc::from(big.as_slice())],
);
let evidence = ConformanceEvidence::new(instance(1), vec![], &oversized, None);
assert!(matches!(
evidence.to_case(),
Err(EvidenceRejected::TooLarge { .. })
));
let wrong_arity = ConformanceCase::new(
ConformanceProperty::StateAssociativity,
vec![bytes(&[1]), bytes(&[2])],
);
let evidence = ConformanceEvidence::new(instance(1), vec![], &wrong_arity, None);
assert!(matches!(
evidence.to_case(),
Err(EvidenceRejected::Arity { .. })
));
let fine = case(ConformanceProperty::StateCommutativity, &[&[1], &[1, 2]]);
let evidence = ConformanceEvidence::new(instance(1), vec![], &fine, None);
assert!(evidence.to_case().is_ok());
}
#[test]
fn unsupported_schema_is_rejected() {
let case = case(ConformanceProperty::StateIdempotence, &[&[1]]);
let mut evidence = ConformanceEvidence::new(instance(1), vec![], &case, None);
evidence.schema_version = 999;
assert!(matches!(
evidence.check_bounds(),
Err(EvidenceRejected::UnsupportedSchema { .. })
));
}
#[test]
fn generator_is_deterministic() {
let corpus = Corpus::from_states(vec![vec![1], vec![2], vec![3], vec![4]]);
let config = GeneratorConfig::default();
let first = generate_cases(&corpus, &config);
let second = generate_cases(&corpus, &config);
assert_eq!(first.len(), second.len());
for (a, b) in first.iter().zip(second.iter()) {
assert_eq!(a.property, b.property);
assert_eq!(a.states, b.states);
}
}
#[test]
fn deltas_observed_against_different_bases_are_never_paired() {
let base_one: Bytes = Arc::from([1u8, 2].as_slice());
let base_two: Bytes = Arc::from([3u8, 4].as_slice());
let corpus = Corpus {
deltas: vec![Arc::from([9u8].as_slice()), Arc::from([7u8].as_slice())],
delta_bases: vec![Some(base_one), Some(base_two)],
..Corpus::from_states(vec![vec![1, 2], vec![3, 4]])
};
let cases = generate_cases(&corpus, &GeneratorConfig::default());
let permutation_cases = cases
.iter()
.filter(|c| c.property == ConformanceProperty::DeltaPermutationInvariance)
.count();
assert_eq!(
permutation_cases, 0,
"deltas seen against different bases must not be paired: they may be causally \
sequenced, and permuting them accuses contracts of an order-dependence the \
network never exercises"
);
let shared: Bytes = Arc::from([1u8, 2].as_slice());
let paired = Corpus {
deltas: vec![Arc::from([9u8].as_slice()), Arc::from([7u8].as_slice())],
delta_bases: vec![Some(shared.clone()), Some(shared)],
..Corpus::from_states(vec![vec![1, 2], vec![3, 4]])
};
let paired_cases = generate_cases(&paired, &GeneratorConfig::default())
.iter()
.filter(|c| c.property == ConformanceProperty::DeltaPermutationInvariance)
.count();
assert!(
paired_cases > 0,
"deltas sharing a base are the genuine concurrent-update case and must still \
be checked, or the fix has simply disabled the property"
);
}
#[test]
fn a_tight_case_budget_still_covers_every_law() {
let states: Vec<Vec<u8>> = (1u8..=12).map(|i| vec![i]).collect();
let base: Bytes = Arc::from([1u8].as_slice());
let corpus = Corpus {
deltas: vec![Arc::from([9u8].as_slice()), Arc::from([7u8].as_slice())],
delta_bases: vec![Some(base.clone()), Some(base.clone())],
transitions: vec![(base, Arc::from([1u8, 9].as_slice()))],
..Corpus::from_states(states)
};
let config = GeneratorConfig {
max_cases: ConformanceProperty::ALL.len(),
..Default::default()
};
let cases = generate_cases(&corpus, &config);
let mut seen: Vec<ConformanceProperty> = cases.iter().map(|c| c.property).collect();
seen.sort_by_key(|p| p.as_str());
seen.dedup();
assert_eq!(
seen.len(),
ConformanceProperty::ALL.len(),
"budget dropped whole properties instead of narrowing each one"
);
}
#[test]
fn generated_delta_cases_use_observed_summaries() {
let corpus = Corpus {
summaries: vec![bytes(&[1]), bytes(&[2, 3])],
..Corpus::from_states(vec![vec![1, 2], vec![2, 3]])
};
let cases = generate_cases(&corpus, &GeneratorConfig::default());
let with_summary: Vec<_> = cases
.iter()
.filter(|c| c.property == ConformanceProperty::DeltaDeterminism && c.summary.is_some())
.collect();
assert!(
!with_summary.is_empty(),
"no generated case exercised get_state_delta against an observed summary"
);
let used: std::collections::HashSet<Vec<u8>> = with_summary
.iter()
.map(|c| c.summary.as_ref().unwrap().to_vec())
.collect();
assert_eq!(
used.len(),
2,
"only some observed summaries were used: {used:?}"
);
}
#[test]
fn generator_deduplicates_oscillating_states() {
let corpus =
Corpus::from_states(vec![vec![1], vec![2], vec![1], vec![2], vec![1]]).deduplicated();
assert_eq!(corpus.states.len(), 2);
}
#[test]
fn generated_cases_find_the_planted_defect() {
let corpus = Corpus::from_states(vec![vec![1, 2], vec![2, 3], vec![4]]);
let cases = generate_cases(&corpus, &GeneratorConfig::default());
let mut fake = Fake::conforming().merging(|_a, b| Ok(b.to_vec()));
let found = cases
.iter()
.filter_map(|c| verify_case(&mut fake, c).violation().cloned())
.any(|v| v.property == ConformanceProperty::StateCommutativity);
assert!(found, "generated corpus missed a last-write-wins merge");
}
#[test]
fn generated_cases_report_nothing_against_a_conforming_contract() {
let corpus = Corpus::from_states(vec![vec![1, 2], vec![2, 3], vec![4], vec![1, 4]]);
let cases = generate_cases(&corpus, &GeneratorConfig::default());
assert!(
cases.len() > 10,
"generator produced almost nothing to check"
);
let mut fake = Fake::conforming();
for case in &cases {
let outcome = verify_case(&mut fake, case);
assert!(
!outcome.is_violation(),
"false positive on a conforming contract: {outcome:?} for {}",
case.property
);
}
}
#[test]
fn bundle_round_trips() {
use super::bundle::{ReplayBundle, Transition};
let mut bundle = ReplayBundle::new(vec![0, 1, 2, 3], vec![7]);
bundle.states = vec![vec![1, 2], vec![2, 3]];
bundle.deltas = vec![vec![9]];
bundle.transitions = vec![Transition {
base_state: vec![1, 2],
delta: Some(vec![3]),
incoming_state: None,
summary: Some(vec![1, 2]),
result_state: vec![1, 2, 3],
}];
let encoded = bundle.encode().expect("encode");
let decoded = ReplayBundle::decode(&encoded).expect("decode");
assert_eq!(bundle, decoded);
let corpus = decoded.to_corpus();
assert!(corpus.states.iter().any(|s| s.as_ref() == [1, 2, 3]));
assert!(corpus.deltas.iter().any(|d| d.as_ref() == [3]));
}
#[test]
fn a_bundle_round_trips_to_an_identical_corpus() {
use super::bundle::ReplayBundle;
let mut bundle = ReplayBundle::new(vec![0, 1, 2, 3], vec![7]);
bundle.states = vec![vec![1, 2], vec![2, 3], vec![4]];
bundle.deltas = vec![vec![9]];
bundle.summaries = vec![vec![1]];
let decoded = ReplayBundle::decode(&bundle.encode().expect("encode")).expect("decode");
let (before, after) = (bundle.to_corpus(), decoded.to_corpus());
assert_eq!(before.states, after.states);
assert_eq!(before.deltas, after.deltas);
assert_eq!(before.summaries, after.summaries);
let config = GeneratorConfig::default();
let (a, b) = (
generate_cases(&before, &config),
generate_cases(&after, &config),
);
assert_eq!(a.len(), b.len());
for (x, y) in a.iter().zip(b.iter()) {
assert_eq!(x.property, y.property);
assert_eq!(x.states, y.states);
assert_eq!(x.summary, y.summary);
}
}
#[test]
fn a_foreign_file_is_not_mistaken_for_a_bundle() {
use super::bundle::{BundleError, ReplayBundle};
assert!(matches!(
ReplayBundle::decode(b"definitely not a bundle"),
Err(BundleError::BadMagic)
));
assert!(matches!(
ReplayBundle::decode(b"FRNT"),
Err(BundleError::BadMagic)
));
assert!(matches!(
ReplayBundle::decode(b""),
Err(BundleError::BadMagic)
));
}
#[test]
fn a_bundle_from_an_unsupported_schema_is_refused() {
use super::bundle::{BUNDLE_SCHEMA_VERSION, BundleError, ReplayBundle};
let mut encoded = ReplayBundle::new(vec![0, 1], vec![])
.encode()
.expect("encode");
let bumped = BUNDLE_SCHEMA_VERSION + 1;
encoded[8..10].copy_from_slice(&bumped.to_le_bytes());
match ReplayBundle::decode(&encoded) {
Err(BundleError::UnsupportedSchema { found, supported }) => {
assert_eq!(found, bumped);
assert_eq!(supported, BUNDLE_SCHEMA_VERSION);
}
other => panic!("expected an unsupported-schema refusal, got {other:?}"),
}
}
#[test]
fn a_bundle_that_names_no_contract_is_refused() {
use super::bundle::{BundleError, ReplayBundle};
let mut bundle = ReplayBundle::new(vec![1, 2, 3], vec![]);
bundle.code = None;
bundle.code_hash = None;
assert!(matches!(
bundle.resolve_code(Some(vec![9, 9, 9])),
Err(BundleError::UnidentifiedContract)
));
}
#[test]
fn supplied_code_must_match_the_bundle_it_replays() {
use super::bundle::{BundleError, ReplayBundle};
let mut bundle = ReplayBundle::new(vec![1, 2, 3], vec![]);
bundle.code = None;
assert!(matches!(
bundle.resolve_code(Some(vec![4, 5, 6])),
Err(BundleError::CodeMismatch { .. })
));
assert_eq!(
bundle.resolve_code(Some(vec![1, 2, 3])).unwrap(),
vec![1, 2, 3]
);
assert!(matches!(
bundle.resolve_code(None),
Err(BundleError::MissingCode)
));
}
#[test]
fn embedded_bundle_code_is_verified_against_its_own_hash() {
use super::bundle::{BundleError, ReplayBundle};
let mut bundle = ReplayBundle::new(vec![1, 2, 3], vec![]);
bundle.code = Some(vec![1, 2, 4]);
assert!(matches!(
bundle.resolve_code(None),
Err(BundleError::CodeMismatch { .. })
));
}
#[test]
fn a_corrupt_bundle_body_is_an_error_not_a_panic() {
use super::bundle::{BundleError, ReplayBundle};
let mut encoded = ReplayBundle::new(vec![0, 1], vec![])
.encode()
.expect("encode");
encoded.truncate(encoded.len() - 1);
assert!(matches!(
ReplayBundle::decode(&encoded),
Err(BundleError::Decode(_))
));
}