use std::cmp;
use crate::error::{MathError, MathResult};
use crate::spend_limits::get_remaining_timeframe_limit;
pub const USDC_BASE_UNITS_PER_CENT: u64 = 10_000;
pub fn usdc_base_units_to_cents(base_units: u64) -> MathResult<u64> {
base_units
.checked_div(USDC_BASE_UNITS_PER_CENT)
.ok_or(MathError::Overflow)
}
pub fn calculate_spend_limit_cents(
vault: &pyra_types::Vault,
max_transaction_limit_cents: u64,
now_unix: u64,
) -> MathResult<u64> {
let timeframe_base_units = get_remaining_timeframe_limit(vault, now_unix);
let transaction_limit_cents = usdc_base_units_to_cents(vault.spend_limit_per_transaction)?;
let timeframe_remaining_cents = usdc_base_units_to_cents(timeframe_base_units)?;
let spend_limit_no_cap = cmp::min(transaction_limit_cents, timeframe_remaining_cents);
Ok(cmp::min(spend_limit_no_cap, max_transaction_limit_cents))
}
#[cfg(test)]
#[allow(
clippy::allow_attributes,
clippy::allow_attributes_without_reason,
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::arithmetic_side_effects,
reason = "test code"
)]
mod tests {
use super::*;
use pyra_types::Vault;
fn make_vault(
spend_limit_per_transaction: u64,
spend_limit_per_timeframe: u64,
remaining_spend_limit_per_timeframe: u64,
next_timeframe_reset_timestamp: u64,
timeframe_in_seconds: u64,
) -> Vault {
Vault {
owner: vec![0; 32],
bump: 0,
spend_limit_per_transaction,
spend_limit_per_timeframe,
remaining_spend_limit_per_timeframe,
next_timeframe_reset_timestamp,
timeframe_in_seconds,
}
}
#[test]
fn cents_basic() {
assert_eq!(usdc_base_units_to_cents(1_000_000).unwrap(), 100); }
#[test]
fn cents_zero() {
assert_eq!(usdc_base_units_to_cents(0).unwrap(), 0);
}
#[test]
fn cents_sub_cent_truncates() {
assert_eq!(usdc_base_units_to_cents(9_999).unwrap(), 0); assert_eq!(usdc_base_units_to_cents(10_000).unwrap(), 1); }
const NOW: u64 = 1_700_000_000;
#[test]
fn spend_limit_basic() {
let vault = make_vault(
10_000_000, 50_000_000, 30_000_000, NOW + 3600, 86_400,
);
let limit = calculate_spend_limit_cents(&vault, 10_000, NOW).unwrap();
assert_eq!(limit, 1000);
}
#[test]
fn spend_limit_expired_timeframe_uses_full() {
let vault = make_vault(
100_000_000, 50_000_000, 10_000_000, NOW - 100, 86_400,
);
let limit = calculate_spend_limit_cents(&vault, 100_000, NOW).unwrap();
assert_eq!(limit, 5000);
}
#[test]
fn spend_limit_capped_by_max() {
let vault = make_vault(
1_000_000_000, 1_000_000_000, 1_000_000_000, NOW + 3600,
86_400,
);
let limit = calculate_spend_limit_cents(&vault, 500, NOW).unwrap();
assert_eq!(limit, 500);
}
#[test]
fn spend_limit_zero_timeframe() {
let vault = make_vault(10_000_000, 50_000_000, 30_000_000, NOW + 3600, 0);
let limit = calculate_spend_limit_cents(&vault, 10_000, NOW).unwrap();
assert_eq!(limit, 0);
}
}
#[cfg(test)]
#[allow(
clippy::allow_attributes,
clippy::allow_attributes_without_reason,
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::arithmetic_side_effects,
reason = "test code"
)]
mod proptests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn usdc_cents_never_exceeds_base_units(base_units in 0u64..=u64::MAX) {
let cents = usdc_base_units_to_cents(base_units).unwrap();
prop_assert!(cents <= base_units, "cents {} > base_units {}", cents, base_units);
}
}
}