Skip to content

Commit

Permalink
Sysvar API v2 (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
buffalojoec authored Jun 25, 2024
1 parent f45e9ca commit e64c3cf
Show file tree
Hide file tree
Showing 2 changed files with 178 additions and 84 deletions.
60 changes: 31 additions & 29 deletions harness/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ pub mod result;
pub mod sysvar;

use {
crate::result::{Check, InstructionResult},
crate::{
result::{Check, InstructionResult},
sysvar::MolluskSysvars,
},
solana_program_runtime::{
compute_budget::ComputeBudget, invoke_context::InvokeContext,
loaded_programs::LoadedProgramsForTxBatch, sysvar_cache::SysvarCache,
Expand Down Expand Up @@ -70,7 +73,7 @@ pub struct Mollusk {
pub program_account: AccountSharedData,
pub program_cache: LoadedProgramsForTxBatch,
pub program_id: Pubkey,
pub sysvar_cache: SysvarCache,
pub sysvars: MolluskSysvars,
}

impl Default for Mollusk {
Expand All @@ -88,7 +91,7 @@ impl Default for Mollusk {
program_account,
program_cache: program::default_program_cache(),
program_id,
sysvar_cache: sysvar::default_sysvar_cache(),
sysvars: MolluskSysvars::default(),
}
}
}
Expand Down Expand Up @@ -133,46 +136,44 @@ impl Mollusk {
);
}

/// Get the current clock.
pub fn get_clock(&self) -> Arc<Clock> {
self.sysvar_cache.get_clock().unwrap_or_default()
/// Get the `Clock` sysvar.
pub fn get_clock(&self) -> &Clock {
&self.sysvars.clock
}

/// Get the current epoch rewards.
pub fn get_epoch_rewards(&self) -> Arc<EpochRewards> {
self.sysvar_cache.get_epoch_rewards().unwrap_or_default()
/// Get the `EpochRewards` sysvar.
pub fn get_epoch_rewards(&self) -> &EpochRewards {
&self.sysvars.epoch_rewards
}

/// Get the current epoch schedule.
pub fn get_epoch_schedule(&self) -> Arc<EpochSchedule> {
self.sysvar_cache.get_epoch_schedule().unwrap_or_default()
/// Get the `EpochSchedule` sysvar.
pub fn get_epoch_schedule(&self) -> &EpochSchedule {
&self.sysvars.epoch_schedule
}

/// Get the current last restart slot.
pub fn get_last_restart_slot(&self) -> Arc<LastRestartSlot> {
self.sysvar_cache
.get_last_restart_slot()
.unwrap_or_default()
/// Get the `LastRestartSlot` sysvar.
pub fn get_last_restart_slot(&self) -> &LastRestartSlot {
&self.sysvars.last_restart_slot
}

/// Get the current rent.
pub fn get_rent(&self) -> Arc<Rent> {
self.sysvar_cache.get_rent().unwrap_or_default()
/// Get the `Rent` sysvar.
pub fn get_rent(&self) -> &Rent {
&self.sysvars.rent
}

/// Get the current slot hashes.
pub fn get_slot_hashes(&self) -> Arc<SlotHashes> {
self.sysvar_cache.get_slot_hashes().unwrap_or_default()
/// Get the `SlotHashes` sysvar.
pub fn get_slot_hashes(&self) -> &SlotHashes {
&self.sysvars.slot_hashes
}

/// Get the current stake history.
pub fn get_stake_history(&self) -> Arc<StakeHistory> {
self.sysvar_cache.get_stake_history().unwrap_or_default()
/// Get the `StakeHistory` sysvar.
pub fn get_stake_history(&self) -> &StakeHistory {
&self.sysvars.stake_history
}

/// Warp to a slot by updating the `Clock` and `SlotHashes` sysvars.
/// Warp the test environment to a slot by updating sysvars.
pub fn warp_to_slot(&mut self, slot: u64) {
sysvar::warp_sysvar_cache_to_slot(&mut self.sysvar_cache, slot);
self.sysvars.warp_to_slot(slot)
}

/// The main Mollusk API method.
Expand Down Expand Up @@ -215,10 +216,11 @@ impl Mollusk {

let invoke_result = {
let mut programs_modified_by_tx = LoadedProgramsForTxBatch::default();
let sysvar_cache = SysvarCache::from(&self.sysvars);

let mut invoke_context = InvokeContext::new(
&mut transaction_context,
&self.sysvar_cache,
&sysvar_cache,
None,
self.compute_budget,
&self.program_cache,
Expand Down
202 changes: 147 additions & 55 deletions harness/src/sysvar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,65 +14,157 @@ use {
},
};

pub(crate) fn warp_sysvar_cache_to_slot(sysvar_cache: &mut SysvarCache, slot: Slot) {
// First update `Clock`.
let epoch_schedule = sysvar_cache.get_epoch_schedule().unwrap_or_default();
let epoch = epoch_schedule.get_epoch(slot);
let leader_schedule_epoch = epoch_schedule.get_leader_schedule_epoch(slot);
let new_clock = Clock {
slot,
epoch,
leader_schedule_epoch,
..Default::default()
};
// Agave's sysvar cache is difficult to work with, so Mollusk offers a wrapper
// around it for modifying its contents.
/// Mollusk sysvars.
#[derive(Default)]
pub struct MolluskSysvars {
pub clock: Clock,
pub epoch_rewards: EpochRewards,
pub epoch_schedule: EpochSchedule,
pub last_restart_slot: LastRestartSlot,
pub rent: Rent,
pub slot_hashes: SlotHashes,
pub stake_history: StakeHistory,
}

// Then update `SlotHashes`.
let mut i = 0;
if let Some(most_recent_slot_hash) = sysvar_cache.get_slot_hashes().unwrap_or_default().first()
{
i = most_recent_slot_hash.0;
}
let mut new_slot_hashes = vec![];
for slot in i..slot + 1 {
new_slot_hashes.push((slot, Hash::default()));
}
let new_slot_hashes = SlotHashes::new(&new_slot_hashes);
impl MolluskSysvars {
/// Warp the test environment to a slot by updating sysvars.
pub fn warp_to_slot(&mut self, slot: Slot) {
// First update `Clock`.
let epoch = self.epoch_schedule.get_epoch(slot);
let leader_schedule_epoch = self.epoch_schedule.get_leader_schedule_epoch(slot);
self.clock = Clock {
slot,
epoch,
leader_schedule_epoch,
..Default::default()
};

sysvar_cache.fill_missing_entries(|pubkey, set_sysvar| {
if pubkey.eq(&Clock::id()) {
set_sysvar(&bincode::serialize(&new_clock).unwrap());
// Then update `SlotHashes`.
let mut i = 0;
if let Some(most_recent_slot_hash) = self.slot_hashes.first() {
i = most_recent_slot_hash.0;
}
if pubkey.eq(&SlotHashes::id()) {
set_sysvar(&bincode::serialize(&new_slot_hashes).unwrap());
let mut new_slot_hashes = vec![];
for slot in i..slot + 1 {
new_slot_hashes.push((slot, Hash::default()));
}
});
self.slot_hashes = SlotHashes::new(&new_slot_hashes);
}
}

/// Create a default sysvar cache instance.
pub fn default_sysvar_cache() -> SysvarCache {
let mut cache = SysvarCache::default();
cache.fill_missing_entries(|pubkey, set_sysvar| {
if pubkey.eq(&Clock::id()) {
set_sysvar(&bincode::serialize(&Clock::default()).unwrap());
}
if pubkey.eq(&EpochRewards::id()) {
set_sysvar(&bincode::serialize(&EpochRewards::default()).unwrap());
}
if pubkey.eq(&EpochSchedule::id()) {
set_sysvar(&bincode::serialize(&EpochSchedule::default()).unwrap());
}
if pubkey.eq(&LastRestartSlot::id()) {
set_sysvar(&bincode::serialize(&LastRestartSlot::default()).unwrap());
}
if pubkey.eq(&Rent::id()) {
set_sysvar(&bincode::serialize(&Rent::default()).unwrap());
}
if pubkey.eq(&SlotHashes::id()) {
set_sysvar(&bincode::serialize(&SlotHashes::default()).unwrap());
}
if pubkey.eq(&StakeHistory::id()) {
set_sysvar(&bincode::serialize(&StakeHistory::default()).unwrap());
}
});
cache
impl From<&MolluskSysvars> for SysvarCache {
fn from(mollusk_cache: &MolluskSysvars) -> Self {
let mut sysvar_cache = SysvarCache::default();
sysvar_cache.fill_missing_entries(|pubkey, set_sysvar| {
if pubkey.eq(&Clock::id()) {
set_sysvar(&bincode::serialize(&mollusk_cache.clock).unwrap());
}
if pubkey.eq(&EpochRewards::id()) {
set_sysvar(&bincode::serialize(&mollusk_cache.epoch_rewards).unwrap());
}
if pubkey.eq(&EpochSchedule::id()) {
set_sysvar(&bincode::serialize(&mollusk_cache.epoch_schedule).unwrap());
}
if pubkey.eq(&LastRestartSlot::id()) {
set_sysvar(&bincode::serialize(&mollusk_cache.last_restart_slot).unwrap());
}
if pubkey.eq(&Rent::id()) {
set_sysvar(&bincode::serialize(&mollusk_cache.rent).unwrap());
}
if pubkey.eq(&SlotHashes::id()) {
set_sysvar(&bincode::serialize(&mollusk_cache.slot_hashes).unwrap());
}
if pubkey.eq(&StakeHistory::id()) {
set_sysvar(&bincode::serialize(&mollusk_cache.stake_history).unwrap());
}
});
sysvar_cache
}
}

#[cfg(test)]
mod tests {
use {super::*, solana_sdk::stake_history::StakeHistoryEntry, std::ops::Deref};

#[test]
fn test_warp_to_slot() {
let mut sysvars = MolluskSysvars::default();
assert_eq!(sysvars.clock.slot, 0);

sysvars.warp_to_slot(200);
assert_eq!(sysvars.clock.slot, 200);

sysvars.warp_to_slot(4_000);
assert_eq!(sysvars.clock.slot, 4_000);

sysvars.warp_to_slot(800_000);
assert_eq!(sysvars.clock.slot, 800_000);
}

#[test]
fn test_to_sysvar_cache() {
let clock = Clock {
slot: 1,
epoch: 2,
leader_schedule_epoch: 3,
..Default::default()
};
let epoch_rewards = EpochRewards {
total_rewards: 4,
..Default::default()
};
let epoch_schedule = EpochSchedule {
slots_per_epoch: 5,
..Default::default()
};
let last_restart_slot = LastRestartSlot {
last_restart_slot: 6,
};
let rent = Rent {
lamports_per_byte_year: 7,
..Default::default()
};
let slot_hashes = SlotHashes::new(&[(8, Hash::default())]);
let stake_history = {
let mut stake_history = StakeHistory::default();
stake_history.add(9, StakeHistoryEntry::default());
stake_history
};

let sysvars = MolluskSysvars {
clock,
epoch_rewards,
epoch_schedule,
last_restart_slot,
rent,
slot_hashes,
stake_history,
};

let sysvar_cache: SysvarCache = (&sysvars).into();
assert_eq!(sysvar_cache.get_clock().unwrap().deref(), &sysvars.clock);
assert_eq!(
sysvar_cache.get_epoch_rewards().unwrap().deref(),
&sysvars.epoch_rewards
);
assert_eq!(
sysvar_cache.get_epoch_schedule().unwrap().deref(),
&sysvars.epoch_schedule
);
assert_eq!(
sysvar_cache.get_last_restart_slot().unwrap().deref(),
&sysvars.last_restart_slot
);
assert_eq!(sysvar_cache.get_rent().unwrap().deref(), &sysvars.rent);
assert_eq!(
sysvar_cache.get_slot_hashes().unwrap().deref(),
&sysvars.slot_hashes
);
assert_eq!(
sysvar_cache.get_stake_history().unwrap().deref(),
&sysvars.stake_history
);
}
}

0 comments on commit e64c3cf

Please sign in to comment.