use std::sync::Arc;
use super::generator::{Corpus, GeneratorConfig, generate_cases};
use super::property::{ConformanceProperty, PropertyOutcome};
use super::runtime_oracle::RuntimeOracle;
use super::verifier::{Bytes, ConformanceCase, verify_case};
const CONFORMING: u8 = 0;
const LAST_WRITE_WINS: u8 = 1;
const MUTUAL_REJECTION: u8 = 2;
const NON_IDEMPOTENT_DELTA: u8 = 3;
const NONDETERMINISTIC_SUMMARY: u8 = 4;
const CAPPED_SET: u8 = 5;
const NEVER_SETTLES: u8 = 6;
const REQUIRES_RELATED: u8 = 7;
const PATH_DISAGREEMENT: u8 = 8;
const RELATED_ID: [u8; 32] = [7; 32];
fn bytes(values: &[u8]) -> Bytes {
Arc::from(values)
}
fn transition_case(
oracle: &mut RuntimeOracle,
base: &[u8],
delta: &[u8],
) -> Result<ConformanceCase, Box<dyn std::error::Error>> {
use super::oracle::ConformanceOracle;
let result = oracle
.update_state(
base,
&[freenet_stdlib::prelude::UpdateData::Delta(
freenet_stdlib::prelude::StateDelta::from(delta.to_vec()),
)],
)?
.new_state
.ok_or("contract produced no state")?
.into_bytes();
assert_ne!(
base,
result.as_slice(),
"a transition that changed nothing proves nothing"
);
Ok(ConformanceCase::new(
ConformanceProperty::TransitionPathAgreement,
vec![bytes(base), bytes(&result)],
))
}
#[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:?}")
}
}
}
#[tokio::test(flavor = "multi_thread")]
async fn a_module_that_is_not_a_contract_fails_to_load_rather_than_reading_inconclusive() {
let not_a_contract = br#"(module (memory (export "memory") 1))"#.to_vec();
let err = RuntimeOracle::standalone(not_a_contract, vec![])
.await
.err()
.expect("a module exporting no contract entry points must not load");
let text = err.to_string();
assert!(
text.contains("not a contract") || text.contains("missing required export"),
"the error must say the module is not a contract, so a caller cannot mistake \
it for a contract that merely could not be judged; got: {text}"
);
}
#[tokio::test(flavor = "multi_thread")]
async fn verifier_matches_real_wasm_for_every_planted_defect()
-> Result<(), Box<dyn std::error::Error>> {
let wasm = crate::wasm_runtime::tests::get_test_module("test_contract_conformance")?;
let mut conforming = RuntimeOracle::standalone(wasm.clone(), vec![CONFORMING]).await?;
let corpus = Corpus {
deltas: vec![bytes(&[9]), bytes(&[7])],
..Corpus::from_states(vec![
vec![1],
vec![2],
vec![1, 2],
vec![2, 3],
vec![1, 2, 3],
vec![4, 5],
])
};
let cases = generate_cases(
&corpus,
&GeneratorConfig {
max_cases: 60,
..Default::default()
},
);
assert!(
cases.len() >= ConformanceProperty::ALL.len(),
"checked too few cases through real WASM to mean anything: {}",
cases.len()
);
for case in &cases {
let outcome = verify_case(&mut conforming, case);
assert!(
!outcome.is_violation(),
"false positive on a conforming contract through real WASM: {outcome:?} for {}",
case.property
);
}
let mut lww = RuntimeOracle::standalone(wasm.clone(), vec![LAST_WRITE_WINS]).await?;
let commutativity_case = ConformanceCase::new(
ConformanceProperty::StateCommutativity,
vec![bytes(&[1, 2]), bytes(&[2, 3])],
);
assert_violates(
verify_case(&mut lww, &commutativity_case),
ConformanceProperty::StateCommutativity,
);
let mut rejection = RuntimeOracle::standalone(wasm.clone(), vec![MUTUAL_REJECTION]).await?;
assert_violates(
verify_case(&mut rejection, &commutativity_case),
ConformanceProperty::StateCommutativity,
);
let cycle_case = ConformanceCase::new(
ConformanceProperty::ReconciliationCycle,
vec![bytes(&[1, 2]), bytes(&[3, 4])],
);
assert_violates(
verify_case(&mut rejection, &cycle_case),
ConformanceProperty::ReconciliationCycle,
);
let mut non_idempotent =
RuntimeOracle::standalone(wasm.clone(), vec![NON_IDEMPOTENT_DELTA]).await?;
let idempotence_case =
ConformanceCase::new(ConformanceProperty::DeltaIdempotence, vec![bytes(&[1, 2])])
.with_deltas(vec![bytes(&[9])]);
assert_violates(
verify_case(&mut non_idempotent, &idempotence_case),
ConformanceProperty::DeltaIdempotence,
);
let mut nondeterministic =
RuntimeOracle::standalone(wasm.clone(), vec![NONDETERMINISTIC_SUMMARY]).await?;
let summary_case = ConformanceCase::new(
ConformanceProperty::SummaryDeterminism,
vec![bytes(&[1, 2])],
);
assert_violates(
verify_case(&mut nondeterministic, &summary_case),
ConformanceProperty::SummaryDeterminism,
);
let mut capped = RuntimeOracle::standalone(wasm.clone(), vec![CAPPED_SET]).await?;
let associativity_case = ConformanceCase::new(
ConformanceProperty::StateAssociativity,
vec![bytes(&[1, 2]), bytes(&[3, 4]), bytes(&[5, 6])],
);
assert_violates(
verify_case(&mut capped, &associativity_case),
ConformanceProperty::StateAssociativity,
);
for pairwise in [
ConformanceCase::new(
ConformanceProperty::StateCommutativity,
vec![bytes(&[1, 2]), bytes(&[3, 4])],
),
ConformanceCase::new(
ConformanceProperty::StateIdempotence,
vec![bytes(&[1, 2]), bytes(&[3, 4])],
),
] {
let property = pairwise.property;
assert_eq!(
verify_case(&mut capped, &pairwise),
PropertyOutcome::Holds,
"among the PAIRWISE state laws the capped-collection mode should break \
none — associativity is the one that names it — but {property} did not \
hold"
);
}
let capped_transition = transition_case(&mut capped, &[1, 2], &[3, 4, 5])?;
assert!(
capped_transition.states[1].len() <= 3,
"the delta path must respect the cap, or this case is about a state the \
merge path can never reach: {:?}",
capped_transition.states[1]
);
assert_violates(
verify_case(&mut capped, &capped_transition),
ConformanceProperty::TransitionPathAgreement,
);
let mut never_settles = RuntimeOracle::standalone(wasm.clone(), vec![NEVER_SETTLES]).await?;
assert_violates(
verify_case(
&mut never_settles,
&ConformanceCase::new(ConformanceProperty::StateIdempotence, vec![bytes(&[1, 2])]),
),
ConformanceProperty::StateIdempotence,
);
assert_violates(
verify_case(
&mut never_settles,
&ConformanceCase::new(
ConformanceProperty::StateAssociativity,
vec![bytes(&[1, 2]), bytes(&[2, 3]), bytes(&[5, 6])],
),
),
ConformanceProperty::StateAssociativity,
);
let commutativity_still_holds = verify_case(
&mut never_settles,
&ConformanceCase::new(
ConformanceProperty::StateCommutativity,
vec![bytes(&[1, 2]), bytes(&[2, 3])],
),
);
assert!(
!commutativity_still_holds.is_violation(),
"this mode should break idempotence and associativity only; a commutativity \
finding means the arm no longer isolates what its documentation claims: \
{commutativity_still_holds:?}"
);
let mut needs_related = RuntimeOracle::standalone(wasm.clone(), vec![REQUIRES_RELATED]).await?;
let case = ConformanceCase::new(
ConformanceProperty::StateCommutativity,
vec![bytes(&[1, 2]), bytes(&[2, 3])],
);
match verify_case(&mut needs_related, &case) {
PropertyOutcome::Inconclusive(reason) => assert_eq!(
reason,
crate::conformance::property::Inconclusive::RelatedRequired,
"a contract asking for related state must be declined, never accused"
),
other @ (PropertyOutcome::Holds | PropertyOutcome::Violated(_)) => {
panic!("expected RelatedRequired without the related state, got {other:?}")
}
}
let mut supplied = std::collections::HashMap::new();
supplied.insert(
freenet_stdlib::prelude::ContractInstanceId::new(RELATED_ID),
Some(freenet_stdlib::prelude::State::from(vec![1u8, 2])),
);
let with_related = ConformanceCase::new(
ConformanceProperty::StateCommutativity,
vec![bytes(&[1, 2]), bytes(&[2, 3])],
)
.with_related(freenet_stdlib::prelude::RelatedContracts::from(supplied));
assert_eq!(
verify_case(&mut needs_related, &with_related),
PropertyOutcome::Holds,
"with the related state supplied the contract must become judgeable"
);
let mut disagreeing = RuntimeOracle::standalone(wasm.clone(), vec![PATH_DISAGREEMENT]).await?;
let colliding: Vec<Bytes> = vec![bytes(&[0x10, 0x51]), bytes(&[0x10, 0x52])];
assert_violates(
verify_case(
&mut disagreeing,
&ConformanceCase::new(ConformanceProperty::PathAgreement, colliding.clone()),
),
ConformanceProperty::PathAgreement,
);
for property in ConformanceProperty::ALL {
if matches!(
property,
ConformanceProperty::PathAgreement | ConformanceProperty::TransitionPathAgreement
) {
continue;
}
let states: Vec<Bytes> = match property.state_arity() {
3 => vec![
colliding[0].clone(),
colliding[1].clone(),
bytes(&[0x23, 0x51]),
],
_ => colliding.clone(),
};
let deltas: Vec<Bytes> = match property.delta_arity() {
0 => Vec::new(),
1 => vec![bytes(&[0x52])],
_ => vec![bytes(&[0x52]), bytes(&[0x63])],
};
assert_eq!(
verify_case(
&mut disagreeing,
&ConformanceCase::new(*property, states).with_deltas(deltas),
),
PropertyOutcome::Holds,
"{property} did not HOLD on the disagreeing-paths mode, so it no longer \
isolates the one defect only path_agreement can see"
);
}
let disagreeing_transition = transition_case(&mut disagreeing, &[0x10, 0x51], &[0x52])?;
assert_violates(
verify_case(&mut disagreeing, &disagreeing_transition),
ConformanceProperty::TransitionPathAgreement,
);
let harmless_transition = transition_case(&mut disagreeing, &[0x10, 0x51], &[0x62])?;
assert_eq!(
verify_case(&mut disagreeing, &harmless_transition),
PropertyOutcome::Holds,
"a transition whose op collides with nothing must not be flagged, or the \
property is a blanket accusation against every contract with a delta path"
);
assert_eq!(
verify_case(
&mut disagreeing,
&ConformanceCase::new(
ConformanceProperty::PathAgreement,
vec![bytes(&[0x10, 0x51]), bytes(&[0x10, 0x62])],
),
),
PropertyOutcome::Holds,
"the two write paths only disagree on a key COLLISION; flagging a pair that \
has none makes this a blanket accusation rather than a finding"
);
let a = RuntimeOracle::standalone(wasm.clone(), vec![CONFORMING]).await?;
let b = RuntimeOracle::standalone(wasm, vec![LAST_WRITE_WINS]).await?;
assert_ne!(
a.instance_id(),
b.instance_id(),
"same code with different parameters must be a different instance"
);
Ok(())
}