Initial commit
This commit is contained in:
992
skills/solana-development/references/sysvars.md
Normal file
992
skills/solana-development/references/sysvars.md
Normal file
@@ -0,0 +1,992 @@
|
||||
# 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<u8>,
|
||||
}
|
||||
|
||||
pub fn create_event(
|
||||
event_account: &AccountInfo,
|
||||
data: Vec<u8>,
|
||||
) -> 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<u64, ProgramError> {
|
||||
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<u64, ProgramError> {
|
||||
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<u64, ProgramError> {
|
||||
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.
|
||||
Reference in New Issue
Block a user