Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
031451b
initial wrapping of the cov logic
procdump Jun 25, 2025
80719f4
improve the error handling
procdump Jun 25, 2025
cb0c68a
clean a little bit
procdump Jun 25, 2025
03b8db6
add the gen_cov.sh for testing
procdump Jun 25, 2025
1b2182b
forgotten file....
procdump Jun 25, 2025
7af278f
cleaning up
procdump Jun 25, 2025
e772c16
Start using the sol-stubs crate
procdump Jun 25, 2025
509d0c7
integrate the loader part
procdump Jun 25, 2025
188c002
forgotten files..
procdump Jun 25, 2025
f8e1b2f
silence warnings
procdump Jun 25, 2025
3361af8
remove wrongly added dep in failure's example
procdump Jun 25, 2025
58938da
poc: modify litesvm in order to generate ts test coverage
vlady-kotsev Jun 25, 2025
b0b9d30
go on with refactoring stubs
procdump Jun 25, 2025
919071d
poc: add third party programs and sysvars sync
vlady-kotsev Jun 26, 2025
0cda978
0) Take into account if there's lite-coverage or not when syncing
procdump Jun 27, 2025
4b50f01
relocate files so that the merge with poc is less painful
procdump Jun 27, 2025
627c96b
Merge branch 'poc/coverage' of github.com:procdump/litesvm_cov into cov
procdump Jun 27, 2025
fffa97b
revert stuff done in test_programs
procdump Jun 27, 2025
0c5e662
revert stuff done in tests
procdump Jun 27, 2025
e829a5c
Merge branch 'poc/coverage_ditch_unwraps_pubs' of github.com:procdump…
procdump Jun 27, 2025
8058500
start using the github repos for sol-stubs and program-test
procdump Jun 27, 2025
1a48c51
Remove more unwraps and instead bail out gracefully.
procdump Jun 27, 2025
e18476a
feat: copy instructions sysvar account to program test
vlady-kotsev Jun 30, 2025
2edd7ea
fix: dependencies
vlady-kotsev Jun 30, 2025
4d2b3dd
feat: add setter for payer
vlady-kotsev Jun 30, 2025
54735cf
fix: redundant code, ease dependency
vlady-kotsev Jun 30, 2025
8eda786
revert back Cargo.lock - just cargo update -p sol-stubs and solana-pr…
procdump Jul 1, 2025
5939de8
Start using the solana-program-test cov branch.
procdump Jul 1, 2025
5df1851
Start using the reworks as done for solana-program-stubs
procdump Jul 1, 2025
44c6bfd
start using the latest solana-program-stubs without concrete location
procdump Jul 1, 2025
bd646ad
Report some log events respecting env var details passed by anchor.
procdump Jul 4, 2025
a2b1e33
use latest stubs with lamports updated accordingly
procdump Jul 8, 2025
8ee63c9
Silence clippy.
procdump Jul 9, 2025
c23fe55
Use latest stubs
procdump Jul 9, 2025
eb0982c
Initial commit adapting pinocchio programs. Moreover the entrypoint
procdump Jul 14, 2025
ee51ead
clean up the entrypoint stuff a little bit
procdump Jul 14, 2025
21147c6
len -> accounts_len
procdump Jul 14, 2025
3c3319b
silence clippy warnings
procdump Jul 14, 2025
a321b17
refactor the entrypoint code:
procdump Jul 14, 2025
5580a72
str::from_utf8 requires an unwrap - hence use lossy String variant
procdump Jul 14, 2025
5a725b5
pinocchio: impl sol_log_pubkey syscall to use solana-program-test's S…
procdump Jul 14, 2025
03b148b
pinocchio: sol_log_compute_units_, sol_remaining_compute_units
procdump Jul 14, 2025
8a30282
pinocchio: sol_memcmp/memset/memmove/memcpy
procdump Jul 14, 2025
0af88a5
pinocchio: sol_get_stack_height
procdump Jul 14, 2025
01788be
pinocchio: sol_get_clock_sysvar
procdump Jul 14, 2025
c7f0e78
pinocchio: sol_get_epoch_schedule_sysvar, sol_get_fees_sysvar, sol_ge…
procdump Jul 14, 2025
5926352
pinocchio: sol_get_last_restart_slot
procdump Jul 14, 2025
5b580c7
pinocchio: sol_get_epoch_stake
procdump Jul 14, 2025
753ed53
pinocchio: sol_get_sysvar
procdump Jul 14, 2025
775734f
pinocchio: sol_set_return_data
procdump Jul 14, 2025
fc80faa
pinocchio: sol_get_return_data
procdump Jul 14, 2025
d7231a7
Pinocchio apps tend to 'repurpose' i.e change value of bytes in the i…
procdump Jul 15, 2025
6bf023d
pinocchio: sol_log_data
procdump Jul 15, 2025
c4a87ba
pinocchio: sol_get_processed_sibling_instruction
procdump Jul 15, 2025
38f51c1
silence cargo clippy
procdump Jul 15, 2025
f50a6f3
pinocchio's AccountMeta actually holds a reference to Pubkey and take…
procdump Jul 15, 2025
eaa0753
pinocchio: declare our own BorrowedAccountMeta so that we mark it as …
procdump Jul 15, 2025
05186a8
Pinocchio's AccountMeta layout:
procdump Jul 15, 2025
3f72830
pinocchio: syscalls in place but let's try to clean up more
procdump Jul 16, 2025
c0eb1d9
pinocchio: syscalls clean up
procdump Jul 16, 2025
561d6b1
pinocchio: invoke_signed_c left - revise the rest of course
procdump Jul 16, 2025
32694b5
pinocchio: sol_invoke_signed, NB: after this func data len will probably
procdump Jul 16, 2025
bcd07c7
silence clippy
procdump Jul 16, 2025
9a358d9
relocate stubs to the solana-program-stubs loader macro
procdump Jul 17, 2025
aeaf0e0
latest stubs
procdump Jul 17, 2025
1ee5e6a
Trim printlns in favor of using workspace's log.
procdump Jul 17, 2025
dc58526
log
procdump Jul 17, 2025
81eeffb
Remove unneeded pub added in the early dev attempts.
procdump Jul 17, 2025
ad78b12
More cleanups as well as add comments.
procdump Jul 17, 2025
45c9f29
remove unneeded type spec
procdump Jul 17, 2025
b7664f1
Comments
procdump Jul 17, 2025
22ad117
more pubs -> pub(crate) as global awareness isn't necessary
procdump Jul 17, 2025
ec94ade
use latest solana-program-test
procdump Jul 17, 2025
370ab4c
remove newline
procdump Jul 17, 2025
8d9bf7c
Bring this typo back. It may harm our PR.
procdump Jul 17, 2025
99b3afb
Make LiteCoverage::new() a singleton. ProgramTest is pretty much
procdump Jul 18, 2025
dc21d82
Come up with the sbf_avatar_path based on the program_name passed.
procdump Jul 18, 2025
6b520ee
Dispose of the unused so_path for the sbf avatars
procdump Jul 18, 2025
a42dbbc
silence cargo clippy
procdump Jul 18, 2025
d45149e
Fix the error msg propagated to caller if unable to find SBF avatar.
procdump Jul 18, 2025
0388ade
Keep the Library handle around when loading a shared object.
procdump Jul 21, 2025
939a849
Now that the stubs api is a stubsv2 and it is renamed to just stubs
procdump Jul 22, 2025
7103948
Merge branch 'master' into cov
procdump Jul 22, 2025
838a283
Merge pull request #1 from procdump/cov
procdump Jul 22, 2025
b3b6588
Start using the published solana-program-stubs crate
procdump Jul 22, 2025
f96adf4
Start using the solana-program-test at limechain
procdump Jul 22, 2025
415f32c
Bugfix: when advancing for accounts on the new_input buffer
procdump Jul 24, 2025
e94cc5a
Silence cargo clippy
procdump Jul 24, 2025
e2afe0b
Equalize ProgramTestContext's recent blockhash with transaction's.
procdump Jul 30, 2025
82973e5
Merge pull request #2 from procdump/equalize_recent_blockhash
procdump Jul 30, 2025
d51971e
Merge pull request #3 from LimeChain/equalize_recent_blockhash
procdump Jul 30, 2025
3555065
Merge branch 'cov_2.3_2' into all_cov
procdump Aug 19, 2025
4c8394e
Adapt to 2.3. TODO: solana-program-test
procdump Aug 19, 2025
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
41 changes: 41 additions & 0 deletions Cargo.lock

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

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ solana-nonce = "2.2"
solana-nonce-account = "2.2"
solana-precompile-error = "2.2"
solana-program-error = "2.2"
solana-program-entrypoint = "2.3"
solana-program-option = "2.2"
solana-program-pack = "2.2"
solana-program-runtime = "2.3"
Expand Down Expand Up @@ -89,6 +90,8 @@ spl-token-2022 = "8.0"
test-log = "0.2"
thiserror = "2.0"
tokio = "1.35"
lite-coverage = { path = "crates/lite-coverage" }
solana-program-stubs = { version = "0.1.0", features = [ "loader_stubs" ] }

