use std::{
env,
path::{Path, PathBuf},
str::FromStr,
};
use anyhow::{Context, Result};
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_sdk::{account::Account, pubkey::Pubkey, signature::Signer};
use wp_solana_test_core::{
fund_token, fund_wsol, override_mint_authority, set_account, TestContext,
};
pub async fn fetch_program(client: &RpcClient, program_id: &Pubkey) -> Result<Vec<u8>> {
let account = client.get_account(program_id).await?;
const BPF_LOADER_UPGRADEABLE: &str = "BPFLoaderUpgradeab1e11111111111111111111111";
let bpf_loader_upgradeable = Pubkey::from_str(BPF_LOADER_UPGRADEABLE)?;
if account.owner == bpf_loader_upgradeable {
let (program_data_address, _bump) =
Pubkey::find_program_address(&[program_id.as_ref()], &bpf_loader_upgradeable);
let program_data_account = client.get_account(&program_data_address).await?;
const PROGRAM_DATA_HEADER_SIZE: usize = 45;
if program_data_account.data.len() < PROGRAM_DATA_HEADER_SIZE {
return Err(anyhow::anyhow!(
"Program data account too small: {} bytes (expected at least {})",
program_data_account.data.len(),
PROGRAM_DATA_HEADER_SIZE
));
}
let program_bytes = program_data_account.data[PROGRAM_DATA_HEADER_SIZE..].to_vec();
Ok(program_bytes)
} else {
Ok(account.data.to_vec())
}
}
fn resolve_fallback_path_from(base_dir: &Path, fallback_path: &str) -> Option<PathBuf> {
let candidate = Path::new(fallback_path);
if candidate.is_absolute() && candidate.exists() {
return Some(candidate.to_path_buf());
}
let mut dir = base_dir.to_path_buf();
loop {
let candidate = dir.join(fallback_path);
if candidate.exists() {
return Some(candidate);
}
if !dir.pop() {
break;
}
}
None
}
fn resolve_fallback_path(fallback_path: &str) -> Option<PathBuf> {
let base_dir = env::current_dir().ok()?;
resolve_fallback_path_from(&base_dir, fallback_path)
}
pub async fn fetch_and_add_program(
ctx: &TestContext,
client: &RpcClient,
program_id: Pubkey,
fallback_path: &str,
) -> Result<()> {
let program_bytes = match fetch_program(client, &program_id).await {
Ok(bytes) => bytes,
Err(e) => {
tracing::warn!(
error = %e,
program_id = %program_id,
"Failed to fetch program from RPC; trying local fallback"
);
if let Some(resolved_path) = resolve_fallback_path(fallback_path) {
tracing::info!(path = %resolved_path.display(), "Loading from file instead");
std::fs::read(&resolved_path).context("Failed to read program binary from file")?
} else {
return Err(anyhow::anyhow!("Failed to fetch program: {}", e));
}
}
};
{
let mut svm = ctx.lock_svm();
svm.add_program(program_id, &program_bytes)?;
}
tracing::info!(program_id = %program_id, "Program added successfully");
Ok(())
}
pub fn setup_mint_and_fund(
ctx: &TestContext,
mint: Pubkey,
mint_account: Account,
is_native: bool,
amount: u64,
) -> Result<Pubkey> {
set_account(ctx, mint, mint_account)?;
if is_native {
let ata = fund_wsol(ctx, ctx.payer.pubkey(), amount)?;
tracing::info!(
mint = %mint,
sol_amount = amount / 1_000_000_000,
"Wrapped SOL into WSOL"
);
Ok(ata)
} else {
override_mint_authority(ctx, mint, ctx.payer.pubkey())?;
let ata = fund_token(ctx, mint, ctx.payer.pubkey(), amount)?;
tracing::info!(
mint = %mint,
amount,
"Minted tokens to payer (authority overridden)"
);
Ok(ata)
}
}
#[cfg(test)]
mod tests {
use std::fs;
use tempfile::tempdir;
use super::resolve_fallback_path_from;
#[test]
fn resolve_fallback_path_finds_hit_in_base_dir() {
let dir = tempdir().expect("tempdir");
let file = dir.path().join("program.so");
fs::write(&file, b"so").expect("write fallback");
let resolved = resolve_fallback_path_from(dir.path(), "program.so").expect("resolved path");
assert_eq!(resolved, file);
}
#[test]
fn resolve_fallback_path_finds_workspace_ancestor_hit() {
let dir = tempdir().expect("tempdir");
let workspace_root = dir.path();
let crate_dir = workspace_root.join("crates/protocols/orca/whirlpool-sdk");
fs::create_dir_all(&crate_dir).expect("create nested crate dir");
let fallback = workspace_root.join("target/deploy/whirlpool.so");
fs::create_dir_all(fallback.parent().expect("fallback parent"))
.expect("create target/deploy");
fs::write(&fallback, b"so").expect("write fallback");
let resolved = resolve_fallback_path_from(&crate_dir, "target/deploy/whirlpool.so")
.expect("resolved path");
assert_eq!(resolved, fallback);
}
#[test]
fn resolve_fallback_path_returns_none_when_missing() {
let dir = tempdir().expect("tempdir");
assert!(resolve_fallback_path_from(dir.path(), "target/deploy/missing.so").is_none());
}
}