supercov_engine/
probe_v2.rs1use serde::{Deserialize, Serialize};
2use supercov_contracts::{PROBE_V2_JS_MAX_CONDITIONS, PROBE_V2_RADIX};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5#[serde(rename_all = "lowercase")]
6pub enum ConditionValue {
7 Unreached,
8 False,
9 True,
10}
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct DecisionVector {
14 pub values: Vec<ConditionValue>,
15 pub outcome: bool,
16}
17
18pub fn decode(condition_count: usize, encoded: u64, outcome: bool) -> Option<DecisionVector> {
20 if condition_count > PROBE_V2_JS_MAX_CONDITIONS {
21 return None;
22 }
23 let mut remaining = encoded;
24 let mut values = Vec::with_capacity(condition_count);
25 for _ in 0..condition_count {
26 let digit = remaining % u64::from(PROBE_V2_RADIX);
27 values.push(match digit {
28 0 => ConditionValue::Unreached,
29 1 => ConditionValue::False,
30 2 => ConditionValue::True,
31 _ => unreachable!("a radix-3 remainder is always 0, 1, or 2"),
32 });
33 remaining /= u64::from(PROBE_V2_RADIX);
34 }
35 (remaining == 0).then_some(DecisionVector { values, outcome })
36}
37
38#[cfg(test)]
39mod tests {
40 use serde::Deserialize;
41
42 use super::*;
43
44 #[derive(Deserialize)]
45 struct Fixture {
46 conditions: usize,
47 encoded: u64,
48 outcome: bool,
49 vector: FixtureVector,
50 }
51
52 #[derive(Deserialize)]
53 struct FixtureVector {
54 values: Vec<Option<bool>>,
55 outcome: bool,
56 }
57
58 #[test]
59 fn matches_every_language_neutral_contract_vector() {
60 let fixtures: Vec<Fixture> =
61 serde_json::from_str(include_str!("../test-assets/probe-v2/vectors.json"))
62 .expect("probe vectors must be valid JSON");
63 for fixture in fixtures {
64 let expected = DecisionVector {
65 values: fixture
66 .vector
67 .values
68 .into_iter()
69 .map(|value| match value {
70 None => ConditionValue::Unreached,
71 Some(false) => ConditionValue::False,
72 Some(true) => ConditionValue::True,
73 })
74 .collect(),
75 outcome: fixture.vector.outcome,
76 };
77 assert_eq!(
78 decode(fixture.conditions, fixture.encoded, fixture.outcome),
79 Some(expected)
80 );
81 }
82 }
83
84 #[test]
85 fn rejects_width_and_high_digits_instead_of_aliasing() {
86 assert_eq!(decode(33, 0, false), None);
87 assert_eq!(decode(2, 9, false), None);
88 }
89}