#![cfg(kani)]
use rust_decimal::Decimal;
#[kani::proof]
#[kani::unwind(1)]
fn proof_conservation_add_reduce() {
let added: i64 = kani::any();
let reduced: i64 = kani::any();
kani::assume(added > 0 && added < 100_000);
kani::assume(reduced > 0 && reduced < 100_000);
kani::assume(reduced <= added);
let dec_added = Decimal::from(added);
let dec_reduced = Decimal::from(reduced);
let inventory = Decimal::ZERO;
let after_add = inventory + dec_added;
let final_inventory = after_add - dec_reduced;
let expected = dec_added - dec_reduced;
kani::assert(
final_inventory == expected,
"Conservation violated: inventory + reduced != added",
);
}
#[kani::proof]
#[kani::unwind(1)]
fn proof_conservation_multiple_operations() {
let add1: i64 = kani::any();
let add2: i64 = kani::any();
let reduce1: i64 = kani::any();
let reduce2: i64 = kani::any();
kani::assume(add1 > 0 && add1 < 10_000);
kani::assume(add2 > 0 && add2 < 10_000);
kani::assume(reduce1 > 0 && reduce1 < 10_000);
kani::assume(reduce2 > 0 && reduce2 < 10_000);
let total_added = add1 + add2;
let total_reduced = reduce1 + reduce2;
kani::assume(total_reduced <= total_added);
let mut inventory = Decimal::ZERO;
inventory = inventory + Decimal::from(add1);
inventory = inventory + Decimal::from(add2);
inventory = inventory - Decimal::from(reduce1);
inventory = inventory - Decimal::from(reduce2);
let expected = Decimal::from(total_added - total_reduced);
kani::assert(
inventory == expected,
"Conservation violated in multi-op sequence",
);
}
#[kani::proof]
#[kani::unwind(1)]
fn proof_conservation_full_reduction_is_zero() {
let units: i64 = kani::any();
kani::assume(units > 0 && units < 1_000_000_000);
let dec_units = Decimal::from(units);
let inventory = Decimal::ZERO + dec_units - dec_units;
kani::assert(
inventory == Decimal::ZERO,
"Full reduction must return to zero",
);
}
struct SimpleLot {
units: i64,
order: u8,
}
#[kani::proof]
#[kani::unwind(1)]
fn proof_fifo_selects_oldest_of_two() {
let lot1_units: i64 = kani::any();
let lot2_units: i64 = kani::any();
kani::assume(lot1_units > 0 && lot1_units < 1000);
kani::assume(lot2_units > 0 && lot2_units < 1000);
let lot1 = SimpleLot {
units: lot1_units,
order: 0,
};
let lot2 = SimpleLot {
units: lot2_units,
order: 1,
};
let selected = if lot1.order < lot2.order {
&lot1
} else {
&lot2
};
kani::assert(selected.order == 0, "FIFO must select oldest lot (order=0)");
}
#[kani::proof]
#[kani::unwind(1)]
fn proof_fifo_ignores_lot_size() {
let lot1_units: i64 = kani::any();
let lot2_units: i64 = kani::any();
kani::assume(lot1_units > 0 && lot1_units < 1000);
kani::assume(lot2_units > lot1_units);
let lots = [(lot1_units, 0u8), (lot2_units, 1u8)];
let selected_order = lots.iter().map(|(_, order)| *order).min().unwrap();
kani::assert(
selected_order == 0,
"FIFO must select oldest lot regardless of size",
);
}
#[kani::proof]
#[kani::unwind(1)]
fn proof_lifo_selects_newest() {
let lot1_order: u8 = 0; let lot2_order: u8 = 1;
let selected_order = if lot1_order > lot2_order {
lot1_order
} else {
lot2_order
};
kani::assert(selected_order == 1, "LIFO must select newest lot (order=1)");
}
#[kani::proof]
#[kani::unwind(1)]
fn proof_hifo_selects_highest_cost() {
let cost1: i64 = kani::any();
let cost2: i64 = kani::any();
kani::assume(cost1 > 0 && cost1 < 100_000);
kani::assume(cost2 > 0 && cost2 < 100_000);
kani::assume(cost1 != cost2);
let selected_cost = if cost1 > cost2 { cost1 } else { cost2 };
let max_cost = std::cmp::max(cost1, cost2);
kani::assert(
selected_cost == max_cost,
"HIFO must select highest cost lot",
);
}
#[kani::proof]
#[kani::unwind(1)]
fn proof_double_entry_two_postings() {
let amount: i64 = kani::any();
kani::assume(amount != 0 && amount != i64::MIN);
kani::assume(amount.abs() < 1_000_000_000);
let debit = Decimal::from(amount);
let credit = Decimal::from(-amount);
let sum = debit + credit;
kani::assert(
sum == Decimal::ZERO,
"Double-entry: debit + credit must equal zero",
);
}
#[kani::proof]
#[kani::unwind(1)]
fn proof_double_entry_multiple_postings() {
let posting1: i64 = kani::any();
let posting2: i64 = kani::any();
kani::assume(posting1 > 0 && posting1 < 100_000);
kani::assume(posting2 > 0 && posting2 < 100_000);
let posting3 = -(posting1 + posting2);
let sum = Decimal::from(posting1) + Decimal::from(posting2) + Decimal::from(posting3);
kani::assert(
sum == Decimal::ZERO,
"Double-entry: all postings must sum to zero",
);
}
#[kani::proof]
#[kani::unwind(1)]
fn proof_decimal_addition_commutative() {
let a: i64 = kani::any();
let b: i64 = kani::any();
kani::assume(a != i64::MIN && b != i64::MIN);
kani::assume(a.abs() < 1_000_000 && b.abs() < 1_000_000);
let dec_a = Decimal::from(a);
let dec_b = Decimal::from(b);
kani::assert(
dec_a + dec_b == dec_b + dec_a,
"Addition must be commutative",
);
}
#[kani::proof]
#[kani::unwind(1)]
fn proof_decimal_negation_involutive() {
let a: i64 = kani::any();
kani::assume(a != i64::MIN);
kani::assume(a.abs() < 1_000_000_000);
let dec_a = Decimal::from(a);
kani::assert(-(-dec_a) == dec_a, "Double negation must return original");
}