|
| 1 | +// Copyright Kani Contributors |
| 2 | +// SPDX-License-Identifier: Apache-2.0 OR MIT |
| 3 | + |
| 4 | +use crate::escrow::*; |
| 5 | +use crate::fsm::*; |
| 6 | +use crate::oracle::*; |
| 7 | + |
| 8 | +#[kani::proof] |
| 9 | +fn timelock_policy_matches_release_rule() { |
| 10 | + let caller: Key = kani::any(); |
| 11 | + let agent: Key = kani::any(); |
| 12 | + let api: Key = kani::any(); |
| 13 | + let now: i64 = kani::any(); |
| 14 | + let expires_at: i64 = kani::any(); |
| 15 | + |
| 16 | + let allowed = can_release_funds(caller, agent, api, now, expires_at); |
| 17 | + |
| 18 | + if now < expires_at { |
| 19 | + kani::assert( |
| 20 | + !allowed || caller == agent, |
| 21 | + "only agent can release before expiry", |
| 22 | + ); |
| 23 | + } else { |
| 24 | + kani::assert( |
| 25 | + !allowed || (caller == agent || caller == api), |
| 26 | + "only agent/api can release after expiry", |
| 27 | + ); |
| 28 | + } |
| 29 | +} |
| 30 | + |
| 31 | +#[kani::proof] |
| 32 | +fn settlement_splits_conserve_value() { |
| 33 | + let amount: u64 = kani::any::<u32>() as u64; |
| 34 | + |
| 35 | + let refund_percentage: u8 = kani::any(); |
| 36 | + kani::assume(refund_percentage <= 100); |
| 37 | + |
| 38 | + let (refund, payment) = dispute_settlement(amount, refund_percentage); |
| 39 | + kani::assert( |
| 40 | + refund as u128 + payment as u128 == amount as u128, |
| 41 | + "dispute settlement must conserve value", |
| 42 | + ); |
| 43 | + |
| 44 | + let quality_threshold: u8 = kani::any(); |
| 45 | + let quality_score: u8 = kani::any(); |
| 46 | + kani::assume(quality_threshold <= 100); |
| 47 | + kani::assume(quality_score <= 100); |
| 48 | + |
| 49 | + let (user_refund, provider_payment) = |
| 50 | + inference_settlement(amount, quality_threshold, quality_score); |
| 51 | + kani::assert( |
| 52 | + user_refund as u128 + provider_payment as u128 == amount as u128, |
| 53 | + "inference settlement must conserve value", |
| 54 | + ); |
| 55 | + |
| 56 | + let was_disputed: bool = kani::any(); |
| 57 | + let (agent_amount, api_amount) = expired_escrow_settlement(amount, was_disputed); |
| 58 | + kani::assert( |
| 59 | + agent_amount as u128 + api_amount as u128 == amount as u128, |
| 60 | + "expired escrow claim must conserve value", |
| 61 | + ); |
| 62 | +} |
| 63 | + |
| 64 | +#[kani::proof] |
| 65 | +fn required_oracle_count_is_monotonic_and_bounded() { |
| 66 | + let amount: u64 = kani::any(); |
| 67 | + let r = required_oracle_count(amount); |
| 68 | + kani::assert( |
| 69 | + r == MIN_ORACLES || r == 4 || r == 5, |
| 70 | + "required oracle count must be in {3,4,5}", |
| 71 | + ); |
| 72 | + |
| 73 | + let a1: u64 = kani::any(); |
| 74 | + let a2: u64 = kani::any(); |
| 75 | + kani::assume(a1 <= a2); |
| 76 | + |
| 77 | + let r1 = required_oracle_count(a1); |
| 78 | + let r2 = required_oracle_count(a2); |
| 79 | + kani::assert(r1 <= r2, "oracle requirement must be monotonic"); |
| 80 | + |
| 81 | + kani::assert( |
| 82 | + required_oracle_count(TIER2_ESCROW_THRESHOLD - 1) == MIN_ORACLES, |
| 83 | + "below tier2 threshold must use minimum", |
| 84 | + ); |
| 85 | + kani::assert( |
| 86 | + required_oracle_count(TIER2_ESCROW_THRESHOLD) == 4, |
| 87 | + "tier2 threshold must require 4 oracles", |
| 88 | + ); |
| 89 | + kani::assert( |
| 90 | + required_oracle_count(TIER3_ESCROW_THRESHOLD - 1) == 4, |
| 91 | + "just below tier3 threshold must still be tier2", |
| 92 | + ); |
| 93 | + kani::assert( |
| 94 | + required_oracle_count(TIER3_ESCROW_THRESHOLD) == 5, |
| 95 | + "tier3 threshold must require 5 oracles", |
| 96 | + ); |
| 97 | +} |
| 98 | + |
| 99 | +fn any_state() -> EscrowState { |
| 100 | + match kani::any::<u8>() % 4 { |
| 101 | + 0 => EscrowState::Active, |
| 102 | + 1 => EscrowState::Disputed, |
| 103 | + 2 => EscrowState::Released, |
| 104 | + _ => EscrowState::Resolved, |
| 105 | + } |
| 106 | +} |
| 107 | + |
| 108 | +fn any_action() -> EscrowAction { |
| 109 | + match kani::any::<u8>() % 4 { |
| 110 | + 0 => EscrowAction::Release, |
| 111 | + 1 => EscrowAction::MarkDisputed, |
| 112 | + 2 => EscrowAction::Resolve, |
| 113 | + _ => EscrowAction::ClaimExpired, |
| 114 | + } |
| 115 | +} |
| 116 | + |
| 117 | +#[kani::proof] |
| 118 | +fn escrow_fsm_actions_respect_transition_table() { |
| 119 | + let state = any_state(); |
| 120 | + let action = any_action(); |
| 121 | + |
| 122 | + let next = step(state, action); |
| 123 | + |
| 124 | + if is_terminal(state) { |
| 125 | + kani::assert(next.is_none(), "terminal states must not transition"); |
| 126 | + return; |
| 127 | + } |
| 128 | + |
| 129 | + if let Some(s2) = next { |
| 130 | + kani::assert(valid_transition(state, s2), "transition must be valid"); |
| 131 | + } |
| 132 | +} |
0 commit comments