# Sysvars (System Variables) This reference provides comprehensive coverage of Solana System Variables (sysvars) for native Rust program development, including access patterns, use cases, and performance implications. ## Table of Contents 1. [What are Sysvars](#what-are-sysvars) 2. [Clock Sysvar](#clock-sysvar) 3. [Rent Sysvar](#rent-sysvar) 4. [EpochSchedule Sysvar](#epochschedule-sysvar) 5. [SlotHashes Sysvar](#slothashes-sysvar) 6. [Other Sysvars](#other-sysvars) 7. [Access Patterns](#access-patterns) 8. [Performance Implications](#performance-implications) 9. [Best Practices](#best-practices) --- ## What are Sysvars **System Variables (sysvars)** are special accounts that provide programs with access to blockchain state and cluster information. ### Key Characteristics 1. **Cluster-wide state:** Same values for all programs in the same slot 2. **Updated automatically:** Runtime maintains values 3. **Predictable addresses:** Well-known pubkeys 4. **Read-only:** Programs cannot modify sysvars 5. **Low CU cost:** Cheaper than account reads ### When to Use Sysvars **Use sysvars when you need:** - Current timestamp or slot number - Rent exemption calculations - Epoch and slot timing information - Recent block hashes (for verification) - Stake history or epoch rewards **Don't use sysvars for:** - User-specific data (use accounts) - Program state (use PDAs) - Cross-program communication (use CPIs) --- ## Clock Sysvar **Address:** `solana_program::sysvar::clock::ID` The Clock sysvar provides timing information about the blockchain. ### Clock Structure ```rust use solana_program::clock::Clock; pub struct Clock { pub slot: Slot, // Current slot pub epoch_start_timestamp: i64, // Timestamp of epoch start (approximate) pub epoch: Epoch, // Current epoch pub leader_schedule_epoch: Epoch, // Epoch for which leader schedule is valid pub unix_timestamp: UnixTimestamp, // Estimated wall-clock Unix timestamp } ``` ### Accessing Clock **Pattern 1: get() (Recommended)** ```rust use solana_program::clock::Clock; use solana_program::sysvar::Sysvar; pub fn process_instruction( program_id: &Pubkey, accounts: &[AccountInfo], _instruction_data: &[u8], ) -> ProgramResult { // Get Clock directly (no account needed) let clock = Clock::get()?; msg!("Current slot: {}", clock.slot); msg!("Current timestamp: {}", clock.unix_timestamp); msg!("Current epoch: {}", clock.epoch); Ok(()) } ``` **Pattern 2: From account** ```rust use solana_program::sysvar::clock; pub fn process_with_account( accounts: &[AccountInfo], ) -> ProgramResult { let account_info_iter = &mut accounts.iter(); let clock_account = next_account_info(account_info_iter)?; // Verify it's the Clock sysvar if clock_account.key != &clock::ID { return Err(ProgramError::InvalidArgument); } let clock = Clock::from_account_info(clock_account)?; msg!("Timestamp: {}", clock.unix_timestamp); Ok(()) } ``` **⚠️ Recommendation:** Use `Clock::get()` unless you specifically need the account for validation. ### Common Clock Use Cases **1. Timestamping events:** ```rust use solana_program::clock::Clock; use solana_program::sysvar::Sysvar; #[derive(BorshSerialize, BorshDeserialize)] pub struct Event { pub created_at: i64, pub data: Vec, } pub fn create_event( event_account: &AccountInfo, data: Vec, ) -> ProgramResult { let clock = Clock::get()?; let event = Event { created_at: clock.unix_timestamp, data, }; event.serialize(&mut &mut event_account.data.borrow_mut()[..])?; Ok(()) } ``` **2. Time-based logic (vesting, expiration):** ```rust pub fn check_vesting( vesting_account: &AccountInfo, ) -> ProgramResult { let clock = Clock::get()?; let vesting = VestingSchedule::try_from_slice(&vesting_account.data.borrow())?; if clock.unix_timestamp < vesting.unlock_timestamp { msg!("Tokens still locked until {}", vesting.unlock_timestamp); return Err(ProgramError::Custom(1)); // Locked } msg!("Vesting unlocked!"); Ok(()) } ``` **3. Slot-based mechanics:** ```rust pub fn process_epoch_transition( state_account: &AccountInfo, ) -> ProgramResult { let clock = Clock::get()?; let mut state = State::try_from_slice(&state_account.data.borrow())?; if clock.epoch > state.last_processed_epoch { msg!("Processing epoch transition: {} -> {}", state.last_processed_epoch, clock.epoch); // Process epoch rewards, resets, etc. state.last_processed_epoch = clock.epoch; state.serialize(&mut &mut state_account.data.borrow_mut()[..])?; } Ok(()) } ``` ### Clock Gotchas **⚠️ unix_timestamp is approximate:** ```rust // ❌ Don't use for precise timing if clock.unix_timestamp == expected_timestamp { // Risky! // Might miss by seconds } // ✅ Use ranges for time checks if clock.unix_timestamp >= unlock_time { // Safe } ``` **⚠️ Timestamps can vary across validators:** The `unix_timestamp` is based on validator voting and may differ slightly between validators in the same slot. Don't assume exact precision. --- ## Rent Sysvar **Address:** `solana_program::sysvar::rent::ID` The Rent sysvar provides rent calculation parameters. ### Rent Structure ```rust use solana_program::rent::Rent; pub struct Rent { pub lamports_per_byte_year: u64, // Base rent rate pub exemption_threshold: f64, // Multiplier for exemption (2.0 = 2 years) pub burn_percent: u8, // Percentage of rent burned } ``` ### Accessing Rent **Pattern 1: get() (Recommended)** ```rust use solana_program::rent::Rent; use solana_program::sysvar::Sysvar; pub fn calculate_rent_exemption( data_size: usize, ) -> Result { let rent = Rent::get()?; // Calculate minimum balance for rent exemption let min_balance = rent.minimum_balance(data_size); msg!("Minimum balance for {} bytes: {} lamports", data_size, min_balance); Ok(min_balance) } ``` **Pattern 2: From account** ```rust use solana_program::sysvar::rent; pub fn check_rent_exemption( accounts: &[AccountInfo], ) -> ProgramResult { let account_info_iter = &mut accounts.iter(); let data_account = next_account_info(account_info_iter)?; let rent_account = next_account_info(account_info_iter)?; if rent_account.key != &rent::ID { return Err(ProgramError::InvalidArgument); } let rent = Rent::from_account_info(rent_account)?; if !rent.is_exempt(data_account.lamports(), data_account.data_len()) { msg!("Account is not rent-exempt!"); return Err(ProgramError::AccountNotRentExempt); } Ok(()) } ``` ### Common Rent Use Cases **1. Account creation with rent exemption:** ```rust use solana_program::rent::Rent; use solana_program::system_instruction; use solana_program::program::invoke_signed; pub fn create_account_rent_exempt( payer: &AccountInfo, new_account: &AccountInfo, system_program: &AccountInfo, program_id: &Pubkey, seeds: &[&[u8]], space: usize, ) -> ProgramResult { let rent = Rent::get()?; let min_balance = rent.minimum_balance(space); msg!("Creating account with {} lamports for {} bytes", min_balance, space); let create_account_ix = system_instruction::create_account( payer.key, new_account.key, min_balance, space as u64, program_id, ); invoke_signed( &create_account_ix, &[payer.clone(), new_account.clone(), system_program.clone()], &[seeds], )?; Ok(()) } ``` **2. Validating account has sufficient balance:** ```rust pub fn validate_rent_exempt_account( account: &AccountInfo, ) -> ProgramResult { let rent = Rent::get()?; if !rent.is_exempt(account.lamports(), account.data_len()) { let required = rent.minimum_balance(account.data_len()); let current = account.lamports(); msg!("Account not rent-exempt: has {} lamports, needs {}", current, required); return Err(ProgramError::AccountNotRentExempt); } Ok(()) } ``` **3. Calculating required lamports for reallocation:** ```rust pub fn reallocate_account( account: &AccountInfo, new_size: usize, ) -> ProgramResult { let rent = Rent::get()?; let old_size = account.data_len(); let current_lamports = account.lamports(); let new_min_balance = rent.minimum_balance(new_size); if new_size > old_size { // Growing account - ensure sufficient lamports if current_lamports < new_min_balance { msg!("Need {} more lamports for reallocation", new_min_balance - current_lamports); return Err(ProgramError::InsufficientFunds); } } account.realloc(new_size, false)?; Ok(()) } ``` --- ## EpochSchedule Sysvar **Address:** `solana_program::sysvar::epoch_schedule::ID` The EpochSchedule sysvar provides information about epoch timing and slot calculations. ### EpochSchedule Structure ```rust use solana_program::epoch_schedule::EpochSchedule; pub struct EpochSchedule { pub slots_per_epoch: u64, // Slots per epoch after warmup pub leader_schedule_slot_offset: u64, // Offset for leader schedule pub warmup: bool, // Whether in warmup period pub first_normal_epoch: Epoch, // First non-warmup epoch pub first_normal_slot: Slot, // First slot of first normal epoch } ``` ### Accessing EpochSchedule ```rust use solana_program::sysvar::epoch_schedule::EpochSchedule; use solana_program::sysvar::Sysvar; pub fn get_epoch_info() -> ProgramResult { let epoch_schedule = EpochSchedule::get()?; msg!("Slots per epoch: {}", epoch_schedule.slots_per_epoch); msg!("First normal epoch: {}", epoch_schedule.first_normal_epoch); msg!("Warmup: {}", epoch_schedule.warmup); Ok(()) } ``` ### Common EpochSchedule Use Cases **1. Calculating epoch from slot:** ```rust use solana_program::clock::Clock; use solana_program::epoch_schedule::EpochSchedule; pub fn calculate_epoch_from_slot( slot: u64, ) -> Result { let epoch_schedule = EpochSchedule::get()?; let epoch = epoch_schedule.get_epoch(slot); msg!("Slot {} is in epoch {}", slot, epoch); Ok(epoch) } ``` **2. Determining slots remaining in epoch:** ```rust pub fn slots_until_epoch_end() -> Result { let clock = Clock::get()?; let epoch_schedule = EpochSchedule::get()?; let current_slot = clock.slot; let current_epoch = clock.epoch; // Get first slot of next epoch let next_epoch_start = epoch_schedule.get_first_slot_in_epoch(current_epoch + 1); let remaining = next_epoch_start - current_slot; msg!("Slots remaining in epoch: {}", remaining); Ok(remaining) } ``` **3. Epoch-based reward distribution:** ```rust #[derive(BorshSerialize, BorshDeserialize)] pub struct RewardState { pub last_distribution_epoch: u64, pub total_distributed: u64, } pub fn distribute_epoch_rewards( reward_state_account: &AccountInfo, ) -> ProgramResult { let clock = Clock::get()?; let mut state = RewardState::try_from_slice(&reward_state_account.data.borrow())?; if clock.epoch > state.last_distribution_epoch { let epochs_passed = clock.epoch - state.last_distribution_epoch; msg!("Distributing rewards for {} epochs", epochs_passed); // Distribute rewards let reward_amount = epochs_passed * 1000; // Example state.total_distributed += reward_amount; state.last_distribution_epoch = clock.epoch; state.serialize(&mut &mut reward_state_account.data.borrow_mut()[..])?; } Ok(()) } ``` --- ## SlotHashes Sysvar **Address:** `solana_program::sysvar::slot_hashes::ID` The SlotHashes sysvar contains recent slot hashes for verification purposes. ### SlotHashes Structure ```rust use solana_program::slot_hashes::SlotHashes; // SlotHashes contains up to 512 recent (slot, hash) pairs pub struct SlotHashes { // Vector of (slot, hash) tuples // Most recent first, up to MAX_ENTRIES (512) } ``` ### Accessing SlotHashes ```rust use solana_program::sysvar::slot_hashes::SlotHashes; use solana_program::sysvar::Sysvar; pub fn verify_recent_slot( claimed_slot: u64, claimed_hash: &[u8; 32], ) -> ProgramResult { let slot_hashes = SlotHashes::get()?; // Check if slot is in recent history for (slot, hash) in slot_hashes.iter() { if *slot == claimed_slot { if hash.as_ref() == claimed_hash { msg!("Slot hash verified!"); return Ok(()); } else { msg!("Slot hash mismatch!"); return Err(ProgramError::InvalidArgument); } } } msg!("Slot not found in recent history"); Err(ProgramError::InvalidArgument) } ``` ### Common SlotHashes Use Cases **1. Verifying transaction recency:** ```rust pub fn verify_transaction_recent( slot_hashes_account: &AccountInfo, claimed_slot: u64, ) -> ProgramResult { let slot_hashes = SlotHashes::from_account_info(slot_hashes_account)?; // Check if claimed slot is in recent 512 slots let is_recent = slot_hashes.iter().any(|(slot, _)| *slot == claimed_slot); if !is_recent { msg!("Transaction too old or slot invalid"); return Err(ProgramError::Custom(1)); } Ok(()) } ``` **2. Preventing replay attacks:** ```rust #[derive(BorshSerialize, BorshDeserialize)] pub struct ProcessedSlot { pub slot: u64, pub hash: [u8; 32], } pub fn process_once_per_slot( state_account: &AccountInfo, ) -> ProgramResult { let slot_hashes = SlotHashes::get()?; let mut state = ProcessedSlot::try_from_slice(&state_account.data.borrow())?; // Get current slot and hash let (current_slot, current_hash) = slot_hashes.iter().next() .ok_or(ProgramError::InvalidArgument)?; if state.slot == *current_slot { msg!("Already processed in this slot!"); return Err(ProgramError::Custom(2)); // Already processed } // Update state state.slot = *current_slot; state.hash = current_hash.to_bytes(); state.serialize(&mut &mut state_account.data.borrow_mut()[..])?; Ok(()) } ``` **⚠️ Note:** SlotHashes only maintains the most recent 512 slots. For older verification, use a different approach. --- ## Other Sysvars ### StakeHistory **Address:** `solana_program::sysvar::stake_history::ID` Provides historical stake activation and deactivation information. ```rust use solana_program::sysvar::stake_history::StakeHistory; pub fn get_stake_history() -> ProgramResult { let stake_history = StakeHistory::get()?; // Access historical stake data by epoch msg!("Stake history available"); Ok(()) } ``` **Use cases:** - Stake pool programs - Historical stake analysis - Reward calculations ### EpochRewards **Address:** `solana_program::sysvar::epoch_rewards::ID` Provides information about epoch rewards distribution (if active). ```rust use solana_program::sysvar::epoch_rewards::EpochRewards; pub fn check_epoch_rewards() -> ProgramResult { let epoch_rewards = EpochRewards::get()?; msg!("Epoch rewards data available"); Ok(()) } ``` **Use cases:** - Stake reward programs - Validator reward tracking ### Instructions **Address:** `solana_program::sysvar::instructions::ID` Provides access to instructions in the current transaction. ```rust use solana_program::sysvar::instructions; pub fn validate_transaction_instructions( instructions_account: &AccountInfo, ) -> ProgramResult { // Check if current instruction is not the first let current_index = instructions::load_current_index_checked(instructions_account)?; msg!("Current instruction index: {}", current_index); // Load a specific instruction if current_index > 0 { let prev_ix = instructions::load_instruction_at_checked( (current_index - 1) as usize, instructions_account, )?; msg!("Previous instruction program: {}", prev_ix.program_id); } Ok(()) } ``` **Use cases:** - Cross-instruction validation - Ensuring instruction order - Detecting sandwich attacks --- ## Access Patterns ### Pattern 1: get() - Direct Access (Recommended) **Advantages:** - No account needed in instruction - Saves account space - Lower CU cost (~100 CU) - Cleaner code **Disadvantages:** - Not supported for all sysvars - Can't be passed to CPIs ```rust use solana_program::sysvar::Sysvar; pub fn use_sysvar_direct() -> ProgramResult { let clock = Clock::get()?; let rent = Rent::get()?; msg!("Clock: {}", clock.unix_timestamp); msg!("Rent: {}", rent.lamports_per_byte_year); Ok(()) } ``` **Supported sysvars:** - Clock - Rent - EpochSchedule - EpochRewards - Fees (deprecated) ### Pattern 2: from_account_info - Account Access **Advantages:** - Works for all sysvars - Can be validated - Can be passed to CPIs - Required for some sysvars (SlotHashes, Instructions) **Disadvantages:** - Account must be passed in instruction - Slightly higher CU cost (~300 CU) - More boilerplate ```rust use solana_program::sysvar::clock; pub fn use_sysvar_from_account( clock_account: &AccountInfo, ) -> ProgramResult { // Validate account address if clock_account.key != &clock::ID { return Err(ProgramError::InvalidArgument); } let clock = Clock::from_account_info(clock_account)?; msg!("Clock: {}", clock.unix_timestamp); Ok(()) } ``` **Required for:** - SlotHashes - StakeHistory - Instructions - Any sysvar passed to CPI ### Pattern 3: Hybrid Approach **Use get() when possible, account when needed:** ```rust pub fn hybrid_sysvar_access( accounts: &[AccountInfo], need_cpi: bool, ) -> ProgramResult { if need_cpi { // Need account for CPI let account_info_iter = &mut accounts.iter(); let clock_account = next_account_info(account_info_iter)?; let clock = Clock::from_account_info(clock_account)?; // Can pass clock_account to CPI msg!("Using account access"); } else { // Direct access is cheaper let clock = Clock::get()?; msg!("Using direct access"); } Ok(()) } ``` --- ## Performance Implications ### Compute Unit Costs | Access Method | Approximate CU Cost | |--------------|---------------------| | Clock::get() | ~100 CU | | Rent::get() | ~100 CU | | EpochSchedule::get() | ~100 CU | | Clock::from_account_info() | ~300 CU | | SlotHashes::from_account_info() | ~500 CU | ### Optimization Tips **1. Use get() when possible:** ```rust // ✅ Efficient - 100 CU let clock = Clock::get()?; // ❌ Wasteful - 300 CU (unless needed for CPI) let clock = Clock::from_account_info(clock_account)?; ``` **2. Cache sysvar values:** ```rust // ❌ Wasteful - calls get() multiple times for i in 0..10 { let clock = Clock::get()?; // 100 CU × 10 = 1000 CU process_item(i, clock.unix_timestamp)?; } // ✅ Efficient - call once let clock = Clock::get()?; // 100 CU let timestamp = clock.unix_timestamp; for i in 0..10 { process_item(i, timestamp)?; } ``` **3. Avoid unnecessary sysvar access:** ```rust // ❌ Wasteful - reading sysvar in every call pub fn update_balance(account: &AccountInfo, amount: u64) -> ProgramResult { let clock = Clock::get()?; // Not needed! // ... no clock usage Ok(()) } // ✅ Efficient - only access when needed pub fn update_with_timestamp(account: &AccountInfo, amount: u64) -> ProgramResult { let clock = Clock::get()?; // Used below let timestamp = clock.unix_timestamp; // ... use timestamp Ok(()) } ``` --- ## Best Practices ### 1. Prefer get() Over from_account_info() **Unless you need the account for CPI or validation:** ```rust // ✅ Default choice let clock = Clock::get()?; // Only if needed for CPI let clock = Clock::from_account_info(clock_account)?; invoke(&ix, &[..., clock_account])?; ``` ### 2. Validate Sysvar Accounts **When accepting sysvar accounts, always validate:** ```rust pub fn validate_clock_account( clock_account: &AccountInfo, ) -> ProgramResult { // ✅ Always validate sysvar address if clock_account.key != &solana_program::sysvar::clock::ID { msg!("Invalid Clock account"); return Err(ProgramError::InvalidArgument); } Ok(()) } ``` ### 3. Use Clock for Timestamps, Not Slot Hashes **For simple time-based logic:** ```rust // ✅ Simple and efficient let clock = Clock::get()?; if clock.unix_timestamp >= unlock_time { // unlock } // ❌ Overkill - SlotHashes is for verification, not timing let slot_hashes = SlotHashes::get()?; // Complex slot-based timing logic ``` ### 4. Cache Sysvar Values **Read once, use multiple times:** ```rust pub fn process_multiple_accounts( accounts: &[AccountInfo], ) -> ProgramResult { // ✅ Read once let clock = Clock::get()?; let timestamp = clock.unix_timestamp; for account in accounts { update_account_timestamp(account, timestamp)?; } Ok(()) } ``` ### 5. Document Sysvar Dependencies **Be explicit about which sysvars your program uses:** ```rust /// Processes user staking /// /// # Sysvars /// - Clock: for stake timestamp /// - Rent: for account validation /// /// # Accounts /// - `[writable]` stake_account /// - `[signer]` user pub fn process_stake( program_id: &Pubkey, accounts: &[AccountInfo], amount: u64, ) -> ProgramResult { let clock = Clock::get()?; let rent = Rent::get()?; // ... Ok(()) } ``` ### 6. Handle Clock Drift **Don't assume unix_timestamp is perfectly accurate:** ```rust // ❌ Risky - exact timestamp match if clock.unix_timestamp == expected_time { // May never trigger } // ✅ Safe - use ranges if clock.unix_timestamp >= expected_time { // Reliable } // ✅ Best - add tolerance for early/late const TOLERANCE: i64 = 60; // 60 seconds if clock.unix_timestamp >= expected_time - TOLERANCE { // Handles clock drift } ``` --- ## Summary **Key Takeaways:** 1. **Use get() when possible** for lower CU costs and simpler code 2. **Use from_account_info()** when passing to CPIs or for sysvars without get() 3. **Always validate** sysvar account addresses when accepting them 4. **Cache sysvar values** to avoid redundant reads 5. **Understand timing limitations** - unix_timestamp is approximate **Most Common Sysvars:** | Sysvar | Primary Use | Access Method | |--------|------------|---------------| | **Clock** | Timestamps, epochs, slots | `Clock::get()` | | **Rent** | Rent exemption calculations | `Rent::get()` | | **EpochSchedule** | Epoch/slot calculations | `EpochSchedule::get()` | | **SlotHashes** | Recent slot verification | `from_account_info()` only | | **Instructions** | Transaction introspection | `from_account_info()` only | **Common Patterns:** ```rust // Timestamp current event let clock = Clock::get()?; event.created_at = clock.unix_timestamp; // Validate rent exemption let rent = Rent::get()?; if !rent.is_exempt(account.lamports(), account.data_len()) { return Err(ProgramError::AccountNotRentExempt); } // Calculate rent for new account let rent = Rent::get()?; let min_balance = rent.minimum_balance(space); ``` Sysvars provide essential cluster state to your programs. Master their access patterns for efficient, production-ready Solana development.