use std::{collections::HashSet, process::Command};
use solana_account_decoder_client_types::UiAccountEncoding;
use solana_client::{
rpc_client::RpcClient,
rpc_config::{RpcAccountInfoConfig, RpcProgramAccountsConfig},
rpc_filter::RpcFilterType,
};
use crate::cli::Args;
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
pub fn dump_program(args: &Args) -> Result<()> {
let out_path = args.dir.join("program.so");
let output = Command::new("solana")
.arg("program")
.arg("dump")
.arg(args.program.to_string())
.arg(&out_path)
.arg("--url")
.arg(&args.url)
.arg("--commitment")
.arg(args.commitment.as_str())
.output()
.map_err(|e| match e.kind() {
std::io::ErrorKind::NotFound => Box::<dyn std::error::Error>::from(
"the `solana` CLI is required but was not found on your PATH. \
Install it from https://solana.com/docs/intro/installation",
),
_ => format!("could not run `solana`: {e}").into(),
})?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("`solana program dump` failed: {}", stderr.trim()).into());
}
println!(
"Wrote executable to {} ({} bytes)",
out_path.display(),
std::fs::metadata(&out_path).map(|m| m.len()).unwrap_or(0)
);
Ok(())
}
pub fn dump_accounts(args: &Args) -> Result<()> {
let client = RpcClient::new_with_timeout_and_commitment(
args.url.clone(),
args.timeout(),
args.commitment.config(),
);
let config = RpcProgramAccountsConfig {
filters: args.only_size.map(|n| vec![RpcFilterType::DataSize(n)]),
account_config: RpcAccountInfoConfig {
encoding: Some(UiAccountEncoding::Base64Zstd),
commitment: Some(args.commitment.config()),
..Default::default()
},
..Default::default()
};
let accounts = client
.get_program_ui_accounts_with_config(&args.program, config)
.map_err(|e| {
format!(
"{e}\n\nhint: many public RPC endpoints restrict or disable \
getProgramAccounts. A dedicated RPC provider, or --only-size to \
narrow the query, usually resolves this."
)
})?;
println!("Found {} program accounts", accounts.len());
let skip: HashSet<usize> = args.skip_size.iter().copied().collect();
let mut written = 0usize;
let mut skipped = 0usize;
let mut failed = 0usize;
for (address, ui_account) in accounts {
let Some(account) = ui_account.to_account() else {
eprintln!("error: could not decode account data for {address}");
failed += 1;
continue;
};
if skip.contains(&account.data.len()) {
skipped += 1;
continue;
}
let path = args
.dir
.join(format!("{}_{address}.bin", account.data.len()));
match std::fs::write(&path, &account.data) {
Ok(()) => written += 1,
Err(e) => {
eprintln!("error: could not write {}: {e}", path.display());
failed += 1;
}
}
}
println!(
"Wrote {written} accounts to {}/<data_len>_<address>.bin",
args.dir.display()
);
if skipped > 0 {
println!("Skipped {skipped} accounts matching --skip-size");
}
if failed > 0 {
return Err(format!("{failed} accounts could not be written").into());
}
Ok(())
}