use vstd::prelude::*;
verus! {
pub open spec fn budget_safety(
capacity: nat,
allocated: nat,
reserved: nat,
pending_eviction: nat,
) -> bool {
allocated + reserved + pending_eviction <= capacity
}
#[derive(Clone, Copy)]
pub struct Budget {
pub capacity: u64,
pub allocated: u64,
pub reserved: u64,
pub pending_eviction: u64,
}
impl Budget {
pub open spec fn used(&self) -> int {
self.allocated as int + self.reserved as int + self.pending_eviction as int
}
pub open spec fn safety_invariant(&self) -> bool {
budget_safety(
self.capacity as nat,
self.allocated as nat,
self.reserved as nat,
self.pending_eviction as nat,
)
}
pub fn new(capacity: u64) -> (b: Budget)
ensures
b.capacity == capacity,
b.allocated == 0,
b.reserved == 0,
b.pending_eviction == 0,
b.safety_invariant(),
{
Budget { capacity, allocated: 0, reserved: 0, pending_eviction: 0 }
}
#[expect(clippy::arithmetic_side_effects, reason = "Verus proves used is bounded by capacity")]
pub fn available(&self) -> (a: u64)
requires self.safety_invariant(),
ensures a as int == self.capacity as int - self.used(),
{
assert(self.allocated + self.reserved <= self.capacity);
let used: u64 = self.allocated + self.reserved + self.pending_eviction;
self.capacity - used
}
#[expect(clippy::arithmetic_side_effects, reason = "Verus proves the guarded addition is within capacity")]
pub fn try_allocate(&mut self, amount: u64) -> (ok: bool)
requires old(self).safety_invariant(),
ensures
final(self).capacity == old(self).capacity,
final(self).reserved == old(self).reserved,
final(self).pending_eviction == old(self).pending_eviction,
final(self).safety_invariant(),
ok == (old(self).used() + amount as int <= old(self).capacity as int),
ok ==> final(self).allocated == old(self).allocated + amount,
!ok ==> final(self).allocated == old(self).allocated,
{
let headroom = self.available();
if amount <= headroom {
self.allocated = self.allocated + amount;
true
} else {
false
}
}
#[expect(clippy::arithmetic_side_effects, reason = "Verus proves the guarded addition is within capacity")]
pub fn reserve(&mut self, amount: u64) -> (ok: bool)
requires old(self).safety_invariant(),
ensures
final(self).capacity == old(self).capacity,
final(self).allocated == old(self).allocated,
final(self).pending_eviction == old(self).pending_eviction,
final(self).safety_invariant(),
ok == (old(self).used() + amount as int <= old(self).capacity as int),
ok ==> final(self).reserved == old(self).reserved + amount,
!ok ==> final(self).reserved == old(self).reserved,
{
let headroom = self.available();
if amount <= headroom {
self.reserved = self.reserved + amount;
true
} else {
false
}
}
#[expect(clippy::arithmetic_side_effects, reason = "Verus proves the transfer operands are bounded")]
pub fn commit_reservation(&mut self, amount: u64)
requires
old(self).safety_invariant(),
amount <= old(self).reserved,
ensures
final(self).capacity == old(self).capacity,
final(self).pending_eviction == old(self).pending_eviction,
final(self).allocated == old(self).allocated + amount,
final(self).reserved == old(self).reserved - amount,
final(self).safety_invariant(),
{
assert(self.allocated + amount <= self.capacity);
self.allocated = self.allocated + amount;
self.reserved = self.reserved - amount;
}
#[expect(clippy::arithmetic_side_effects, reason = "Verus proves amount does not exceed allocated")]
pub fn release(&mut self, amount: u64)
requires
old(self).safety_invariant(),
amount <= old(self).allocated,
ensures
final(self).capacity == old(self).capacity,
final(self).reserved == old(self).reserved,
final(self).pending_eviction == old(self).pending_eviction,
final(self).allocated == old(self).allocated - amount,
final(self).safety_invariant(),
{
self.allocated = self.allocated - amount;
}
#[expect(clippy::arithmetic_side_effects, reason = "Verus proves the allocation transfer is bounded")]
pub fn mark_eviction(&mut self, amount: u64)
requires
old(self).safety_invariant(),
amount <= old(self).allocated,
ensures
final(self).capacity == old(self).capacity,
final(self).reserved == old(self).reserved,
final(self).allocated == old(self).allocated - amount,
final(self).pending_eviction == old(self).pending_eviction + amount,
final(self).safety_invariant(),
{
assert(self.pending_eviction + amount <= self.capacity);
self.allocated = self.allocated - amount;
self.pending_eviction = self.pending_eviction + amount;
}
#[expect(clippy::arithmetic_side_effects, reason = "Verus proves amount does not exceed pending eviction")]
pub fn complete_eviction(&mut self, amount: u64)
requires
old(self).safety_invariant(),
amount <= old(self).pending_eviction,
ensures
final(self).capacity == old(self).capacity,
final(self).allocated == old(self).allocated,
final(self).reserved == old(self).reserved,
final(self).pending_eviction == old(self).pending_eviction - amount,
final(self).safety_invariant(),
{
self.pending_eviction = self.pending_eviction - amount;
}
}
}