diff --git a/harness/src/lib.rs b/harness/src/lib.rs index 585866e..37a1136 100644 --- a/harness/src/lib.rs +++ b/harness/src/lib.rs @@ -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, @@ -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 { @@ -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(), } } } @@ -133,46 +136,44 @@ impl Mollusk { ); } - /// Get the current clock. - pub fn get_clock(&self) -> Arc { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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. @@ -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, diff --git a/harness/src/sysvar.rs b/harness/src/sysvar.rs index e5a8648..37ef65c 100644 --- a/harness/src/sysvar.rs +++ b/harness/src/sysvar.rs @@ -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 + ); + } }