wp-solana-test-core 0.1.1

Protocol-agnostic Solana test infrastructure built on LiteSVM
Documentation
//! Clock sysvar control helpers for [`TestContext`].
//!
//! These functions allow tests to manipulate the LiteSVM `Clock` sysvar,
//! enabling time-sensitive scenarios such as lending protocol interest
//! accrual without waiting for real time to elapse.

use anyhow::Result;
use solana_sdk::clock::Clock;

use crate::context::TestContext;

/// Slot duration in milliseconds (Solana default: 400ms).
const SLOT_DURATION_MS: i64 = 400;

/// Advance the `Clock` sysvar by the given number of seconds.
///
/// Both `unix_timestamp` and `slot` are updated. Slots advance proportionally
/// at 1 slot per 400ms via integer division: `seconds * 1000 / 400`.
///
/// Because this is integer arithmetic, the result truncates toward zero.
/// For example, 1 second yields `1000 / 400 = 2` slots (not 2.5).
///
/// `seconds` must be non-negative; pass negative values to [`set_clock`]
/// instead.
pub fn advance_clock(ctx: &TestContext, seconds: i64) -> Result<()> {
    if seconds < 0 {
        anyhow::bail!("advance_clock does not support negative seconds; use set_clock instead");
    }

    let mut svm = ctx.lock_svm();
    let mut clock = svm.get_sysvar::<Clock>();

    clock.unix_timestamp = clock
        .unix_timestamp
        .checked_add(seconds)
        .ok_or_else(|| anyhow::anyhow!("unix_timestamp overflow when advancing by {seconds}s"))?;

    let slot_delta = (seconds * 1000) / SLOT_DURATION_MS;
    let slot_delta_u64 = u64::try_from(slot_delta)
        .map_err(|_| anyhow::anyhow!("negative slot delta: {slot_delta}"))?;
    clock.slot = clock
        .slot
        .checked_add(slot_delta_u64)
        .ok_or_else(|| anyhow::anyhow!("slot overflow when advancing by {slot_delta} slots"))?;

    svm.set_sysvar::<Clock>(&clock);
    Ok(())
}

/// Set the `Clock` sysvar's `unix_timestamp` to an absolute value.
///
/// Only `unix_timestamp` is modified; `slot` and other clock fields remain
/// unchanged.
pub fn set_clock(ctx: &TestContext, unix_timestamp: i64) -> Result<()> {
    let mut svm = ctx.lock_svm();
    let mut clock = svm.get_sysvar::<Clock>();
    clock.unix_timestamp = unix_timestamp;
    svm.set_sysvar::<Clock>(&clock);
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::context::new_test_context;

    #[test]
    fn test_advance_clock() {
        let ctx = new_test_context().unwrap();
        let before = {
            let svm = ctx.lock_svm();
            svm.get_sysvar::<Clock>().unix_timestamp
        };

        advance_clock(&ctx, 3600).unwrap();

        let after = {
            let svm = ctx.lock_svm();
            svm.get_sysvar::<Clock>().unix_timestamp
        };
        assert_eq!(after - before, 3600);
    }

    #[test]
    fn test_set_clock() {
        let ctx = new_test_context().unwrap();
        let target_ts: i64 = 1_700_000_000;

        set_clock(&ctx, target_ts).unwrap();

        let actual = {
            let svm = ctx.lock_svm();
            svm.get_sysvar::<Clock>().unix_timestamp
        };
        assert_eq!(actual, target_ts);
    }

    #[test]
    fn test_advance_clock_updates_slot() {
        let ctx = new_test_context().unwrap();
        let slot_before = {
            let svm = ctx.lock_svm();
            svm.get_sysvar::<Clock>().slot
        };

        // Advance by 10 seconds → expect 10 * 1000 / 400 = 25 slots
        advance_clock(&ctx, 10).unwrap();

        let slot_after = {
            let svm = ctx.lock_svm();
            svm.get_sysvar::<Clock>().slot
        };
        assert_eq!(slot_after - slot_before, 25);
    }
}