From e69b04604e17578c183b0d9aa7c51646da0112b8 Mon Sep 17 00:00:00 2001 From: hanako mumei <81144685+2501babe@users.noreply.github.com> Date: Fri, 16 Aug 2024 00:09:32 -0700 Subject: [PATCH] make tests more modular and stuff (squashed for rebase) --- Cargo.lock | 1 + svm/Cargo.toml | 1 + svm/tests/integration_test.rs | 775 ++++++++++++++++++++++------------ svm/tests/mock_bank.rs | 28 +- 4 files changed, 531 insertions(+), 274 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 27430a5e552747..f983e8c47c3585 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7713,6 +7713,7 @@ dependencies = [ "solana-timings", "solana-type-overrides", "solana-vote", + "test-case", "thiserror", ] diff --git a/svm/Cargo.toml b/svm/Cargo.toml index 603f0c8ae8a1d5..c98ab2f16371f2 100644 --- a/svm/Cargo.toml +++ b/svm/Cargo.toml @@ -53,6 +53,7 @@ solana-sdk = { workspace = true, features = ["dev-context-only-utils"] } # See order-crates-for-publishing.py for using this unusual `path = "."` solana-svm = { path = ".", features = ["dev-context-only-utils"] } solana-svm-conformance = { workspace = true } +test-case = { workspace = true } [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] diff --git a/svm/tests/integration_test.rs b/svm/tests/integration_test.rs index f35f023e54ae2a..1311459d47c354 100644 --- a/svm/tests/integration_test.rs +++ b/svm/tests/integration_test.rs @@ -1,27 +1,25 @@ #![cfg(test)] use { - crate::{ - mock_bank::{ - create_executable_environment, deploy_program, register_builtins, MockBankCallback, - MockForkGraph, - }, - transaction_builder::SanitizedTransactionBuilder, + crate::mock_bank::{ + create_executable_environment, deploy_program, program_address, register_builtins, + MockBankCallback, MockForkGraph, WALLCLOCK_TIME, }, solana_sdk::{ - account::{AccountSharedData, ReadableAccount, WritableAccount}, - clock::Clock, + account::{AccountSharedData, WritableAccount}, + clock::Slot, hash::Hash, - instruction::AccountMeta, + instruction::{AccountMeta, Instruction}, + native_token::LAMPORTS_PER_SOL, pubkey::Pubkey, - signature::Signature, - sysvar::SysvarId, - transaction::{SanitizedTransaction, TransactionError}, + signature::Signer, + signer::keypair::Keypair, + system_program, system_transaction, + transaction::{SanitizedTransaction, Transaction, TransactionError}, + transaction_context::TransactionReturnData, }, solana_svm::{ account_loader::{CheckedTransactionDetails, TransactionCheckResult}, - transaction_processing_callback::TransactionProcessingCallback, - transaction_processing_result::TransactionProcessingResultExtensions, transaction_processor::{ ExecutionRecordingConfig, TransactionBatchProcessor, TransactionProcessingConfig, TransactionProcessingEnvironment, @@ -29,213 +27,473 @@ use { }, solana_type_overrides::sync::{Arc, RwLock}, std::collections::{HashMap, HashSet}, + test_case::test_case, }; // This module contains the implementation of TransactionProcessingCallback mod mock_bank; -mod transaction_builder; const DEPLOYMENT_SLOT: u64 = 0; const EXECUTION_SLOT: u64 = 5; // The execution slot must be greater than the deployment slot const EXECUTION_EPOCH: u64 = 2; // The execution epoch must be greater than the deployment epoch +const LAMPORTS_PER_SIGNATURE: u64 = 5000; -fn prepare_transactions( - mock_bank: &MockBankCallback, -) -> (Vec, Vec) { - let mut transaction_builder = SanitizedTransactionBuilder::default(); - let mut all_transactions = Vec::new(); - let mut transaction_checks = Vec::new(); - - // A transaction that works without any account - let hello_program = deploy_program("hello-solana".to_string(), DEPLOYMENT_SLOT, mock_bank); - let fee_payer = Pubkey::new_unique(); - transaction_builder.create_instruction(hello_program, Vec::new(), HashMap::new(), Vec::new()); - - let sanitized_transaction = - transaction_builder.build(Hash::default(), (fee_payer, Signature::new_unique()), false); - - all_transactions.push(sanitized_transaction.unwrap()); - transaction_checks.push(Ok(CheckedTransactionDetails { - nonce: None, - lamports_per_signature: 20, - })); - - // The transaction fee payer must have enough funds - let mut account_data = AccountSharedData::default(); - account_data.set_lamports(80000); - mock_bank - .account_shared_data - .write() - .unwrap() - .insert(fee_payer, account_data); - - // A simple funds transfer between accounts - let transfer_program_account = - deploy_program("simple-transfer".to_string(), DEPLOYMENT_SLOT, mock_bank); - let sender = Pubkey::new_unique(); - let recipient = Pubkey::new_unique(); - let fee_payer = Pubkey::new_unique(); - let system_account = Pubkey::from([0u8; 32]); - - transaction_builder.create_instruction( - transfer_program_account, - vec![ - AccountMeta { - pubkey: sender, - is_signer: true, - is_writable: true, - }, - AccountMeta { - pubkey: recipient, - is_signer: false, - is_writable: true, - }, - AccountMeta { - pubkey: system_account, - is_signer: false, - is_writable: false, - }, - ], - HashMap::from([(sender, Signature::new_unique())]), - vec![0, 0, 0, 0, 0, 0, 0, 10], - ); +pub type AccountMap = HashMap; - let sanitized_transaction = - transaction_builder.build(Hash::default(), (fee_payer, Signature::new_unique()), true); - all_transactions.push(sanitized_transaction.unwrap()); - transaction_checks.push(Ok(CheckedTransactionDetails { - nonce: None, - lamports_per_signature: 20, - })); - - // Setting up the accounts for the transfer - - // fee payer - let mut account_data = AccountSharedData::default(); - account_data.set_lamports(80000); - mock_bank - .account_shared_data - .write() - .unwrap() - .insert(fee_payer, account_data); - - // sender - let mut account_data = AccountSharedData::default(); - account_data.set_lamports(900000); - mock_bank - .account_shared_data - .write() - .unwrap() - .insert(sender, account_data); - - // recipient - let mut account_data = AccountSharedData::default(); - account_data.set_lamports(900000); - mock_bank - .account_shared_data - .write() - .unwrap() - .insert(recipient, account_data); - - // The system account is set in `create_executable_environment` - - // A program that utilizes a Sysvar - let program_account = deploy_program("clock-sysvar".to_string(), DEPLOYMENT_SLOT, mock_bank); - let fee_payer = Pubkey::new_unique(); - transaction_builder.create_instruction(program_account, Vec::new(), HashMap::new(), Vec::new()); - - let sanitized_transaction = - transaction_builder.build(Hash::default(), (fee_payer, Signature::new_unique()), false); - - all_transactions.push(sanitized_transaction.unwrap()); - transaction_checks.push(Ok(CheckedTransactionDetails { - nonce: None, - lamports_per_signature: 20, - })); - - let mut account_data = AccountSharedData::default(); - account_data.set_lamports(80000); - mock_bank - .account_shared_data - .write() - .unwrap() - .insert(fee_payer, account_data); - - // A transaction that fails - let sender = Pubkey::new_unique(); - let recipient = Pubkey::new_unique(); - let fee_payer = Pubkey::new_unique(); - let system_account = Pubkey::new_from_array([0; 32]); - let data = 900050u64.to_be_bytes().to_vec(); - transaction_builder.create_instruction( - transfer_program_account, - vec![ - AccountMeta { - pubkey: sender, - is_signer: true, - is_writable: true, - }, - AccountMeta { - pubkey: recipient, - is_signer: false, - is_writable: true, - }, - AccountMeta { - pubkey: system_account, - is_signer: false, - is_writable: false, - }, - ], - HashMap::from([(sender, Signature::new_unique())]), - data, - ); +// container for a transaction batch and all data needed to run and verify it against svm +#[derive(Debug, Default)] +pub struct TestData { + // programs to deploy to the new svm before transaction execution + pub initial_programs: Vec<(String, Slot)>, + + // accounts to deploy to the new svm before transaction execution + pub initial_accounts: AccountMap, + + // transactions to execute and transaction-specific checks to perform on the results from svm + pub transaction_batch: Vec, + + // expected final account states, checked after transaction execution + pub final_accounts: AccountMap, +} + +impl TestData { + // add a new a rent-exempt account that exists before the batch + // inserts it into both account maps, assuming it lives unchanged (except for svm fixing rent epoch) + // rent-paying accounts must be added by hand because svm will not set rent epoch to u64::MAX + pub fn add_initial_account(&mut self, pubkey: Pubkey, account: &AccountSharedData) { + assert!(self + .initial_accounts + .insert(pubkey, account.clone()) + .is_none()); + + self.create_account(pubkey, account); + } + + // add a new rent-exempt account that is created by the transaction + // inserts it only into the post account map + pub fn create_account(&mut self, pubkey: Pubkey, account: &AccountSharedData) { + let mut account = account.clone(); + account.set_rent_epoch(u64::MAX); + + assert!(self.final_accounts.insert(pubkey, account).is_none()); + } + + // edit an existing account to reflect changes you expect the transaction to make to it + pub fn update_account(&mut self, pubkey: Pubkey, account: &AccountSharedData) { + let mut account = account.clone(); + account.set_rent_epoch(u64::MAX); + + assert!(self.final_accounts.insert(pubkey, account).is_some()); + } + + // add lamports to an existing expected final account state + pub fn add_lamports(&mut self, pubkey: &Pubkey, lamports: u64) { + self.final_accounts + .get_mut(pubkey) + .unwrap() + .checked_add_lamports(lamports) + .unwrap(); + } + + // subtract lamports from an existing expected final account state + pub fn sub_lamports(&mut self, pubkey: &Pubkey, lamports: u64) { + self.final_accounts + .get_mut(pubkey) + .unwrap() + .checked_sub_lamports(lamports) + .unwrap(); + } + + // convenience function that adds a transaction that is expected to succeed + pub fn push_transaction(&mut self, transaction: Transaction) { + self.transaction_batch.push(TransactionBatchItem { + transaction, + ..TransactionBatchItem::default() + }); + } + + // convenience function that adds a transaction that is expected to execute but fail + pub fn push_failed_transaction(&mut self, transaction: Transaction) { + self.transaction_batch.push(TransactionBatchItem { + transaction, + asserts: TransactionBatchItemAsserts::failed(), + ..TransactionBatchItem::default() + }); + } + + // internal helper to gather SanitizedTransaction objects for execution + fn prepare_transactions(&self) -> (Vec, Vec) { + self.transaction_batch + .iter() + .cloned() + .map(|item| { + ( + SanitizedTransaction::from_transaction_for_tests(item.transaction), + item.check_result, + ) + }) + .unzip() + } + + // internal helper to gather test items for post-execution checks + fn asserts(&self) -> Vec { + self.transaction_batch + .iter() + .cloned() + .map(|item| item.asserts) + .collect() + } +} - let sanitized_transaction = - transaction_builder.build(Hash::default(), (fee_payer, Signature::new_unique()), true); - all_transactions.push(sanitized_transaction.clone().unwrap()); - transaction_checks.push(Ok(CheckedTransactionDetails { - nonce: None, - lamports_per_signature: 20, - })); - - // fee payer - let mut account_data = AccountSharedData::default(); - account_data.set_lamports(80000); - mock_bank - .account_shared_data - .write() - .unwrap() - .insert(fee_payer, account_data); - - // Sender without enough funds - let mut account_data = AccountSharedData::default(); - account_data.set_lamports(900000); - mock_bank - .account_shared_data - .write() - .unwrap() - .insert(sender, account_data); - - // recipient - let mut account_data = AccountSharedData::default(); - account_data.set_lamports(900000); - mock_bank - .account_shared_data - .write() - .unwrap() - .insert(recipient, account_data); - - // A transaction whose verification has already failed - all_transactions.push(sanitized_transaction.unwrap()); - transaction_checks.push(Err(TransactionError::BlockhashNotFound)); - - (all_transactions, transaction_checks) +// one transaction in a batch plus check results for svm and asserts for tests +#[derive(Clone, Debug)] +pub struct TransactionBatchItem { + pub transaction: Transaction, + pub check_result: TransactionCheckResult, + pub asserts: TransactionBatchItemAsserts, } -#[test] -fn svm_integration() { - let mock_bank = MockBankCallback::default(); - let (transactions, check_results) = prepare_transactions(&mock_bank); +impl Default for TransactionBatchItem { + fn default() -> Self { + Self { + transaction: Transaction::default(), + check_result: Ok(CheckedTransactionDetails { + nonce: None, + lamports_per_signature: 5000, + }), + asserts: TransactionBatchItemAsserts::succeeded(), + } + } +} + +// asserts for a given transaction in a batch +// we can automatically check whether it executed, whether it succeeded +// log items we expect to see (exect match only), and rodata +// rodata is a doubly-nested Option. None means no check is performed, Some(None) means we assert no rodata +#[derive(Clone, Debug)] +pub struct TransactionBatchItemAsserts { + pub executed: bool, + pub succeeded: bool, + pub logs: Vec, + pub return_data: Option>, +} + +impl TransactionBatchItemAsserts { + pub fn succeeded() -> Self { + Self { + executed: true, + succeeded: true, + logs: vec![], + return_data: None, + } + } + + pub fn failed() -> Self { + Self { + executed: true, + succeeded: false, + logs: vec![], + return_data: None, + } + } + + pub fn not_executed() -> Self { + Self { + executed: false, + succeeded: false, + logs: vec![], + return_data: None, + } + } +} + +fn program_medley() -> TestData { + let mut test_data = TestData::default(); + + // 0: A transaction that works without any account + { + let program_name = "hello-solana".to_string(); + let program_id = program_address(&program_name); + test_data + .initial_programs + .push((program_name, DEPLOYMENT_SLOT)); + + let fee_payer_keypair = Keypair::new(); + let fee_payer = fee_payer_keypair.pubkey(); + + let mut fee_payer_data = AccountSharedData::default(); + fee_payer_data.set_lamports(LAMPORTS_PER_SOL); + test_data.add_initial_account(fee_payer, &fee_payer_data); + + let instruction = Instruction::new_with_bytes(program_id, &[], vec![]); + test_data.push_transaction(Transaction::new_signed_with_payer( + &[instruction], + Some(&fee_payer), + &[&fee_payer_keypair], + Hash::default(), + )); + + test_data.transaction_batch[0] + .asserts + .logs + .push("Program log: Hello, Solana!".to_string()); + + test_data.sub_lamports(&fee_payer, LAMPORTS_PER_SIGNATURE); + } + + // 1: A simple funds transfer between accounts + { + let program_name = "simple-transfer".to_string(); + let program_id = program_address(&program_name); + test_data + .initial_programs + .push((program_name, DEPLOYMENT_SLOT)); + + let fee_payer_keypair = Keypair::new(); + let sender_keypair = Keypair::new(); + + let fee_payer = fee_payer_keypair.pubkey(); + let sender = sender_keypair.pubkey(); + let recipient = Pubkey::new_unique(); + + let transfer_amount = 10; + + let mut fee_payer_data = AccountSharedData::default(); + fee_payer_data.set_lamports(LAMPORTS_PER_SOL); + test_data.add_initial_account(fee_payer, &fee_payer_data); + + let mut sender_data = AccountSharedData::default(); + sender_data.set_lamports(LAMPORTS_PER_SOL); + test_data.add_initial_account(sender, &sender_data); + + let mut recipient_data = AccountSharedData::default(); + recipient_data.set_lamports(LAMPORTS_PER_SOL); + test_data.add_initial_account(recipient, &recipient_data); + + let instruction = Instruction::new_with_bytes( + program_id, + &u64::to_be_bytes(transfer_amount), + vec![ + AccountMeta::new(sender, true), + AccountMeta::new(recipient, false), + AccountMeta::new_readonly(system_program::id(), false), + ], + ); + + test_data.push_transaction(Transaction::new_signed_with_payer( + &[instruction], + Some(&fee_payer), + &[&fee_payer_keypair, &sender_keypair], + Hash::default(), + )); + + test_data.add_lamports(&recipient, transfer_amount); + test_data.sub_lamports(&sender, transfer_amount); + test_data.sub_lamports(&fee_payer, LAMPORTS_PER_SIGNATURE * 2); + } + + // 2: A program that utilizes a Sysvar + { + let program_name = "clock-sysvar".to_string(); + let program_id = program_address(&program_name); + test_data + .initial_programs + .push((program_name, DEPLOYMENT_SLOT)); + + let fee_payer_keypair = Keypair::new(); + let fee_payer = fee_payer_keypair.pubkey(); + + let mut fee_payer_data = AccountSharedData::default(); + fee_payer_data.set_lamports(LAMPORTS_PER_SOL); + test_data.add_initial_account(fee_payer, &fee_payer_data); + + let instruction = Instruction::new_with_bytes(program_id, &[], vec![]); + test_data.push_transaction(Transaction::new_signed_with_payer( + &[instruction], + Some(&fee_payer), + &[&fee_payer_keypair], + Hash::default(), + )); + + let ro_data = TransactionReturnData { + program_id, + data: i64::to_be_bytes(WALLCLOCK_TIME).to_vec(), + }; + test_data.transaction_batch[2].asserts.return_data = Some(Some(ro_data)); + + test_data.sub_lamports(&fee_payer, LAMPORTS_PER_SIGNATURE); + } + + // 3: A transaction that fails + { + let program_id = program_address("simple-transfer"); + + let fee_payer_keypair = Keypair::new(); + let sender_keypair = Keypair::new(); + + let fee_payer = fee_payer_keypair.pubkey(); + let sender = sender_keypair.pubkey(); + let recipient = Pubkey::new_unique(); + + let base_amount = 900_000; + let transfer_amount = base_amount + 50; + + let mut fee_payer_data = AccountSharedData::default(); + fee_payer_data.set_lamports(LAMPORTS_PER_SOL); + test_data.add_initial_account(fee_payer, &fee_payer_data); + + let mut sender_data = AccountSharedData::default(); + sender_data.set_lamports(base_amount); + test_data.add_initial_account(sender, &sender_data); + + let mut recipient_data = AccountSharedData::default(); + recipient_data.set_lamports(base_amount); + test_data.add_initial_account(recipient, &recipient_data); + + let instruction = Instruction::new_with_bytes( + program_id, + &u64::to_be_bytes(transfer_amount), + vec![ + AccountMeta::new(sender, true), + AccountMeta::new(recipient, false), + AccountMeta::new_readonly(system_program::id(), false), + ], + ); + + test_data.push_failed_transaction(Transaction::new_signed_with_payer( + &[instruction], + Some(&fee_payer), + &[&fee_payer_keypair, &sender_keypair], + Hash::default(), + )); + + test_data.transaction_batch[3] + .asserts + .logs + .push("Transfer: insufficient lamports 900000, need 900050".to_string()); + + test_data.sub_lamports(&fee_payer, LAMPORTS_PER_SIGNATURE * 2); + } + + // 4: A transaction whose verification has already failed + { + let fee_payer_keypair = Keypair::new(); + let fee_payer = fee_payer_keypair.pubkey(); + + test_data.transaction_batch.push(TransactionBatchItem { + transaction: Transaction::new_signed_with_payer( + &[], + Some(&fee_payer), + &[&fee_payer_keypair], + Hash::default(), + ), + check_result: Err(TransactionError::BlockhashNotFound), + asserts: TransactionBatchItemAsserts::not_executed(), + }); + } + + test_data +} + +fn simple_transfer() -> TestData { + let mut test_data = TestData::default(); + let transfer_amount = LAMPORTS_PER_SOL; + + // 0: a transfer that succeeds + { + let source_keypair = Keypair::new(); + let source = source_keypair.pubkey(); + let destination = Pubkey::new_unique(); + + let mut source_data = AccountSharedData::default(); + let mut destination_data = AccountSharedData::default(); + + source_data.set_lamports(LAMPORTS_PER_SOL * 10); + test_data.add_initial_account(source, &source_data); + + test_data.push_transaction(system_transaction::transfer( + &source_keypair, + &destination, + transfer_amount, + Hash::default(), + )); + + destination_data + .checked_add_lamports(transfer_amount) + .unwrap(); + test_data.create_account(destination, &destination_data); + + test_data.sub_lamports(&source, transfer_amount + LAMPORTS_PER_SIGNATURE); + } + + // 1: an executable transfer that fails + { + let source_keypair = Keypair::new(); + let source = source_keypair.pubkey(); + + let mut source_data = AccountSharedData::default(); + + source_data.set_lamports(transfer_amount - 1); + test_data.add_initial_account(source, &source_data); + + test_data.push_failed_transaction(system_transaction::transfer( + &source_keypair, + &Pubkey::new_unique(), + transfer_amount, + Hash::default(), + )); + + test_data.sub_lamports(&source, LAMPORTS_PER_SIGNATURE); + } + + // 2: a non-executable transfer that fails before loading + { + test_data.transaction_batch.push(TransactionBatchItem { + transaction: system_transaction::transfer( + &Keypair::new(), + &Pubkey::new_unique(), + transfer_amount, + Hash::default(), + ), + check_result: Err(TransactionError::BlockhashNotFound), + asserts: TransactionBatchItemAsserts::not_executed(), + }); + } + + // 3: a non-executable transfer that fails during loading + { + test_data.transaction_batch.push(TransactionBatchItem { + transaction: system_transaction::transfer( + &Keypair::new(), + &Pubkey::new_unique(), + transfer_amount, + Hash::default(), + ), + asserts: TransactionBatchItemAsserts::not_executed(), + ..TransactionBatchItem::default() + }); + } + + test_data +} + +#[test_case(program_medley())] +#[test_case(simple_transfer())] +fn svm_integration(test_data: TestData) { + let mut mock_bank = MockBankCallback::default(); + + for (name, slot) in &test_data.initial_programs { + deploy_program(name.to_string(), *slot, &mut mock_bank); + } + + for (pubkey, account) in &test_data.initial_accounts { + mock_bank + .account_shared_data + .write() + .unwrap() + .insert(*pubkey, account.clone()); + } + let batch_processor = TransactionBatchProcessor::::new( EXECUTION_SLOT, EXECUTION_EPOCH, @@ -263,7 +521,9 @@ fn svm_integration() { ..Default::default() }; - let result = batch_processor.load_and_execute_sanitized_transactions( + // execute transaction batch + let (transactions, check_results) = test_data.prepare_transactions(); + let batch_output = batch_processor.load_and_execute_sanitized_transactions( &mock_bank, &transactions, check_results, @@ -271,60 +531,47 @@ fn svm_integration() { &processing_config, ); - assert_eq!(result.processing_results.len(), 5); - - let executed_tx_0 = result.processing_results[0] - .processed_transaction() - .unwrap(); - assert!(executed_tx_0.was_successful()); - let logs = executed_tx_0 - .execution_details - .log_messages - .as_ref() - .unwrap(); - assert!(logs.contains(&"Program log: Hello, Solana!".to_string())); - - let executed_tx_1 = result.processing_results[1] - .processed_transaction() - .unwrap(); - assert!(executed_tx_1.was_successful()); - - // The SVM does not commit the account changes in MockBank - let recipient_key = transactions[1].message().account_keys()[2]; - let recipient_data = executed_tx_1 - .loaded_transaction - .accounts + // build a hashmap of final account states incrementally + // NOTE with SIMD-83 an account may appear multiple times, thus we need the final state + let mut final_accounts_actual = AccountMap::default(); + for (pubkey, account_data) in batch_output + .processing_results + .iter() + .filter(|r| r.is_ok()) + .flat_map(|r| r.as_ref().unwrap().loaded_transaction.accounts.clone()) + { + final_accounts_actual.insert(pubkey, account_data); + } + + // check that all the account states we care about are present and correct + for (pubkey, expected_account_data) in test_data.final_accounts.iter() { + let actual_account_data = final_accounts_actual.get(pubkey); + assert_eq!(Some(expected_account_data), actual_account_data); + } + + // now run our transaction-by-transaction checks + for (result, test_item) in batch_output + .processing_results .iter() - .find(|key| key.0 == recipient_key) - .unwrap(); - assert_eq!(recipient_data.1.lamports(), 900010); - - let executed_tx_2 = result.processing_results[2] - .processed_transaction() - .unwrap(); - let return_data = executed_tx_2 - .execution_details - .return_data - .as_ref() - .unwrap(); - let time = i64::from_be_bytes(return_data.data[0..8].try_into().unwrap()); - let clock_data = mock_bank.get_account_shared_data(&Clock::id()).unwrap(); - let clock_info: Clock = bincode::deserialize(clock_data.data()).unwrap(); - assert_eq!(clock_info.unix_timestamp, time); - - let executed_tx_3 = result.processing_results[3] - .processed_transaction() - .unwrap(); - assert!(executed_tx_3.execution_details.status.is_err()); - assert!(executed_tx_3 - .execution_details - .log_messages - .as_ref() - .unwrap() - .contains(&"Transfer: insufficient lamports 900000, need 900050".to_string())); - - assert!(matches!( - result.processing_results[4], - Err(TransactionError::BlockhashNotFound) - )); + .zip(test_data.asserts()) + { + match result { + Ok(tx) => { + assert!(test_item.executed); + assert_eq!(test_item.succeeded, tx.execution_details.status.is_ok()); + + if !test_item.logs.is_empty() { + let actual_logs = tx.execution_details.log_messages.clone().unwrap(); + for expected_log in test_item.logs { + assert!(actual_logs.contains(&expected_log)); + } + } + + if let Some(expected_ro_data) = test_item.return_data { + assert_eq!(expected_ro_data, tx.execution_details.return_data); + } + } + Err(_) => assert!(!test_item.executed), + } + } } diff --git a/svm/tests/mock_bank.rs b/svm/tests/mock_bank.rs index 169ac63cf8854b..0db0299830317a 100644 --- a/svm/tests/mock_bank.rs +++ b/svm/tests/mock_bank.rs @@ -35,10 +35,11 @@ use { env, fs::{self, File}, io::Read, - time::{SystemTime, UNIX_EPOCH}, }, }; +pub const WALLCLOCK_TIME: i64 = 946684800; // Arbitrarily Jan 1, 2000 + pub struct MockForkGraph {} impl ForkGraph for MockForkGraph { @@ -116,9 +117,19 @@ fn load_program(name: String) -> Vec { } #[allow(unused)] -pub fn deploy_program(name: String, deployment_slot: Slot, mock_bank: &MockBankCallback) -> Pubkey { - let program_account = Pubkey::new_unique(); - let program_data_account = Pubkey::new_unique(); +pub fn program_address(program_name: &str) -> Pubkey { + Pubkey::create_with_seed(&Pubkey::default(), program_name, &Pubkey::default()).unwrap() +} + +#[allow(unused)] +pub fn deploy_program( + name: String, + deployment_slot: Slot, + mock_bank: &mut MockBankCallback, +) -> Pubkey { + let program_account = program_address(&name); + let program_data_account = bpf_loader_upgradeable::get_program_data_address(&program_account); + let state = UpgradeableLoaderState::Program { programdata_address: program_data_account, }; @@ -128,6 +139,7 @@ pub fn deploy_program(name: String, deployment_slot: Slot, mock_bank: &MockBankC account_data.set_data(bincode::serialize(&state).unwrap()); account_data.set_lamports(25); account_data.set_owner(bpf_loader_upgradeable::id()); + account_data.set_executable(true); mock_bank .account_shared_data .write() @@ -181,16 +193,12 @@ pub fn create_executable_environment( program_cache.fork_graph = Some(Arc::downgrade(&fork_graph)); // We must fill in the sysvar cache entries - let time_now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("Time went backwards") - .as_secs() as i64; let clock = Clock { slot: DEPLOYMENT_SLOT, - epoch_start_timestamp: time_now.saturating_sub(10) as UnixTimestamp, + epoch_start_timestamp: WALLCLOCK_TIME.saturating_sub(10) as UnixTimestamp, epoch: DEPLOYMENT_EPOCH, leader_schedule_epoch: DEPLOYMENT_EPOCH, - unix_timestamp: time_now as UnixTimestamp, + unix_timestamp: WALLCLOCK_TIME as UnixTimestamp, }; let mut account_data = AccountSharedData::default();