[profile.bench]
debug = true
Expand Down
31 changes: 31 additions & 0 deletions crates/lite-coverage/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[package]
name = "lite-coverage"
version = "0.1.0"
edition = "2021"
[lib]
path = "src/lib.rs"

[dependencies]
libloading = "0.8.8"
base64 = "0.22.1"
bincode = { workspace = true }
serde = { workspace = true }
jsonrpc-core = "18.0.0"
bs58 = { version = "0.5.1", default-features = false }
borsh = "1.5.7"
solana-program-runtime = { workspace = true }
solana-program-test = { workspace = true }
solana-account = { workspace = true }
solana-keypair = { workspace = true }
solana-signer = { workspace = true }
solana-transaction = { workspace = true }
solana-instruction = { workspace = true }
solana-pubkey = { workspace = true }
solana-program-error = { workspace = true }
solana-program-entrypoint = { workspace = true }
solana-sysvar = { workspace = true }
solana-sdk-ids = { workspace = true }
tokio = { workspace = true }
lazy_static = "1.5.0"
solana-program-stubs = { workspace = true }
log.workspace = true
7 changes: 7 additions & 0 deletions crates/lite-coverage/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mod lite_coverage;
mod loader;
mod sbf;
mod stubs;
mod types;
pub use lite_coverage::*;
pub use types::*;
181 changes: 181 additions & 0 deletions crates/lite-coverage/src/lite_coverage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
use std::{io::Write, rc::Rc};

