#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Ledger {
pub treasury: u128,
pub period_costs: u128,
pub period_revenue: u128,
pub seed_capital: u128,
}
fn signed_diff(a: u128, b: u128) -> i128 {
const CAP: u128 = i128::MAX as u128;
if a >= b {
(a - b).min(CAP) as i128
} else {
-((b - a).min(CAP) as i128)
}
}
pub fn net_position(ledger: &Ledger) -> i128 {
signed_diff(ledger.period_revenue, ledger.period_costs)
}
pub fn gross_inflow(ledger: &Ledger) -> u128 {
ledger.period_revenue.saturating_add(ledger.seed_capital)
}
pub fn is_solvent(ledger: &Ledger) -> bool {
ledger.treasury >= ledger.period_costs
}
pub fn is_self_funding(ledger: &Ledger) -> bool {
ledger.period_revenue >= ledger.period_costs
}
pub fn relies_on_seed(ledger: &Ledger) -> bool {
ledger.period_costs > ledger.period_revenue && ledger.seed_capital > 0
}
pub fn runway_cycles(treasury: u128, avg_burn_per_cycle: u128) -> Option<u64> {
if avg_burn_per_cycle == 0 {
return None; }
Some((treasury / avg_burn_per_cycle).min(u64::MAX as u128) as u64)
}
pub fn breakeven_revenue(cost_per_call: u128, calls: u64) -> u128 {
cost_per_call.saturating_mul(calls as u128)
}
pub fn breakeven_price(total_cost: u128, calls: u64) -> u128 {
if calls == 0 {
return 0;
}
total_cost.div_ceil(calls as u128)
}
pub fn margin_per_call(price_per_call: u128, cost_per_call: u128) -> i128 {
signed_diff(price_per_call, cost_per_call)
}
pub fn payroll_total(amounts: &[u128]) -> u128 {
amounts.iter().fold(0u128, |acc, &x| acc.saturating_add(x))
}
#[cfg(test)]
mod tests {
use super::*;
fn ledger(treasury: u128, costs: u128, revenue: u128, seed: u128) -> Ledger {
Ledger { treasury, period_costs: costs, period_revenue: revenue, seed_capital: seed }
}
#[test]
fn net_position_is_earned_revenue_minus_costs() {
assert_eq!(net_position(&ledger(0, 30, 50, 0)), 20); assert_eq!(net_position(&ledger(0, 50, 50, 0)), 0); assert_eq!(net_position(&ledger(0, 80, 50, 0)), -30); }
#[test]
fn net_position_excludes_seed_capital() {
let l = ledger(1_000_000, 5, 0, 1_000_000);
assert_eq!(net_position(&l), -5);
let l = ledger(0, 10, 4, 999);
assert_eq!(net_position(&l), -6);
}
#[test]
fn net_position_saturates_instead_of_overflowing() {
assert_eq!(net_position(&ledger(0, 0, u128::MAX, 0)), i128::MAX);
assert_eq!(net_position(&ledger(0, u128::MAX, 0, 0)), i128::MIN + 1);
}
#[test]
fn gross_inflow_sums_revenue_and_seed() {
assert_eq!(gross_inflow(&ledger(0, 0, 40, 60)), 100);
let l = ledger(0, 90, 40, 60);
assert_eq!(gross_inflow(&l), 100);
assert_eq!(net_position(&l), -50);
assert_eq!(gross_inflow(&ledger(0, 0, u128::MAX, u128::MAX)), u128::MAX); }
#[test]
fn solvency_edges_on_treasury_vs_costs() {
assert!(is_solvent(&ledger(100, 100, 0, 0))); assert!(is_solvent(&ledger(101, 100, 0, 0))); assert!(!is_solvent(&ledger(99, 100, 0, 0))); assert!(is_solvent(&ledger(0, 0, 0, 0))); assert!(!is_solvent(&ledger(0, 1, 0, 0))); }
#[test]
fn self_funding_counts_only_earned_revenue() {
assert!(is_self_funding(&ledger(0, 50, 50, 0))); assert!(is_self_funding(&ledger(0, 50, 80, 0))); assert!(!is_self_funding(&ledger(0, 50, 49, 0))); assert!(!is_self_funding(&ledger(1_000, 50, 10, 1_000)));
assert!(is_self_funding(&ledger(0, 0, 0, 0))); }
#[test]
fn relies_on_seed_flags_the_seed_propped_period() {
assert!(relies_on_seed(&ledger(0, 50, 10, 40))); assert!(!relies_on_seed(&ledger(0, 50, 50, 40))); assert!(!relies_on_seed(&ledger(0, 50, 10, 0))); assert!(!relies_on_seed(&ledger(0, 10, 50, 5))); }
#[test]
fn runway_is_floor_division_of_treasury_by_burn() {
assert_eq!(runway_cycles(100, 10), Some(10)); assert_eq!(runway_cycles(105, 10), Some(10)); assert_eq!(runway_cycles(9, 10), Some(0)); assert_eq!(runway_cycles(0, 10), Some(0)); }
#[test]
fn runway_zero_burn_is_none_not_finite() {
assert_eq!(runway_cycles(100, 0), None); assert_eq!(runway_cycles(0, 0), None);
}
#[test]
fn runway_saturates_to_u64_max() {
assert_eq!(runway_cycles(u128::MAX, 1), Some(u64::MAX));
}
#[test]
fn breakeven_revenue_is_cost_times_calls() {
assert_eq!(breakeven_revenue(3, 10), 30);
assert_eq!(breakeven_revenue(5, 0), 0); assert_eq!(breakeven_revenue(u128::MAX, 2), u128::MAX); }
#[test]
fn breakeven_price_rounds_up_so_you_never_underprice() {
assert_eq!(breakeven_price(100, 10), 10); assert_eq!(breakeven_price(100, 3), 34); assert_eq!(breakeven_price(1, 4), 1); assert_eq!(breakeven_price(0, 4), 0); assert_eq!(breakeven_price(100, 0), 0); }
#[test]
fn margin_per_call_is_signed_price_minus_cost() {
assert_eq!(margin_per_call(5, 3), 2); assert_eq!(margin_per_call(3, 3), 0); assert_eq!(margin_per_call(2, 3), -1); let cost = 7u128;
let price = breakeven_price(cost, 1); assert!(margin_per_call(price, cost) >= 0);
}
#[test]
fn payroll_total_sums_a_batch() {
assert_eq!(payroll_total(&[10, 20, 30]), 60);
assert_eq!(payroll_total(&[]), 0); assert_eq!(payroll_total(&[u128::MAX, 1]), u128::MAX); }
#[test]
fn payroll_pre_check_against_solvency() {
let l = ledger(50, 0, 0, 0);
let run = [20u128, 20, 20];
assert_eq!(payroll_total(&run), 60);
assert!(payroll_total(&run) > l.treasury); }
}