use serde_json::json;
use solwatch::audit::max_uniform_ratio;
use solwatch::chain::{ata_pda, first_sol_source, is_pubkey, pumpfun_bonding_curve_pda};
use solwatch::checks::bundles::mint_deltas_detailed;
use solwatch::checks::cluster::{ancestors_of, apply_funding_evidence, jito_tip, EvidenceGraph};
use std::collections::HashMap;
const TIP: &str = "96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5";
#[test]
fn pumpfun_curve_pda_matches_chain() {
let mint = "6LD2ep3ruTVe7zacGHoMpfMdzVqYBmUSwXRVJRNQpump";
let curve = pumpfun_bonding_curve_pda(mint).expect("derives");
assert_eq!(curve, "EMaBKenrqP5CxfVfSP65G9gHjxgphnzZzJj9ujHWLDeU");
let ata = ata_pda(&curve, mint, solwatch::config::SPL_TOKEN).expect("ata derives");
assert_eq!(ata, "8LZx3G25qQtcWmgwrxWFR689ZTDpjSXfwsse4XpWRs2L");
}
#[test]
fn curve_pda_is_deterministic_and_distinct() {
let a = pumpfun_bonding_curve_pda("DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263").unwrap();
let b = pumpfun_bonding_curve_pda("EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm").unwrap();
assert!(is_pubkey(&a) && is_pubkey(&b));
assert_ne!(a, b);
assert_eq!(
a,
pumpfun_bonding_curve_pda("DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263").unwrap()
);
assert!(pumpfun_bonding_curve_pda("not-base58!").is_none());
}
fn tip_ins(dest: &str, lamports: u64) -> serde_json::Value {
json!({"program": "system", "parsed": {"type": "transfer",
"info": {"source": "payer", "destination": dest, "lamports": lamports}}})
}
#[test]
fn jito_tip_top_level() {
let tx = json!({"transaction": {"message": {"instructions": [
{"program": "spl-token", "parsed": {"type": "transfer", "info": {}}},
tip_ins(TIP, 100_000),
]}}});
assert_eq!(jito_tip(&tx), Some((TIP.to_string(), 100_000)));
}
#[test]
fn jito_tip_inner_cpi() {
let tx = json!({
"transaction": {"message": {"instructions": [
{"programId": "SomeRouter11111111111111111111111111111111"}
]}},
"meta": {"innerInstructions": [
{"index": 0, "instructions": [ tip_ins(TIP, 5000) ]}
]}
});
assert_eq!(jito_tip(&tx), Some((TIP.to_string(), 5000)));
}
#[test]
fn jito_tip_ignores_dust_and_strangers() {
let dust = json!({"transaction": {"message": {"instructions": [tip_ins(TIP, 999)]}}});
assert_eq!(jito_tip(&dust), None);
let other = json!({"transaction": {"message": {"instructions": [
tip_ins("SomeRandomWallet1111111111111111111111111111", 100_000)]}}});
assert_eq!(jito_tip(&other), None);
}
#[test]
fn first_sol_source_finds_transfer_and_create() {
let w = "FreshWallet111111111111111111111111111111111";
let tx = json!({"transaction": {"message": {"instructions": [
{"parsed": {"type": "transfer", "info":
{"source": "FunderA", "destination": w, "lamports": 50_000_000}}}]}}});
assert_eq!(first_sol_source(&tx, w), Some("FunderA".to_string()));
let tx2 = json!({
"transaction": {"message": {"instructions": []}},
"meta": {"innerInstructions": [{"index": 0, "instructions": [
{"parsed": {"type": "createAccount", "info":
{"source": "FunderB", "newAccount": w, "lamports": 2_000_000}}}]}]}
});
assert_eq!(first_sol_source(&tx2, w), Some("FunderB".to_string()));
let tx3 = json!({"transaction": {"message": {"instructions": [
{"parsed": {"type": "transfer", "info":
{"source": "FunderC", "destination": "other", "lamports": 1}}}]}}});
assert_eq!(first_sol_source(&tx3, w), None);
}
#[test]
fn mint_deltas_resolve_token_accounts() {
let mint = "M1nt111111111111111111111111111111111111111";
let tx = json!({
"transaction": {"message": {"accountKeys": [
{"pubkey": "feePayer"}, {"pubkey": "prog"}, {"pubkey": "curveTa"},
{"pubkey": "aliceTa"}
]}},
"meta": {
"preTokenBalances": [
{"accountIndex": 2, "mint": mint, "owner": "curve", "uiTokenAmount": {"amount": "1000"}}
],
"postTokenBalances": [
{"accountIndex": 2, "mint": mint, "owner": "curve", "uiTokenAmount": {"amount": "400"}},
{"accountIndex": 3, "mint": mint, "owner": "alice", "uiTokenAmount": {"amount": "600"}}
]
}
});
let d = mint_deltas_detailed(&tx, mint);
assert_eq!(d.len(), 1);
assert_eq!(d[0].owner, "alice");
assert_eq!(d[0].amount, 600);
assert_eq!(d[0].token_account.as_deref(), Some("aliceTa"));
}
fn wallets(n: usize, prefix: &str) -> Vec<String> {
(0..n).map(|i| format!("{prefix}{i:04}")).collect()
}
#[test]
fn union_needs_accumulated_weight_of_one() {
let ws = wallets(3, "w");
let mut g = EvidenceGraph::new(ws.clone());
g.add(&ws[0], &ws[1], "same_block", 0.5, None, Some(1), None);
assert!(g.clusters().is_empty());
g.add(&ws[0], &ws[1], "bundle", 0.4, None, Some(1), None);
assert!(g.clusters().is_empty());
g.add(&ws[0], &ws[1], "funding", 1.0, None, None, None);
let c = g.clusters();
assert_eq!(c, vec![vec![0, 1]]);
g.add(
&ws[1],
&ws[2],
"transfer",
1.0,
Some("5".into()),
Some(9),
None,
);
assert_eq!(g.clusters(), vec![vec![0, 1, 2]]);
}
#[test]
fn display_edges_never_union() {
let ws = wallets(2, "w");
let mut g = EvidenceGraph::new(ws.clone());
g.add_display("SomeCex", &ws[0], "funding", 0.0, None);
g.add_display("SomeCex", &ws[1], "funding", 0.0, None);
assert!(g.clusters().is_empty());
}
#[test]
fn cex_hub_must_not_merge_everything() {
let hub = "CexHotWallet11111111111111111111111111111111".to_string();
let funder = "PrivateFunder1111111111111111111111111111111".to_string();
let mut all = wallets(500, "cexuser");
let ring = wallets(5, "ring");
all.extend(ring.clone());
let mut g = EvidenceGraph::new(all.clone());
let mut funder_cache: HashMap<String, Option<String>> = HashMap::new();
for w in &all[..500] {
funder_cache.insert(w.clone(), Some(hub.clone()));
}
for w in &ring {
funder_cache.insert(w.clone(), Some(funder.clone()));
}
let chain_parent = HashMap::new();
let mut funder_stop = HashMap::new();
funder_stop.insert(hub.clone(), true); funder_stop.insert(funder.clone(), false);
apply_funding_evidence(&mut g, &all, &funder_cache, &chain_parent, &funder_stop);
let after_funding = g.clusters();
assert_eq!(
after_funding.len(),
1,
"the private ring unions on funding alone"
);
assert_eq!(after_funding[0].len(), 5);
for pair in all[..40].windows(2) {
g.add(&pair[0], &pair[1], "same_block", 0.5, None, Some(3), None);
}
for pair in ring.windows(2) {
g.add(&pair[0], &pair[1], "same_block", 0.5, None, Some(3), None);
}
let clusters = g.clusters();
assert_eq!(
clusters.len(),
1,
"only the private-funder ring may cluster"
);
assert_eq!(clusters[0].len(), 5);
let members: Vec<&str> = clusters[0].iter().map(|&i| all[i].as_str()).collect();
assert!(members.iter().all(|m| m.starts_with("ring")));
assert!(g
.edges
.iter()
.any(|e| e.from == hub && e.weight == 0.0 && e.kind == "funding"));
}
#[test]
fn unknown_funder_stop_status_fails_safe() {
let ws = wallets(4, "w");
let mut g = EvidenceGraph::new(ws.clone());
let mut funder_cache = HashMap::new();
for w in &ws {
funder_cache.insert(w.clone(), Some("UnprobedFunder".to_string()));
}
apply_funding_evidence(&mut g, &ws, &funder_cache, &HashMap::new(), &HashMap::new());
assert!(g.clusters().is_empty());
}
#[test]
fn shared_grand_funder_is_weak_evidence() {
let ws = wallets(2, "w");
let mut g = EvidenceGraph::new(ws.clone());
let mut funder_cache = HashMap::new();
funder_cache.insert(ws[0].clone(), Some("f1".to_string()));
funder_cache.insert(ws[1].clone(), Some("f2".to_string()));
let mut chain_parent = HashMap::new();
chain_parent.insert("f1".to_string(), "gf".to_string());
chain_parent.insert("f2".to_string(), "gf".to_string());
let mut funder_stop = HashMap::new();
for f in ["f1", "f2", "gf"] {
funder_stop.insert(f.to_string(), false);
}
apply_funding_evidence(&mut g, &ws, &funder_cache, &chain_parent, &funder_stop);
assert!(
g.clusters().is_empty(),
"0.5 ancestor evidence must not union"
);
g.add(&ws[0], &ws[1], "same_block", 0.5, None, Some(7), None);
assert_eq!(g.clusters(), vec![vec![0, 1]]);
}
#[test]
fn ancestors_stop_at_stop_nodes() {
let mut funder_cache = HashMap::new();
funder_cache.insert("w".to_string(), Some("f1".to_string()));
let mut chain_parent = HashMap::new();
chain_parent.insert("f1".to_string(), "cex".to_string());
chain_parent.insert("cex".to_string(), "beyond".to_string());
let mut funder_stop = HashMap::new();
funder_stop.insert("f1".to_string(), false);
funder_stop.insert("cex".to_string(), true);
let anc = ancestors_of("w", &funder_cache, &chain_parent, &funder_stop, 3);
assert_eq!(anc, vec!["f1".to_string(), "cex".to_string()]);
}
#[test]
fn oversized_clusters_are_discarded() {
let ws = wallets(1001, "x");
let mut g = EvidenceGraph::new(ws.clone());
for i in 0..1000 {
g.add(&ws[i], &ws[i + 1], "transfer", 1.0, None, None, None);
}
assert!(g.clusters().is_empty());
}
#[test]
fn uniform_buy_ratio() {
let amounts = vec![
1_000_000u128,
1_000_100,
999_950,
1_000_000,
1_000_005,
999_999,
50_000,
73_000_000,
];
let r = max_uniform_ratio(&amounts);
assert!((r - 0.75).abs() < 1e-9, "got {r}");
assert_eq!(max_uniform_ratio(&[]), 0.0);
let organic = vec![100u128, 5_000, 42_000, 1_000_000, 9_000_000];
assert!(max_uniform_ratio(&organic) <= 0.4);
}
#[test]
fn linked_groups_surface_sub_threshold_evidence() {
use std::collections::HashSet;
let ws = wallets(5, "w");
let mut g = EvidenceGraph::new(ws.clone());
g.add(&ws[0], &ws[1], "transfer", 1.0, None, None, None);
g.add(&ws[2], &ws[3], "same_block", 0.5, None, Some(3), None);
let clusters = g.clusters();
assert_eq!(clusters, vec![vec![0, 1]]);
let clustered: HashSet<usize> = clusters.iter().flatten().copied().collect();
let linked = g.linked_groups(&clustered);
assert_eq!(
linked,
vec![vec![2, 3]],
"sub-threshold pair surfaces as a lead"
);
}