use alea_sdk::{config_pda, is_round_recent, Config, PROGRAM_ID};
use anchor_lang::AccountDeserialize;
use solana_client::rpc_client::RpcClient;
use solana_sdk::{commitment_config::CommitmentConfig, sysvar};
const DEVNET_RPC: &str = "https://api.devnet.solana.com";
const MAX_AGE_SECONDS: u64 = 30;
fn deserialize_clock(data: &[u8]) -> solana_sdk::clock::Clock {
bincode::deserialize(data).expect("Clock sysvar account data must bincode-deserialize")
}
fn clock_to_anchor(
clock: solana_sdk::clock::Clock,
) -> anchor_lang::solana_program::sysvar::clock::Clock {
anchor_lang::solana_program::sysvar::clock::Clock {
slot: clock.slot,
epoch_start_timestamp: clock.epoch_start_timestamp,
epoch: clock.epoch,
leader_schedule_epoch: clock.leader_schedule_epoch,
unix_timestamp: clock.unix_timestamp,
}
}
#[test]
#[ignore = "hits devnet — run explicitly with `-- --ignored`"]
fn is_round_recent_against_live_devnet_clock() {
let rpc = RpcClient::new_with_commitment(DEVNET_RPC.to_string(), CommitmentConfig::confirmed());
let clock_data = rpc
.get_account_data(&sysvar::clock::ID)
.expect("failed to fetch Clock sysvar from devnet");
let clock_sdk = deserialize_clock(&clock_data);
let clock = clock_to_anchor(clock_sdk.clone());
println!(
"[devnet_clock] Clock slot={} unix_timestamp={}",
clock.slot, clock.unix_timestamp
);
let (pda, _bump) = config_pda(&PROGRAM_ID);
println!("[devnet_clock] Config PDA: {}", pda);
let config_data = rpc
.get_account_data(&pda)
.expect("failed to fetch Config PDA — is the program initialized on devnet?");
assert_eq!(
config_data.len(),
Config::LEN,
"Config PDA must be exactly 217 bytes"
);
let config = Config::try_deserialize(&mut config_data.as_ref())
.expect("Config PDA data must Anchor-deserialize");
println!(
"[devnet_clock] Config genesis_time={} period={}",
config.genesis_time, config.period
);
let current_timestamp = clock.unix_timestamp as u64;
assert!(
current_timestamp >= config.genesis_time,
"devnet slot timestamp ({}) must be after evmnet genesis ({})",
current_timestamp,
config.genesis_time
);
let current_round = ((current_timestamp - config.genesis_time) / config.period) + 1;
println!("[devnet_clock] computed current_round={}", current_round);
let recent = current_round - 1;
assert!(
is_round_recent(recent, &config, &clock, MAX_AGE_SECONDS),
"round {} (~{}s ago) should be recent under {}s max_age",
recent,
config.period,
MAX_AGE_SECONDS
);
let stale = current_round.saturating_sub(200);
assert!(
!is_round_recent(stale, &config, &clock, MAX_AGE_SECONDS),
"round {} (~{}s ago) should be stale under {}s max_age",
stale,
200 * config.period,
MAX_AGE_SECONDS
);
let boundary_rounds_back = MAX_AGE_SECONDS / config.period;
let boundary = current_round.saturating_sub(boundary_rounds_back);
assert!(
is_round_recent(boundary, &config, &clock, MAX_AGE_SECONDS),
"round {} (~{}s ago) at the max_age boundary should be accepted (<= inclusive)",
boundary,
boundary_rounds_back * config.period
);
println!("[devnet_clock] ✓ all 3 live-Clock assertions passed");
}