Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.lock

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

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -249,4 +249,3 @@ solana-program-runtime = { git = "https://github.com/Lightprotocol/agave", rev =
solana-bpf-loader-program = { git = "https://github.com/Lightprotocol/agave", rev = "3a9e4e0a4411df4d9961aaa7c9f190d3fa15bc21" }
# Patch solana-program-memory to use older version where is_nonoverlapping is public
solana-program-memory = { git = "https://github.com/anza-xyz/solana-sdk", rev = "1c1d667f161666f12f5a43ebef8eda9470a8c6ee" }
litesvm = { git = "https://github.com/Lightprotocol/litesvm", rev = "a04cb80b6847eb720c840a5e5d9a6f74ce630cc6" }
2 changes: 1 addition & 1 deletion sdk-libs/program-test/src/compressible.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use light_ctoken_types::{
use solana_pubkey::Pubkey;

#[cfg(feature = "devenv")]
use crate::LightProgramTest;
use crate::{litesvm_extensions::LiteSvmExtensions, LightProgramTest};

#[cfg(feature = "devenv")]
pub type CompressibleAccountStore = HashMap<Pubkey, StoredCompressibleAccount>;
Expand Down
2 changes: 2 additions & 0 deletions sdk-libs/program-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ pub mod compressible;
#[cfg(feature = "devenv")]
pub mod forester;
pub mod indexer;
pub mod litesvm_extensions;
pub mod logging;
pub mod program_test;
pub mod utils;
Expand All @@ -144,4 +145,5 @@ pub use light_client::{
indexer::{AddressWithTree, Indexer},
rpc::{Rpc, RpcError},
};
pub use litesvm_extensions::LiteSvmExtensions;
pub use program_test::{config::ProgramTestConfig, LightProgramTest};
199 changes: 199 additions & 0 deletions sdk-libs/program-test/src/litesvm_extensions.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
use litesvm::LiteSVM;
use solana_account::ReadableAccount;
use solana_sdk::{account::Account, pubkey::Pubkey};

/// Extension trait for LiteSVM to add utility methods.
///
/// This trait provides additional functionality on top of the base LiteSVM,
/// such as querying all accounts owned by a specific program.
pub trait LiteSvmExtensions {
/// Returns all accounts owned by the provided program id.
///
/// This method iterates through the internal accounts database and filters
/// accounts by their owner field.
///
/// # Arguments
///
/// * `program_id` - The program ID to filter accounts by
///
/// # Returns
///
/// A vector of tuples containing (Pubkey, Account) for all accounts owned by the program
fn get_program_accounts(&self, program_id: &Pubkey) -> Vec<(Pubkey, Account)>;
}

impl LiteSvmExtensions for LiteSVM {
fn get_program_accounts(&self, program_id: &Pubkey) -> Vec<(Pubkey, Account)> {
self.accounts_db()
.inner
.iter()
.filter(|(_, account)| account.owner() == program_id)
.map(|(pubkey, account)| (*pubkey, Account::from(account.clone())))
.collect()
}
}

#[cfg(test)]
mod tests {
use solana_sdk::{
native_token::LAMPORTS_PER_SOL,
signature::{Keypair, Signer},
system_instruction::{create_account, transfer},
system_program,
transaction::{Transaction, VersionedTransaction},
};

use super::*;

#[test]
fn test_get_program_accounts() {
let mut svm = LiteSVM::new();
let payer_keypair = Keypair::new();
let payer = payer_keypair.pubkey();

// Fund the payer
svm.airdrop(&payer, 10 * LAMPORTS_PER_SOL).unwrap();

// Establish baseline of system accounts
let baseline_system_accounts = svm.get_program_accounts(&system_program::id());
let baseline_count = baseline_system_accounts.len();

// Create multiple accounts owned by system program
let num_system_accounts = 5;
let mut system_owned_accounts = vec![];

for i in 0..num_system_accounts {
let new_account_keypair = Keypair::new();
let new_account = new_account_keypair.pubkey();
let space = 10 + i;
let rent_amount = svm.minimum_balance_for_rent_exemption(space);

let instruction = create_account(
&payer,
&new_account,
rent_amount,
space as u64,
&system_program::id(),
);

let tx = Transaction::new_signed_with_payer(
&[instruction],
Some(&payer),
&[&payer_keypair, &new_account_keypair],
svm.latest_blockhash(),
);

svm.send_transaction(VersionedTransaction::from(tx))
.unwrap();
system_owned_accounts.push((new_account, rent_amount, space));
}

// Create a custom program and some accounts owned by it
let custom_program_id = Pubkey::new_unique();
let num_custom_accounts = 3;
let mut custom_owned_accounts = vec![];

for i in 0..num_custom_accounts {
let new_account_keypair = Keypair::new();
let new_account = new_account_keypair.pubkey();
let space = 20 + i * 2;
let rent_amount = svm.minimum_balance_for_rent_exemption(space);

let instruction = create_account(
&payer,
&new_account,
rent_amount,
space as u64,
&custom_program_id,
);

let tx = Transaction::new_signed_with_payer(
&[instruction],
Some(&payer),
&[&payer_keypair, &new_account_keypair],
svm.latest_blockhash(),
);

svm.send_transaction(VersionedTransaction::from(tx))
.unwrap();
custom_owned_accounts.push((new_account, rent_amount, space));
}

// Do some transfers to create new accounts
for i in 0..3 {
let to = Pubkey::new_unique();
let amount = (i + 1) * 1000;
let instruction = transfer(&payer, &to, amount);
let tx = Transaction::new_signed_with_payer(
&[instruction],
Some(&payer),
&[&payer_keypair],
svm.latest_blockhash(),
);
svm.send_transaction(VersionedTransaction::from(tx))
.unwrap();
}

// Test get_program_accounts for system program
let system_accounts = svm.get_program_accounts(&system_program::id());

// Should contain baseline + 5 created accounts + 3 transfer recipients
let expected_count = baseline_count + num_system_accounts + 3;
assert_eq!(
system_accounts.len(),
expected_count,
"Expected {} system accounts (baseline {} + {} created + 3 transfers), got {}",
expected_count,
baseline_count,
num_system_accounts,
system_accounts.len()
);

// Verify all our created system accounts are present with correct data
for (pubkey, expected_lamports, expected_space) in &system_owned_accounts {
let found = system_accounts
.iter()
.find(|(pk, _)| pk == pubkey)
.expect("System account should be found");

assert_eq!(found.1.lamports, *expected_lamports);
assert_eq!(found.1.data.len(), *expected_space);
assert_eq!(found.1.owner, system_program::id());
}

// Verify individual get_account returns the same data
for (pubkey, _, _) in &system_owned_accounts {
let individual_account = svm.get_account(pubkey).unwrap();
let from_program_accounts =
system_accounts.iter().find(|(pk, _)| pk == pubkey).unwrap();

assert_eq!(
individual_account.lamports,
from_program_accounts.1.lamports
);
assert_eq!(individual_account.data, from_program_accounts.1.data);
assert_eq!(individual_account.owner, from_program_accounts.1.owner);
}

// Test get_program_accounts for custom program
let custom_accounts = svm.get_program_accounts(&custom_program_id);
assert_eq!(custom_accounts.len(), num_custom_accounts);

// Verify all custom accounts are present with correct data
for (pubkey, expected_lamports, expected_space) in &custom_owned_accounts {
let found = custom_accounts
.iter()
.find(|(pk, _)| pk == pubkey)
.expect("Custom account should be found");

assert_eq!(found.1.lamports, *expected_lamports);
assert_eq!(found.1.data.len(), *expected_space);
assert_eq!(found.1.owner, custom_program_id);
}

// Test get_program_accounts for non-existent program
let nonexistent_program = Pubkey::new_unique();
let no_accounts = svm.get_program_accounts(&nonexistent_program);
assert_eq!(no_accounts.len(), 0);
}
}
5 changes: 3 additions & 2 deletions sdk-libs/program-test/src/program_test/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use solana_transaction_status_client_types::TransactionStatus;

use crate::{
indexer::{TestIndexer, TestIndexerExtensions},
litesvm_extensions::LiteSvmExtensions,
program_test::LightProgramTest,
};

Expand Down Expand Up @@ -56,9 +57,9 @@ impl Rpc for LightProgramTest {

async fn get_program_accounts(
&self,
_program_id: &Pubkey,
program_id: &Pubkey,
) -> Result<Vec<(Pubkey, Account)>, RpcError> {
unimplemented!("get_program_accounts")
Ok(self.context.get_program_accounts(program_id))
}

async fn confirm_transaction(&self, _transaction: Signature) -> Result<bool, RpcError> {
Expand Down
Loading