use solana_keypair::Keypair;
use solana_pubkey::Pubkey;

use crate::{
loader::{entrypoint, Loader},
types::{LiteCoverageError, NativeProgram},
AdditionalProgram, ProgramTestContextHandle,
};
use {
solana_account::AccountSharedData,
solana_program_test::{processor, ProgramTest, ProgramTestContext},
solana_transaction::versioned::VersionedTransaction,
std::{cell::RefCell, sync::Arc},
tokio::runtime::Runtime,
};

/// Main object to look after code coverage.
#[derive(Clone)]
pub struct LiteCoverage {
pub programs: Vec<NativeProgram>,
pub pt_context: Rc<RefCell<Option<ProgramTestContext>>>,
rt: Arc<Runtime>,
#[allow(dead_code)]
loader: Rc<Loader>,
}

impl LiteCoverage {
/// Get a singleton instance to the main code coverage object.
pub fn new(
programs: Vec<NativeProgram>,
additional_programs: Vec<AdditionalProgram>,
payer: Keypair,
) -> LiteCoverageError<Self> {
{
use std::sync::Once;
static SINGLETON: Once = Once::new();

if !SINGLETON.is_completed() {
SINGLETON.call_once(|| {});
} else {
return Err(Box::<dyn std::error::Error + Send + Sync>::from(
"LiteCoverage is singleton, please use only one instance of LiteSVM",
));
}
}

let static_programs = Box::leak(Box::new(programs.clone()));
let mut program_test = ProgramTest::default();
program_test.prefer_bpf(false);
program_test.set_payer(payer);

for (pubkey, name) in additional_programs.clone().into_iter() {
let name = Box::leak(Box::new(name));
program_test.add_upgradeable_program_to_genesis(name, &pubkey);
}

let mut loader = Loader::new();
for (program_id, program_name) in static_programs.iter() {
log::info!(
"Adding native program (SBF avatar) '{}' with program id: {}",
program_name,
program_id
);
loader.add_program(program_name, program_id)?;
program_test.add_program(program_name, *program_id, processor!(entrypoint));
}
log::info!("Loaded: {:?}", loader);

let rt = tokio::runtime::Runtime::new()?;
let pt_context = rt.block_on(async move { program_test.start_with_context().await });
loader.adjust_stubs()?;

LiteCoverage::log_anchor_test_event_artifacts(programs.clone(), additional_programs)?;

Ok(Self {
pt_context: Rc::new(RefCell::new(Some(pt_context))),
programs,
rt: Arc::new(rt),
loader: Rc::new(loader),
})
}

/// Get a handle to the ProgramTestContext.
pub fn get_program_test_context(&self) -> ProgramTestContextHandle {
assert!(
self.pt_context.borrow().is_some(),
"ProgramTestContext is already acquired!"
);
ProgramTestContextHandle::new(Rc::clone(&self.pt_context))
}

/// Add an account to the ProgramTestContext.
pub fn add_account(&self, account_pubkey: &Pubkey, account_data: &AccountSharedData) {
let mut pt_context = self.get_program_test_context();
if let Some(ctx) = &mut *pt_context {
ctx.set_account(account_pubkey, account_data);
}
}

/// Register the transaction's recent blockhash into ProgramTestContext.
/// This avoids the need to pre-sign with payer.
async fn register_recent_blockhash_from_transaction(
&self,
tx: &VersionedTransaction,
) -> LiteCoverageError<()> {
let pt_context = self.get_program_test_context();
let ctx = pt_context
.as_ref()
.ok_or(Box::<dyn std::error::Error + Send + Sync>::from(
"Missing ProgramTestContext",
))?;

let trans = tx.clone().into_legacy_transaction().unwrap();
ctx.register_recent_blockhash(&trans.message.recent_blockhash, None);
Ok(())
}

/// Send the transaction to the natively loaded SBF avatars already prepared for
/// obtaining code coverage.
pub fn send_transaction(
&self,
tx: VersionedTransaction,
accounts: &[(Pubkey, AccountSharedData)],
) -> LiteCoverageError<()> {
let _: LiteCoverageError<()> = self.rt.block_on(async {
for (account_pubkey, account_data) in accounts {
self.add_account(account_pubkey, account_data);
}
self.register_recent_blockhash_from_transaction(&tx).await?;

let pt_context = self.get_program_test_context();
let ctx =
pt_context
.as_ref()
.ok_or(Box::<dyn std::error::Error + Send + Sync>::from(
"Missing ProgramTestContext",
))?;
let res = ctx
.banks_client
.process_transaction_with_metadata(tx)
.await?;

log::info!("LiteCoverage transaction result: {:#?}", res);
Ok(())
});
Ok(())
}

/// Log some events provided that some anchor envvars are globally set.
/// This is useful for anchor to know that it's litesvm that's actually used.
/// With this information anchor can go further visualizing the code coverage results
/// or bail out.
fn log_anchor_test_event_artifacts(
progs: Vec<NativeProgram>,
additional_progs: Vec<AdditionalProgram>,
) -> LiteCoverageError<()> {
if let Ok(report_events) = std::env::var("ANCHOR_TEST_CODE_COVERAGE_REPORT_EVENTS") {
if report_events == "true" {
if let Ok(event_file) =
std::env::var("ANCHOR_TEST_CODE_COVERAGE_ARTIFACTS_EVENT_FILE")
{
let mut file = std::fs::OpenOptions::new()
.create(true)
.append(true)
.write(true)
.open(format!("{}.{}.log", event_file, std::process::id()))?;
file.write_all("litesvm=true\n".as_bytes())?;
for (pubkey, name) in &progs {
file.write_all(format!("{}={}\n", name, pubkey).as_bytes())?;
}
for (pubkey, name) in &additional_progs {
file.write_all(format!("{}={}\n", name, pubkey).as_bytes())?;
}
}
}
}
Ok(())
}
}
Loading