#![forbid(unsafe_code)]
use anchor_client::solana_sdk::{pubkey::Pubkey, sysvar};
use std::str::FromStr;
#[allow(deprecated)]
use anchor_lang::system_program;
#[must_use]
pub fn micro_lamports_to_usdc(micro_lamports: u64) -> f64 {
#[allow(clippy::cast_precision_loss)]
{
micro_lamports as f64 / 1_000_000.0
}
}
#[must_use]
pub fn usdc_to_micro_lamports(usdc_amount: f64) -> u64 {
let result = usdc_amount.max(0.0) * 1_000_000.0;
#[allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_precision_loss
)]
{
result.round().min(18_446_744_073_709_551_615.0) as u64
}
}
#[must_use]
pub fn is_valid_pubkey(address: &str) -> bool {
Pubkey::from_str(address).is_ok()
}
#[must_use]
pub fn system_programs() -> Vec<Pubkey> {
vec![
system_program::ID,
spl_token::id(),
spl_associated_token_account::id(),
sysvar::rent::id(),
sysvar::clock::id(),
]
}
#[must_use]
pub fn format_duration(seconds: u64) -> String {
let days = seconds / 86400;
let hours = (seconds % 86400) / 3600;
let minutes = (seconds % 3600) / 60;
let secs = seconds % 60;
if days > 0 {
format!("{days}d {hours}h {minutes}m {secs}s")
} else if hours > 0 {
format!("{hours}h {minutes}m {secs}s")
} else if minutes > 0 {
format!("{minutes}m {secs}s")
} else {
format!("{secs}s")
}
}
#[must_use]
pub fn calculate_next_renewal(
start_timestamp: i64,
period_seconds: u64,
periods_elapsed: u32,
) -> i64 {
start_timestamp.saturating_add(
period_seconds
.saturating_mul(u64::from(periods_elapsed.saturating_add(1)))
.try_into()
.unwrap_or(i64::MAX),
)
}
#[must_use]
pub fn is_renewal_due(next_renewal_timestamp: i64, grace_period_seconds: u64) -> bool {
let current_timestamp = chrono::Utc::now().timestamp();
let grace_end =
next_renewal_timestamp.saturating_add(grace_period_seconds.try_into().unwrap_or(i64::MAX));
current_timestamp >= next_renewal_timestamp && current_timestamp <= grace_end
}
#[must_use]
pub fn is_subscription_overdue(next_renewal_timestamp: i64, grace_period_seconds: u64) -> bool {
let current_timestamp = chrono::Utc::now().timestamp();
let grace_end =
next_renewal_timestamp.saturating_add(grace_period_seconds.try_into().unwrap_or(i64::MAX));
current_timestamp > grace_end
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_micro_lamports_to_usdc() {
const EPSILON: f64 = 1e-10;
assert!((micro_lamports_to_usdc(1_000_000) - 1.0).abs() < EPSILON);
assert!((micro_lamports_to_usdc(5_500_000) - 5.5).abs() < EPSILON);
assert!((micro_lamports_to_usdc(0) - 0.0).abs() < EPSILON);
assert!((micro_lamports_to_usdc(500_000) - 0.5).abs() < EPSILON);
}
#[test]
fn test_usdc_to_micro_lamports() {
assert_eq!(usdc_to_micro_lamports(1.0), 1_000_000);
assert_eq!(usdc_to_micro_lamports(5.5), 5_500_000);
assert_eq!(usdc_to_micro_lamports(0.0), 0);
assert_eq!(usdc_to_micro_lamports(0.5), 500_000);
assert_eq!(usdc_to_micro_lamports(-1.0), 0);
}
#[test]
fn test_is_valid_pubkey() {
assert!(is_valid_pubkey("11111111111111111111111111111112"));
assert!(!is_valid_pubkey("invalid_address"));
assert!(!is_valid_pubkey(""));
assert!(!is_valid_pubkey("too_short"));
}
#[test]
fn test_system_programs() {
let programs = system_programs();
assert!(!programs.is_empty());
assert!(programs.contains(&system_program::ID));
assert!(programs.contains(&spl_token::id()));
}
#[test]
fn test_format_duration() {
assert_eq!(format_duration(30), "30s");
assert_eq!(format_duration(90), "1m 30s");
assert_eq!(format_duration(3661), "1h 1m 1s");
assert_eq!(format_duration(90061), "1d 1h 1m 1s");
assert_eq!(format_duration(0), "0s");
assert_eq!(format_duration(60), "1m 0s");
assert_eq!(format_duration(3600), "1h 0m 0s");
assert_eq!(format_duration(86400), "1d 0h 0m 0s");
}
#[test]
fn test_calculate_next_renewal() {
let start = 1000_i64;
let period = 2_592_000_u64;
assert_eq!(
calculate_next_renewal(start, period, 0),
start + i64::try_from(period).unwrap()
);
assert_eq!(
calculate_next_renewal(start, period, 1),
start + i64::try_from(2 * period).unwrap()
);
}
#[test]
fn test_is_renewal_due() {
let now = chrono::Utc::now().timestamp();
let grace_period = 86400;
let past_renewal = now - 3600; assert!(is_renewal_due(past_renewal, grace_period));
let future_renewal = now + 3600; assert!(!is_renewal_due(future_renewal, grace_period));
}
#[test]
fn test_is_subscription_overdue() {
let now = chrono::Utc::now().timestamp();
let grace_period = 86400;
let way_past_renewal = now - (2 * 86400); assert!(is_subscription_overdue(way_past_renewal, grace_period));
let recent_past_renewal = now - 3600; assert!(!is_subscription_overdue(recent_past_renewal, grace_period));
}
}