use antegen_thread_program::{
constants::*,
state::{
compile_instruction, decompile_instruction, CommissionCalculator, FiberState,
PaymentProcessor, Schedule, Signal, Thread, ThreadConfig, Trigger, CURRENT_THREAD_VERSION,
SEED_THREAD_FIBER,
},
utils::{calculate_jitter_offset, next_timestamp},
};
use solana_sdk::{
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
};
mod common;
use common::setup::{FIBER_PROGRAM_ID, PROGRAM_ID};
fn make_thread(fiber_ids: Vec<u8>, fiber_cursor: u8) -> Thread {
Thread {
version: CURRENT_THREAD_VERSION,
bump: 0,
authority: Pubkey::new_unique(),
id: b"test".to_vec(),
name: "test".to_string(),
created_at: 0,
trigger: Trigger::Immediate { jitter: 0 },
schedule: Schedule::Timed { prev: 0, next: 0 },
fiber_ids,
fiber_cursor,
fiber_next_id: 0,
fiber_signal: Signal::None,
paused: false,
exec_count: 0,
last_executor: Pubkey::default(),
nonce_account: PROGRAM_ID, last_nonce: String::new(),
close_fiber: Vec::new(),
}
}
#[test]
fn test_advance_to_next_fiber_sequential() {
let mut thread = make_thread(vec![0, 1, 2], 0);
thread.advance_to_next_fiber();
assert_eq!(thread.fiber_cursor, 1);
thread.advance_to_next_fiber();
assert_eq!(thread.fiber_cursor, 2);
}
#[test]
fn test_advance_to_next_fiber_wrap() {
let mut thread = make_thread(vec![0, 1, 2], 2);
thread.advance_to_next_fiber();
assert_eq!(thread.fiber_cursor, 0);
}
#[test]
fn test_advance_to_next_fiber_empty() {
let mut thread = make_thread(vec![], 0);
thread.advance_to_next_fiber();
assert_eq!(thread.fiber_cursor, 0);
}
#[test]
fn test_advance_to_next_fiber_single() {
let mut thread = make_thread(vec![0], 0);
thread.advance_to_next_fiber();
assert_eq!(thread.fiber_cursor, 0); }
#[test]
fn test_advance_to_next_fiber_cursor_not_found() {
let mut thread = make_thread(vec![0, 2, 4], 3); thread.advance_to_next_fiber();
assert_eq!(thread.fiber_cursor, 0); }
#[test]
fn test_has_nonce_account_true() {
let mut thread = make_thread(vec![], 0);
thread.nonce_account = Pubkey::new_unique(); assert!(thread.has_nonce_account());
}
#[test]
fn test_has_nonce_account_false_system_program() {
let mut thread = make_thread(vec![], 0);
thread.nonce_account = solana_system_interface::program::ID;
assert!(!thread.has_nonce_account());
}
#[test]
fn test_has_nonce_account_false_program_id() {
let mut thread = make_thread(vec![], 0);
thread.nonce_account = PROGRAM_ID; assert!(!thread.has_nonce_account());
}
#[test]
fn test_validate_no_fibers() {
let thread = make_thread(vec![], 0);
assert!(thread.validate_for_execution().is_err());
}
#[test]
fn test_validate_invalid_cursor() {
let thread = make_thread(vec![0, 1], 5); assert!(thread.validate_for_execution().is_err());
}
#[test]
fn test_validate_valid_cursor() {
let thread = make_thread(vec![0], 0);
assert!(thread.validate_for_execution().is_ok());
}
#[test]
fn test_is_ready_timed_past() {
let mut thread = make_thread(vec![0], 0);
thread.schedule = Schedule::Timed {
prev: 100,
next: 200,
};
assert!(thread.is_ready(0, 200)); assert!(thread.is_ready(0, 300)); }
#[test]
fn test_is_ready_timed_future() {
let mut thread = make_thread(vec![0], 0);
thread.schedule = Schedule::Timed {
prev: 100,
next: 200,
};
assert!(!thread.is_ready(0, 100)); }
#[test]
fn test_is_ready_block_slot() {
let mut thread = make_thread(vec![0], 0);
thread.trigger = Trigger::Slot { slot: 100 };
thread.schedule = Schedule::Block {
prev: 50,
next: 100,
};
assert!(thread.is_ready(100, 0)); assert!(!thread.is_ready(99, 0)); }
#[test]
fn test_compile_decompile_roundtrip() {
let program_id = Pubkey::new_unique();
let account1 = Pubkey::new_unique();
let account2 = Pubkey::new_unique();
let ix = Instruction {
program_id,
accounts: vec![
AccountMeta::new(account1, true),
AccountMeta::new_readonly(account2, false),
],
data: vec![1, 2, 3, 4],
};
let compiled = compile_instruction(ix.clone()).unwrap();
let decompiled = decompile_instruction(&compiled).unwrap();
assert_eq!(decompiled.program_id, ix.program_id);
assert_eq!(decompiled.data, ix.data);
assert_eq!(decompiled.accounts.len(), ix.accounts.len());
}
#[test]
fn test_compiled_account_sorting() {
let program_id = Pubkey::new_unique();
let ro_account = Pubkey::new_unique();
let rw_signer = Pubkey::new_unique();
let rw_account = Pubkey::new_unique();
let ix = Instruction {
program_id,
accounts: vec![
AccountMeta::new_readonly(ro_account, false),
AccountMeta::new(rw_signer, true),
AccountMeta::new(rw_account, false),
],
data: vec![],
};
let compiled = compile_instruction(ix).unwrap();
assert_eq!(compiled.num_rw_signers, 1);
assert_eq!(compiled.num_ro_signers, 0);
assert_eq!(compiled.num_rw, 1); }
fn make_config() -> ThreadConfig {
ThreadConfig {
version: 1,
bump: 0,
admin: Pubkey::new_unique(),
paused: false,
commission_fee: 1000,
executor_fee_bps: 9000,
core_team_bps: 1000,
grace_period_seconds: 5,
fee_decay_seconds: 295,
}
}
#[test]
fn test_commission_within_grace() {
let config = make_config();
let multiplier = config.calculate_commission_multiplier(3); assert_eq!(multiplier, 1.0);
}
#[test]
fn test_commission_during_decay() {
let config = make_config();
let time = 5 + 147; let multiplier = config.calculate_commission_multiplier(time);
assert!(multiplier < 1.0 && multiplier > 0.0);
}
#[test]
fn test_commission_expired() {
let config = make_config();
let multiplier = config.calculate_commission_multiplier(301);
assert_eq!(multiplier, 0.0);
}
#[test]
fn test_payment_normal() {
let config = make_config();
let payments = config.calculate_payments(0, -5000, false);
assert_eq!(payments.fee_payer_reimbursement, 5000); assert!(payments.executor_commission > 0);
assert!(payments.core_team_fee > 0);
}
#[test]
fn test_payment_forgo_commission() {
let config = make_config();
let payments = config.calculate_payments(0, -5000, true); assert_eq!(payments.executor_commission, 0);
assert!(payments.core_team_fee > 0); }
#[test]
fn test_payment_no_payment_positive_balance() {
let config = make_config();
let payments = config.calculate_payments(0, 5000, false);
assert_eq!(payments.fee_payer_reimbursement, 0);
assert_eq!(payments.executor_commission, 0);
assert!(payments.core_team_fee > 0);
}
#[test]
fn test_jitter_zero() {
let pubkey = Pubkey::new_unique();
assert_eq!(calculate_jitter_offset(100, &pubkey, 0), 0);
}
#[test]
fn test_jitter_deterministic() {
let pubkey = Pubkey::new_unique();
let j1 = calculate_jitter_offset(100, &pubkey, 60);
let j2 = calculate_jitter_offset(100, &pubkey, 60);
assert_eq!(j1, j2); }
#[test]
fn test_jitter_bounded() {
let pubkey = Pubkey::new_unique();
let jitter = 60u64;
let offset = calculate_jitter_offset(12345, &pubkey, jitter);
assert!(offset >= 0);
assert!((offset as u64) < jitter);
}
#[test]
fn test_next_timestamp_cron() {
let after = 1700000000i64; let schedule = "0 * * * * * *".to_string(); let result = next_timestamp(after, schedule);
assert!(result.is_some());
let next = result.unwrap();
assert!(next > after);
}
#[test]
fn test_thread_pda_derivation() {
let authority = Pubkey::new_unique();
let id = b"test-thread";
let (expected, _bump) =
Pubkey::find_program_address(&[SEED_THREAD, authority.as_ref(), id], &PROGRAM_ID);
let derived = Thread::pubkey(authority, id);
assert_eq!(derived, expected);
}
#[test]
fn test_fiber_pda_derivation() {
let thread = Pubkey::new_unique();
let index = 3u8;
let (expected, _bump) = Pubkey::find_program_address(
&[SEED_THREAD_FIBER, thread.as_ref(), &[index]],
&FIBER_PROGRAM_ID,
);
let derived = FiberState::pubkey(thread, index);
assert_eq!(derived, expected);
}