Skip to main content

pyra_margin/
spend_limits.rs

1use pyra_types::Vault;
2
3/// Returns the effective remaining spend limit for the current timeframe in base units.
4///
5/// - If `timeframe_in_seconds == 0`, spending is disabled — returns 0.
6/// - If the timeframe has expired (`next_timeframe_reset_timestamp < now_unix`),
7///   returns the full `spend_limit_per_timeframe`.
8/// - Otherwise, returns `remaining_spend_limit_per_timeframe`.
9///
10/// `now_unix` is the current Unix timestamp in seconds (e.g. `Utc::now().timestamp() as u64`).
11/// It is passed as a parameter instead of calling `Utc::now()` directly to keep this
12/// function pure and testable without time mocking.
13pub fn get_remaining_timeframe_limit(vault: &Vault, now_unix: u64) -> u64 {
14    if vault.timeframe_in_seconds == 0 {
15        return 0;
16    }
17
18    if vault.next_timeframe_reset_timestamp < now_unix {
19        vault.spend_limit_per_timeframe
20    } else {
21        vault.remaining_spend_limit_per_timeframe
22    }
23}
24
25#[cfg(test)]
26#[allow(
27    clippy::allow_attributes,
28    clippy::allow_attributes_without_reason,
29    clippy::unwrap_used,
30    clippy::expect_used,
31    clippy::panic,
32    clippy::arithmetic_side_effects,
33    reason = "test code"
34)]
35mod tests {
36    use super::*;
37
38    fn make_vault(
39        timeframe_in_seconds: u64,
40        next_timeframe_reset_timestamp: u64,
41        spend_limit_per_timeframe: u64,
42        remaining_spend_limit_per_timeframe: u64,
43    ) -> Vault {
44        Vault {
45            owner: vec![0; 32],
46            bump: 0,
47            spend_limit_per_transaction: 0,
48            spend_limit_per_timeframe,
49            remaining_spend_limit_per_timeframe,
50            next_timeframe_reset_timestamp,
51            timeframe_in_seconds,
52        }
53    }
54
55    const NOW: u64 = 1_700_000_000;
56
57    #[test]
58    fn zero_timeframe_blocks_spending() {
59        let vault = make_vault(0, 0, 1_000_000, 500_000);
60        assert_eq!(get_remaining_timeframe_limit(&vault, NOW), 0);
61    }
62
63    #[test]
64    fn zero_timeframe_blocks_even_with_future_reset() {
65        let vault = make_vault(0, NOW + 9999, 1_000_000, 500_000);
66        assert_eq!(get_remaining_timeframe_limit(&vault, NOW), 0);
67    }
68
69    #[test]
70    fn expired_timeframe_returns_full_limit() {
71        let vault = make_vault(86_400, NOW - 100, 1_000_000, 200_000);
72        assert_eq!(get_remaining_timeframe_limit(&vault, NOW), 1_000_000);
73    }
74
75    #[test]
76    fn active_timeframe_returns_remaining() {
77        let vault = make_vault(86_400, NOW + 3600, 1_000_000, 300_000);
78        assert_eq!(get_remaining_timeframe_limit(&vault, NOW), 300_000);
79    }
80
81    #[test]
82    fn reset_at_exact_now_returns_remaining() {
83        // next_reset == now means the reset hasn't happened yet (< vs <=)
84        let vault = make_vault(86_400, NOW, 1_000_000, 400_000);
85        assert_eq!(get_remaining_timeframe_limit(&vault, NOW), 400_000);
86    }
87
88    #[test]
89    fn reset_one_second_before_now_returns_full() {
90        let vault = make_vault(86_400, NOW - 1, 1_000_000, 100_000);
91        assert_eq!(get_remaining_timeframe_limit(&vault, NOW), 1_000_000);
92    }
93}