use crate::check::{Check, Cmp};
pub struct Invariant;
impl Invariant {
pub fn authority_unchanged(account: impl Into<String>, field: impl Into<String>) -> Check {
Check::account(account).field_unchanged(field).build()
}
pub fn no_lamport_loss(account: impl Into<String>) -> Check {
Check::account(account).lamports_delta(Cmp::ge(0)).build()
}
pub fn max_lamport_loss(account: impl Into<String>, max: u64) -> Check {
Check::account(account)
.lamports_delta(Cmp::ge(-(max as i128)))
.build()
}
pub fn no_token_loss(account: impl Into<String>) -> Check {
Check::account(account).token_delta(Cmp::ge(0)).build()
}
pub fn max_token_loss(account: impl Into<String>, max: u64) -> Check {
Check::account(account)
.token_delta(Cmp::ge(-(max as i128)))
.build()
}
pub fn monotonic(account: impl Into<String>, field: impl Into<String>) -> Check {
Check::account(account)
.field_delta(field, Cmp::ge(0))
.build()
}
pub fn field_constant(account: impl Into<String>, field: impl Into<String>) -> Check {
Check::account(account).field_unchanged(field).build()
}
pub fn field_equals(
account: impl Into<String>,
field: impl Into<String>,
value: impl Into<i128>,
) -> Check {
Check::account(account).field(field, Cmp::eq(value)).build()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::check::CheckKind;
use crate::replay::{CmpOp, StateCheck};
fn one_assert(check: &Check) -> (&str, &StateCheck) {
match &check.0 {
CheckKind::Account(asserts) if asserts.len() == 1 => {
(asserts[0].address.as_str(), &asserts[0].check)
}
_ => panic!("expected a single-assert account check"),
}
}
#[test]
fn max_lamport_loss_is_a_bounded_negative_delta() {
let (addr, sc) = {
let c = Invariant::max_lamport_loss("Vau1t", 500);
let (a, s) = one_assert(&c);
(a.to_string(), s.clone())
};
assert_eq!(addr, "Vau1t");
match sc {
StateCheck::LamportsDelta { op, value } => {
assert_eq!(op, CmpOp::Ge);
assert_eq!(value, -500);
}
other => panic!("expected LamportsDelta, got {other:?}"),
}
}
#[test]
fn authority_unchanged_compares_the_field_bytes() {
let c = Invariant::authority_unchanged("Mkt", "admin");
let (_, sc) = one_assert(&c);
match sc {
StateCheck::FieldUnchanged { name } => assert_eq!(name, "admin"),
other => panic!("expected FieldUnchanged, got {other:?}"),
}
}
#[test]
fn monotonic_requires_a_non_negative_field_delta() {
let c = Invariant::monotonic("Pool", "reserve");
let (_, sc) = one_assert(&c);
match sc {
StateCheck::FieldDelta { op, value, .. } => {
assert_eq!(*op, CmpOp::Ge);
assert_eq!(*value, 0);
}
other => panic!("expected FieldDelta, got {other:?}"),
}
}
}