Skip to content

Commit

Permalink
Add SBF tests for sol_get_sysvar (anza-xyz#3288)
Browse files Browse the repository at this point in the history
* add SBF tests for `sol_get_sysvar`

* review feedback
  • Loading branch information
buffalojoec authored Oct 25, 2024
1 parent 7d455b0 commit df5b94e
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 97 deletions.
1 change: 1 addition & 0 deletions programs/sbf/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions programs/sbf/rust/sysvar/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ license = { workspace = true }
edition = { workspace = true }

[dependencies]
bincode = { workspace = true }
solana-program = { workspace = true }

[lib]
crate-type = ["cdylib"]

[lints]
workspace = true
280 changes: 185 additions & 95 deletions programs/sbf/rust/sysvar/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,114 +10,204 @@ use solana_program::{
msg,
program_error::ProgramError,
pubkey::Pubkey,
stake_history::StakeHistoryGetEntry,
sysvar::{
self, clock::Clock, epoch_rewards::EpochRewards, epoch_schedule::EpochSchedule,
instructions, rent::Rent, slot_hashes::SlotHashes, slot_history::SlotHistory,
stake_history::StakeHistory, Sysvar,
self,
clock::Clock,
epoch_rewards::EpochRewards,
epoch_schedule::EpochSchedule,
instructions,
rent::Rent,
slot_hashes::{PodSlotHashes, SlotHashes},
slot_history::SlotHistory,
stake_history::{StakeHistory, StakeHistorySysvar},
Sysvar,
},
};

// Adapted from `solana_program::sysvar::get_sysvar` (private).
#[cfg(target_os = "solana")]
fn sol_get_sysvar_handler<T>(dst: &mut [u8], offset: u64, length: u64) -> Result<(), ProgramError>
where
T: Sysvar,
{
let sysvar_id = &T::id() as *const _ as *const u8;
let var_addr = dst as *mut _ as *mut u8;

let result =
unsafe { solana_program::syscalls::sol_get_sysvar(sysvar_id, var_addr, offset, length) };

match result {
solana_program::entrypoint::SUCCESS => Ok(()),
e => Err(e.into()),
}
}

// Double-helper arrangement is easier to write to a mutable slice.
fn sol_get_sysvar<T>() -> Result<T, ProgramError>
where
T: Sysvar,
{
#[cfg(target_os = "solana")]
{
let len = T::size_of();
let mut data = vec![0; len];

sol_get_sysvar_handler::<T>(&mut data, 0, len as u64)?;

bincode::deserialize(&data).map_err(|_| ProgramError::InvalidArgument)
}
#[cfg(not(target_os = "solana"))]
Err(ProgramError::UnsupportedSysvar)
}

solana_program::entrypoint_no_alloc!(process_instruction);
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
// Clock
{
msg!("Clock identifier:");
sysvar::clock::id().log();
let clock = Clock::from_account_info(&accounts[2]).unwrap();
assert_ne!(clock, Clock::default());
let got_clock = Clock::get()?;
assert_eq!(clock, got_clock);
}
// `sol_get_sysvar` can eat up some heap space for calls like
// `PodSlotHashes`, so break up the instructions.
//
// * 0: Fixed-size sysvars (Clock, Rent, etc.).
// * 1: Instruction sysvar.
// * 2: Stake History.
// * 3: Slot Hashes.
match instruction_data.first() {
Some(&0) => {
// Clock
{
msg!("Clock identifier:");
sysvar::clock::id().log();
let clock = Clock::from_account_info(&accounts[2]).unwrap();
assert_ne!(clock, Clock::default());
let got_clock = Clock::get()?;
assert_eq!(clock, got_clock);
// Syscall `sol_get_sysvar`.
let sgs_clock = sol_get_sysvar::<Clock>()?;
assert_eq!(clock, sgs_clock);
}

// Epoch Schedule
{
msg!("EpochSchedule identifier:");
sysvar::epoch_schedule::id().log();
let epoch_schedule = EpochSchedule::from_account_info(&accounts[3]).unwrap();
assert_eq!(epoch_schedule, EpochSchedule::default());
let got_epoch_schedule = EpochSchedule::get()?;
assert_eq!(epoch_schedule, got_epoch_schedule);
}
// Epoch Rewards
{
msg!("EpochRewards identifier:");
sysvar::epoch_rewards::id().log();
let epoch_rewards = EpochRewards::from_account_info(&accounts[10]).unwrap();
let got_epoch_rewards = EpochRewards::get()?;
assert_eq!(epoch_rewards, got_epoch_rewards);
// Syscall `sol_get_sysvar`.
let sgs_epoch_rewards = sol_get_sysvar::<EpochRewards>()?;
assert_eq!(epoch_rewards, sgs_epoch_rewards);
}

// Instructions
msg!("Instructions identifier:");
sysvar::instructions::id().log();
assert_eq!(*accounts[4].owner, sysvar::id());
let index = instructions::load_current_index_checked(&accounts[4])?;
let instruction = instructions::load_instruction_at_checked(index as usize, &accounts[4])?;
assert_eq!(0, index);
assert_eq!(
instruction,
Instruction::new_with_bytes(
*program_id,
instruction_data,
vec![
AccountMeta::new(*accounts[0].key, true),
AccountMeta::new(*accounts[1].key, false),
AccountMeta::new_readonly(*accounts[2].key, false),
AccountMeta::new_readonly(*accounts[3].key, false),
AccountMeta::new_readonly(*accounts[4].key, false),
AccountMeta::new_readonly(*accounts[5].key, false),
AccountMeta::new_readonly(*accounts[6].key, false),
AccountMeta::new_readonly(*accounts[7].key, false),
AccountMeta::new_readonly(*accounts[8].key, false),
AccountMeta::new_readonly(*accounts[9].key, false),
AccountMeta::new_readonly(*accounts[10].key, false),
],
)
);

// Recent Blockhashes
#[allow(deprecated)]
{
msg!("RecentBlockhashes identifier:");
sysvar::recent_blockhashes::id().log();
let recent_blockhashes = RecentBlockhashes::from_account_info(&accounts[5]).unwrap();
assert_ne!(recent_blockhashes, RecentBlockhashes::default());
}
// Epoch Schedule
{
msg!("EpochSchedule identifier:");
sysvar::epoch_schedule::id().log();
let epoch_schedule = EpochSchedule::from_account_info(&accounts[3]).unwrap();
assert_eq!(epoch_schedule, EpochSchedule::default());
let got_epoch_schedule = EpochSchedule::get()?;
assert_eq!(epoch_schedule, got_epoch_schedule);
// Syscall `sol_get_sysvar`.
let sgs_epoch_schedule = sol_get_sysvar::<EpochSchedule>()?;
assert_eq!(epoch_schedule, sgs_epoch_schedule);
}

// Rent
{
msg!("Rent identifier:");
sysvar::rent::id().log();
let rent = Rent::from_account_info(&accounts[6]).unwrap();
let got_rent = Rent::get()?;
assert_eq!(rent, got_rent);
}
// Recent Blockhashes
#[allow(deprecated)]
{
msg!("RecentBlockhashes identifier:");
sysvar::recent_blockhashes::id().log();
let recent_blockhashes =
RecentBlockhashes::from_account_info(&accounts[5]).unwrap();
assert_ne!(recent_blockhashes, RecentBlockhashes::default());
}

// Slot Hashes
msg!("SlotHashes identifier:");
sysvar::slot_hashes::id().log();
assert_eq!(
Err(ProgramError::UnsupportedSysvar),
SlotHashes::from_account_info(&accounts[7])
);

// Slot History
msg!("SlotHistory identifier:");
sysvar::slot_history::id().log();
assert_eq!(
Err(ProgramError::UnsupportedSysvar),
SlotHistory::from_account_info(&accounts[8])
);

// Stake History
msg!("StakeHistory identifier:");
sysvar::stake_history::id().log();
let _ = StakeHistory::from_account_info(&accounts[9]).unwrap();

// Epoch Rewards
{
msg!("EpochRewards identifier:");
sysvar::epoch_rewards::id().log();
let epoch_rewards = EpochRewards::from_account_info(&accounts[10]).unwrap();
let got_epoch_rewards = EpochRewards::get()?;
assert_eq!(epoch_rewards, got_epoch_rewards);
}
// Rent
{
msg!("Rent identifier:");
sysvar::rent::id().log();
let rent = Rent::from_account_info(&accounts[6]).unwrap();
let got_rent = Rent::get()?;
assert_eq!(rent, got_rent);
// Syscall `sol_get_sysvar`.
let sgs_rent = sol_get_sysvar::<Rent>()?;
assert_eq!(rent, sgs_rent);
}

// Slot History
// (Not fixed-size, but also not supported)
msg!("SlotHistory identifier:");
sysvar::slot_history::id().log();
assert_eq!(
Err(ProgramError::UnsupportedSysvar),
SlotHistory::from_account_info(&accounts[8])
);

Ok(())
}
Some(&1) => {
// Instructions
msg!("Instructions identifier:");
sysvar::instructions::id().log();
assert_eq!(*accounts[4].owner, sysvar::id());
let index = instructions::load_current_index_checked(&accounts[4])?;
let instruction =
instructions::load_instruction_at_checked(index as usize, &accounts[4])?;
assert_eq!(0, index);
assert_eq!(
instruction,
Instruction::new_with_bytes(
*program_id,
instruction_data,
vec![
AccountMeta::new(*accounts[0].key, true),
AccountMeta::new(*accounts[1].key, false),
AccountMeta::new_readonly(*accounts[2].key, false),
AccountMeta::new_readonly(*accounts[3].key, false),
AccountMeta::new_readonly(*accounts[4].key, false),
AccountMeta::new_readonly(*accounts[5].key, false),
AccountMeta::new_readonly(*accounts[6].key, false),
AccountMeta::new_readonly(*accounts[7].key, false),
AccountMeta::new_readonly(*accounts[8].key, false),
AccountMeta::new_readonly(*accounts[9].key, false),
AccountMeta::new_readonly(*accounts[10].key, false),
],
)
);

Ok(())
Ok(())
}
Some(&2) => {
// Stake History
{
msg!("StakeHistory identifier:");
sysvar::stake_history::id().log();
let _ = StakeHistory::from_account_info(&accounts[9]).unwrap();
// Syscall `sol_get_sysvar`.
let stake_history_sysvar = StakeHistorySysvar(1);
assert!(stake_history_sysvar.get_entry(0).is_some());
}

Ok(())
}
Some(&3) => {
// Slot Hashes
{
msg!("SlotHashes identifier:");
sysvar::slot_hashes::id().log();
assert_eq!(
Err(ProgramError::UnsupportedSysvar),
SlotHashes::from_account_info(&accounts[7])
);
// Syscall `sol_get_sysvar`.
let pod_slot_hashes = PodSlotHashes::fetch()?;
assert!(pod_slot_hashes.get(/* slot */ &0)?.is_some());
}

Ok(())
}
_ => Err(ProgramError::InvalidInstructionData),
}
}
22 changes: 20 additions & 2 deletions programs/sbf/tests/sysvar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use {
message::Message,
pubkey::Pubkey,
signature::{Keypair, Signer},
stake_history::{StakeHistory, StakeHistoryEntry},
sysvar::{
clock, epoch_rewards, epoch_schedule, instructions, recent_blockhashes, rent,
slot_hashes, slot_history, stake_history,
Expand All @@ -31,7 +32,9 @@ fn test_sysvar_syscalls() {
..
} = create_genesis_config(50);
genesis_config.accounts.remove(&disable_fees_sysvar::id());

let bank = Bank::new_for_tests(&genesis_config);

let epoch_rewards = epoch_rewards::EpochRewards {
distribution_starting_block_height: 42,
total_rewards: 100,
Expand All @@ -40,6 +43,21 @@ fn test_sysvar_syscalls() {
..epoch_rewards::EpochRewards::default()
};
bank.set_sysvar_for_tests(&epoch_rewards);

let stake_history = {
let mut stake_history = StakeHistory::default();
stake_history.add(
0,
StakeHistoryEntry {
effective: 200,
activating: 300,
deactivating: 400,
},
);
stake_history
};
bank.set_sysvar_for_tests(&stake_history);

let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
let mut bank_client = BankClient::new_shared(bank);
let authority_keypair = Keypair::new();
Expand All @@ -52,10 +70,10 @@ fn test_sysvar_syscalls() {
);
bank.freeze();

for instruction_data in &[0u8, 1u8] {
for ix_discriminator in 0..4 {
let instruction = Instruction::new_with_bincode(
program_id,
&[instruction_data],
&[ix_discriminator],
vec![
AccountMeta::new(mint_keypair.pubkey(), true),
AccountMeta::new(Pubkey::new_unique(), false),
Expand Down

0 comments on commit df5b94e

Please sign in to comment.