use serde_json::json;
use solwatch::chain::{is_pubkey, metadata_pda, parse_metaplex};
use solwatch::checks::bundles::mint_deltas;
use solwatch::checks::holders::vault_reserve_owners;
use solwatch::types::{score_flags, Flag, Severity, Verdict};
const BONK: &str = "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263";
#[test]
fn pool_reserve_vault_is_detected() {
let reserve_ta = "7h8WiEhfhgcZC3ThcBBWTEL6dh6NyLmMad5aXCbs1nji";
let reserve_owner = "FhVo3mqL8PW5pH5U2CN4XE33DokiyZnUwuGpH2hmHLuM";
let real_holder_ta = "Dbpr8Sa1wVSLu6szQsyE9nMN2fcms9LdLk4tRTv1C2Po";
let real_holder_owner = "3xEGYaVoEjfWjwMo5WbYscu8q9KcDqvQLmUC6ewzQBSS";
let ta_bytes = bs58::decode(reserve_ta).into_vec().unwrap();
let mut blob = vec![0u8; 168];
blob.extend_from_slice(&ta_bytes);
blob.extend_from_slice(&[9u8; 64]);
let accounts = vec![
(reserve_ta.to_string(), reserve_owner.to_string()),
(real_holder_ta.to_string(), real_holder_owner.to_string()),
];
let vaults = vault_reserve_owners(&[blob], &accounts);
assert!(
vaults.contains(reserve_owner),
"the reserve owner must be flagged as a pool vault"
);
assert!(
!vaults.contains(real_holder_owner),
"a real holder whose token account is NOT in any pool must never be excluded"
);
}
#[test]
fn no_false_vault_exclusions() {
let accounts = vec![(
"7h8WiEhfhgcZC3ThcBBWTEL6dh6NyLmMad5aXCbs1nji".to_string(),
"FhVo3mqL8PW5pH5U2CN4XE33DokiyZnUwuGpH2hmHLuM".to_string(),
)];
assert!(vault_reserve_owners(&[], &accounts).is_empty());
assert!(vault_reserve_owners(&[vec![7u8; 300]], &accounts).is_empty());
}
#[test]
fn score_clean_token() {
let flags = vec![
Flag::new("mint_authority", Severity::Good, "ok", "".into(), 0),
Flag::new("freeze_authority", Severity::Good, "ok", "".into(), 0),
];
let (score, verdict) = score_flags(&flags, true);
assert_eq!(score, 100);
assert_eq!(verdict, Verdict::LowRisk);
}
#[test]
fn score_honeypot_hard_caps_at_5() {
let flags = vec![Flag::new(
"non_transferable",
Severity::Critical,
"honeypot",
"".into(),
60,
)
.hard()];
let (score, verdict) = score_flags(&flags, true);
assert!(score <= 5, "critical hard fail must cap at 5, got {score}");
assert_eq!(verdict, Verdict::Avoid);
}
#[test]
fn score_non_critical_hard_caps_at_20() {
let flags = vec![Flag::new("no_liquidity", Severity::Danger, "x", "".into(), 25).hard()];
let (score, _) = score_flags(&flags, true);
assert!(score <= 20);
}
#[test]
fn score_unresolved_token_is_unknown() {
let (score, verdict) = score_flags(&[], false);
assert_eq!(score, 0);
assert_eq!(verdict, Verdict::Unknown);
}
#[test]
fn pubkey_validation() {
assert!(is_pubkey(BONK));
assert!(is_pubkey("So11111111111111111111111111111111111111112"));
assert!(!is_pubkey("0x8e62F281f282686fCa6dCB39288069a93fC23F1c")); assert!(!is_pubkey("notbase58!!!"));
assert!(!is_pubkey(""));
}
#[test]
fn metadata_pda_derivation() {
let pda = metadata_pda(BONK).expect("pda derives");
assert!(is_pubkey(&pda));
assert_eq!(pda, metadata_pda(BONK).unwrap());
}
#[test]
fn metaplex_parser_roundtrip() {
let mut b: Vec<u8> = vec![4];
b.extend([7u8; 32]); b.extend([9u8; 32]); for (s, cap) in [("Bonk", 32), ("BONK", 10), ("https://x/meta.json", 200)] {
b.extend((cap as u32).to_le_bytes());
let mut raw = s.as_bytes().to_vec();
raw.resize(cap, 0);
b.extend(raw);
}
b.extend(100u16.to_le_bytes()); b.push(0); b.push(0); b.push(1); let m = parse_metaplex(&b).expect("parses");
assert_eq!(m.name, "Bonk");
assert_eq!(m.symbol, "BONK");
assert_eq!(m.uri, "https://x/meta.json");
assert!(m.is_mutable);
assert_eq!(m.update_authority, bs58_of([7u8; 32]));
}
#[test]
fn metaplex_parser_rejects_garbage() {
assert!(parse_metaplex(&[]).is_none());
assert!(parse_metaplex(&[4u8; 40]).is_none());
let mut b: Vec<u8> = vec![4];
b.extend([0u8; 64]);
b.extend(u32::MAX.to_le_bytes());
assert!(parse_metaplex(&b).is_none());
}
fn bs58_of(b: [u8; 32]) -> String {
bs58::encode(b).into_string()
}
#[test]
fn mint_deltas_from_parsed_tx() {
let mint = "M1nt111111111111111111111111111111111111111";
let tx = json!({
"meta": {
"preTokenBalances": [
{"accountIndex": 3, "mint": mint, "owner": "curve", "uiTokenAmount": {"amount": "1000"}},
{"accountIndex": 4, "mint": "OTHER", "owner": "alice", "uiTokenAmount": {"amount": "5"}}
],
"postTokenBalances": [
{"accountIndex": 3, "mint": mint, "owner": "curve", "uiTokenAmount": {"amount": "400"}},
{"accountIndex": 5, "mint": mint, "owner": "alice", "uiTokenAmount": {"amount": "600"}},
{"accountIndex": 4, "mint": "OTHER", "owner": "alice", "uiTokenAmount": {"amount": "9"}}
]
}
});
let mut d = mint_deltas(&tx, mint);
d.sort();
assert_eq!(d, vec![("alice".to_string(), 600u128)]);
}
#[test]
fn tweet_is_bounded() {
use solwatch::report::render_tweet;
use solwatch::types::{AuditResult, TokenMeta};
let r = AuditResult {
address: BONK.into(),
chain: "solana",
scanned_at: "2026-07-13T00:00:00Z".into(),
token: TokenMeta {
symbol: Some("X".repeat(120)),
market_cap_usd: Some(123456789012345.0),
..Default::default()
},
score: 3,
verdict: Verdict::Avoid,
flags: vec![
Flag::new(
"a",
Severity::Critical,
&"very long flag title ".repeat(10),
"d".into(),
60,
),
Flag::new(
"b",
Severity::Danger,
&"another long one ".repeat(10),
"d".into(),
30,
),
],
sections: json!({}),
warnings: vec![],
coverage_gaps: vec![],
rokha_url: None,
rokha_app: serde_json::Value::Null,
};
assert!(render_tweet(&r).chars().count() <= 280);
let app = solwatch::report::render_rokha_app(&r);
assert_eq!(app["verdict"], "avoid");
let html = solwatch::report::render_html(&r);
assert!(html.contains("__AUDIT"));
assert!(!html.contains("</script><script>alert"));
let dir = std::env::temp_dir().join(format!("solwatch-test-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
let md = solwatch::report::write_report(&r, &dir, false).unwrap();
let base = md.strip_suffix(".md").unwrap();
assert!(dir.join(&md).exists());
assert!(dir.join(format!("{base}.json")).exists());
assert!(dir.join("index.json").exists());
assert!(
!dir.join(format!("{base}.html")).exists(),
".html written without --report-html"
);
let md = solwatch::report::write_report(&r, &dir, true).unwrap();
let base = md.strip_suffix(".md").unwrap();
assert!(dir.join(format!("{base}.html")).exists());
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn raydium_shared_vault_authorities_excluded() {
let auth: std::collections::HashMap<&str, &str> = solwatch::config::shared_pool_authorities()
.into_iter()
.collect();
assert!(auth.contains_key("5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1"));
assert!(auth.contains_key("GpMZbSM2GgvTKHJirzeGfMFoaZ8UR2X7F4v8vHTvxFbL"));
}
fn t2022_mint(extensions: Vec<serde_json::Value>) -> solwatch::chain::MintInfo {
solwatch::chain::MintInfo {
program: solwatch::config::TOKEN_2022.into(),
decimals: 6,
supply: 1_000_000,
supply_raw: "1000000".into(),
mint_authority: None,
freeze_authority: None,
extensions,
}
}
#[test]
fn pausable_authority_is_hard_danger() {
let m = t2022_mint(vec![json!({
"extension": "pausableConfig",
"state": {"authority": "Pause111111111111111111111111111111111111111", "paused": false}
})]);
let a = solwatch::checks::mint::analyze(&m, None);
let f = a
.flags
.iter()
.find(|f| f.id == "pausable")
.expect("pausable flag");
assert_eq!(f.severity, Severity::Danger);
assert!(
f.hard_fail,
"a live pause authority must hard-cap the score"
);
}
#[test]
fn paused_now_is_critical_honeypot() {
let m = t2022_mint(vec![json!({
"extension": "pausableConfig",
"state": {"authority": "Pause111111111111111111111111111111111111111", "paused": true}
})]);
let a = solwatch::checks::mint::analyze(&m, None);
let f = a
.flags
.iter()
.find(|f| f.id == "paused")
.expect("paused flag");
assert_eq!(f.severity, Severity::Critical);
assert!(f.hard_fail);
assert!(a.flags.iter().all(|f| f.id != "pausable"));
}
#[test]
fn revoked_pause_authority_is_silent() {
let m = t2022_mint(vec![json!({
"extension": "pausableConfig",
"state": {"authority": serde_json::Value::Null, "paused": false}
})]);
let a = solwatch::checks::mint::analyze(&m, None);
assert!(a
.flags
.iter()
.all(|f| f.id != "pausable" && f.id != "paused"));
}
#[test]
fn zero_fee_with_live_authority_flags() {
let m = t2022_mint(vec![json!({
"extension": "transferFeeConfig",
"state": {
"transferFeeConfigAuthority": "Fee1111111111111111111111111111111111111111",
"newerTransferFee": {"transferFeeBasisPoints": 0}
}
})]);
let a = solwatch::checks::mint::analyze(&m, None);
assert!(
a.flags
.iter()
.any(|f| f.id == "transfer_fee_authority" && f.severity == Severity::Warning),
"0% fee with a live authority must warn it can be switched on"
);
assert!(a.flags.iter().all(|f| f.id != "transfer_fee"));
}
#[test]
fn zero_fee_revoked_authority_is_silent() {
let m = t2022_mint(vec![json!({
"extension": "transferFeeConfig",
"state": {
"transferFeeConfigAuthority": serde_json::Value::Null,
"newerTransferFee": {"transferFeeBasisPoints": 0}
}
})]);
let a = solwatch::checks::mint::analyze(&m, None);
assert!(a.flags.iter().all(|f| f.id != "transfer_fee_authority"));
}
#[test]
fn tweet_marks_partial_scan_on_reassuring_verdict() {
use solwatch::report::render_tweet;
use solwatch::types::{AuditResult, TokenMeta};
let mk = |verdict, coverage: &str| AuditResult {
address: BONK.into(),
chain: "solana",
scanned_at: "2026-07-16T00:00:00Z".into(),
token: TokenMeta {
symbol: Some("PONS".into()),
market_cap_usd: Some(500_000.0),
..Default::default()
},
score: 85,
verdict,
flags: vec![],
sections: json!({"cluster": {"scope": {"coverage": coverage}}}),
warnings: vec![],
coverage_gaps: vec![],
rokha_url: None,
rokha_app: serde_json::Value::Null,
};
assert!(render_tweet(&mk(Verdict::LowRisk, "partial")).contains("partial scan"));
assert!(!render_tweet(&mk(Verdict::LowRisk, "full")).contains("partial scan"));
assert!(render_tweet(&mk(Verdict::Avoid, "partial")).contains("partial scan"));
let mut fast = mk(Verdict::LowRisk, "full");
fast.coverage_gaps = vec!["launch-window + cluster passes skipped (--fast)".into()];
assert!(render_tweet(&fast).contains("partial scan"));
}
#[test]
fn coverage_ceiling_downgrades_low_risk_to_fair() {
use solwatch::types::verdict_from_score;
assert_eq!(verdict_from_score(88), Verdict::LowRisk);
assert_eq!(verdict_from_score(79), Verdict::Fair);
assert_eq!(verdict_from_score(35), Verdict::HighRisk);
assert_eq!(verdict_from_score(5), Verdict::Avoid);
}
#[test]
fn undecodable_extension_is_never_silent() {
let m = t2022_mint(vec![json!({"extension": "unparseableExtension"})]);
let a = solwatch::checks::mint::analyze(&m, None);
assert!(
a.flags.iter().any(|f| f.id == "extension_undecodable"),
"an undecodable Token-2022 extension must raise a flag — unknown powers are not absent powers"
);
}
#[test]
fn revoked_permanent_delegate_is_not_a_honeypot() {
let revoked = t2022_mint(vec![json!({
"extension": "permanentDelegate",
"state": {"delegate": serde_json::Value::Null}
})]);
let a = solwatch::checks::mint::analyze(&revoked, None);
assert!(
a.flags.iter().all(|f| f.id != "permanent_delegate"),
"a revoked permanent delegate must not be flagged as an active seize power"
);
let active = t2022_mint(vec![json!({
"extension": "permanentDelegate",
"state": {"delegate": "De1e9ate11111111111111111111111111111111111"}
})]);
let a2 = solwatch::checks::mint::analyze(&active, None);
let f = a2
.flags
.iter()
.find(|f| f.id == "permanent_delegate")
.expect("an active permanent delegate is a honeypot lever and must flag");
assert!(f.hard_fail);
}
#[test]
fn scheduled_transfer_fee_reports_the_higher_rate() {
let m = t2022_mint(vec![json!({
"extension": "transferFeeConfig",
"state": {
"newerTransferFee": {"transferFeeBasisPoints": 0, "epoch": 999},
"olderTransferFee": {"transferFeeBasisPoints": 5000, "epoch": 1},
"transferFeeConfigAuthority": serde_json::Value::Null
}
})]);
let a = solwatch::checks::mint::analyze(&m, None);
let f = a
.flags
.iter()
.find(|f| f.id == "transfer_fee")
.expect("a 50% active fee must be flagged even when a 0% change is scheduled");
assert_eq!(f.severity, Severity::Critical);
assert!(f.hard_fail);
assert!(a.flags.iter().any(|f| f.id == "transfer_fee_scheduled"));
}
#[test]
fn coverage_accumulator_gates_the_verdict_cap() {
use solwatch::types::Coverage;
let mut cov = Coverage::new();
assert!(!cov.is_blind(), "a fresh accumulator is not blind");
cov.degrade("launch-window bundle + wallet-cluster passes skipped (--fast)");
assert!(cov.is_blind());
assert!(cov.summary().contains("--fast"));
let before = cov.gaps().len();
cov.degrade("launch-window bundle + wallet-cluster passes skipped (--fast)");
assert_eq!(cov.gaps().len(), before);
assert_eq!(solwatch::types::verdict_from_score(79), Verdict::Fair);
}
#[test]
fn unknown_verdict_publishes_no_score() {
use solwatch::report::render_tweet;
use solwatch::types::{AuditResult, TokenMeta};
let r = AuditResult {
address: BONK.into(),
chain: "solana",
scanned_at: "2026-07-19T00:00:00Z".into(),
token: TokenMeta::default(),
score: 0,
verdict: Verdict::Unknown,
flags: vec![],
sections: json!({}),
warnings: vec![],
coverage_gaps: vec!["mint account unresolved — no checks could run".into()],
rokha_url: None,
rokha_app: serde_json::Value::Null,
};
let t = render_tweet(&r);
assert!(
!t.contains("0/100"),
"UNKNOWN must not publish a 0/100 score: {t}"
);
}
#[test]
fn rokha_app_carries_coverage() {
use solwatch::types::{AuditResult, TokenMeta};
let r = AuditResult {
address: BONK.into(),
chain: "solana",
scanned_at: "2026-07-19T00:00:00Z".into(),
token: TokenMeta {
symbol: Some("BONK".into()),
..Default::default()
},
score: 79,
verdict: Verdict::Fair,
flags: vec![],
sections: json!({}),
warnings: vec!["jupiter: timeout".into()],
coverage_gaps: vec!["live holder concentration unreadable".into()],
rokha_url: None,
rokha_app: serde_json::Value::Null,
};
let app = solwatch::report::render_rokha_app(&r);
assert_eq!(app["coverage"], "partial");
assert_eq!(
app["coverageGaps"][0],
"live holder concentration unreadable"
);
assert_eq!(app["warnings"][0], "jupiter: timeout");
}
#[test]
fn mispriced_pairs_are_dropped() {
use solwatch::chain::dexscreener::{drop_mispriced_pairs, DexPair};
let mk = |price: &str, liq: f64| -> DexPair {
serde_json::from_value(json!({
"chainId": "solana",
"dexId": "meteora",
"pairAddress": format!("pair{price}"),
"baseToken": {"address": "JUP", "symbol": "JUP", "name": "Jupiter"},
"quoteToken": {"address": "Q", "symbol": "Q", "name": "Q"},
"priceUsd": price,
"liquidity": {"usd": liq},
}))
.expect("fixture parses")
};
let mut pairs = vec![
mk("949.12", 369_384_475.0), mk("952.99", 53_296_320.0), mk("0.1943", 840_227.0), mk("0.1942", 319_783.0), mk("0.1939", 138_845.0), ];
let (dropped, median) = drop_mispriced_pairs(&mut pairs);
assert_eq!(dropped, 2, "both ~4,900x-off pairs must be discarded");
assert_eq!(pairs.len(), 3);
assert!(median.is_some_and(|m| (m - 0.1943).abs() < 0.01));
let total: f64 = pairs.iter().map(solwatch::chain::dexscreener::liq).sum();
assert!(
total < 1_500_000.0,
"liquidity must reflect the real pools only, got {total}"
);
let mut normal = vec![mk("1.00", 100.0), mk("1.03", 100.0), mk("0.97", 100.0)];
let (dropped2, _) = drop_mispriced_pairs(&mut normal);
assert_eq!(dropped2, 0, "a few percent of spread is not mispricing");
let mut curve: Vec<DexPair> = vec![
serde_json::from_value(json!({
"chainId": "solana", "dexId": "pumpfun", "pairAddress": "curve",
"baseToken": {"address": "T", "symbol": "T", "name": "T"},
"quoteToken": {"address": "S", "symbol": "S", "name": "S"},
}))
.unwrap(),
mk("1.00", 10.0),
];
let (dropped3, _) = drop_mispriced_pairs(&mut curve);
assert_eq!(dropped3, 0);
}
#[cfg(test)]
fn result_with_cluster(section: serde_json::Value) -> solwatch::types::AuditResult {
use solwatch::types::{AuditResult, TokenMeta};
AuditResult {
address: BONK.into(),
chain: "solana",
scanned_at: "2026-07-19T00:00:00Z".into(),
token: TokenMeta {
symbol: Some("BONK".into()),
..Default::default()
},
score: 50,
verdict: Verdict::Fair,
flags: vec![],
sections: json!({ "cluster": section }),
warnings: vec![],
coverage_gaps: vec![],
rokha_url: None,
rokha_app: serde_json::Value::Null,
}
}
#[cfg(test)]
fn v2_section() -> serde_json::Value {
json!({
"schema": 2,
"scope": {
"wallets_analyzed": 14, "holders_analyzed": 9, "snipers_analyzed": 5,
"funder_hops": 3, "transfer_probes": 10, "transfer_txs_sampled": 31,
"coverage": "partial", "jitoApiOk": true,
"limits": ["holder set is capped at the 20 largest token accounts"],
"note": "",
},
"findings": [
{"id": 1, "wallets": ["A","B"], "pct_combined": 22.5, "pct_bought": 30.0,
"evidence": "jito_bundle", "confidence": "proven", "proof": "jito:abc", "note": "n1"},
{"id": 2, "wallets": ["C","D"], "pct_combined": 8.0, "pct_bought": 9.0,
"evidence": "shared_funder", "confidence": "probable", "proof": null, "note": "n2"},
{"wallets": ["E","F"], "pct_combined": 3.0, "pct_bought": null,
"evidence": "same_slot", "confidence": "lead", "proof": null, "note": "n3"},
],
"effective_concentration": {
"top10_raw_pct": 41.0, "top10_entity_pct_floor": 52.0,
"largest_entity_pct_floor": 22.5, "is_floor": true,
},
})
}
#[test]
fn wallet_link_renderers_read_the_v2_contract() {
let r = result_with_cluster(v2_section());
let md = solwatch::report::render_markdown(&r);
assert!(
md.contains("## Linked wallets"),
"md section missing:\n{md}"
);
assert!(md.contains("jito bundle"), "evidence not rendered:\n{md}");
assert!(md.contains("**proven**"), "confidence not rendered:\n{md}");
assert!(
md.contains("_lead only_"),
"leads must still be listed:\n{md}"
);
assert!(
md.contains("at least 52%") && md.contains("floors"),
"entity concentration must read as a floor:\n{md}"
);
assert!(
md.contains("20 largest token accounts"),
"structural limits must ride the durable report:\n{md}"
);
let app = solwatch::report::render_rokha_app(&r);
let headings: Vec<&str> = app["sections"]
.as_array()
.unwrap()
.iter()
.filter_map(|s| s["heading"].as_str())
.collect();
assert!(headings.contains(&"Linked wallets"), "{headings:?}");
assert!(
headings.contains(&"What the wallet-link pass cannot see"),
"the app block must carry the limits too: {headings:?}"
);
let metrics = app["metrics"].as_array().unwrap();
let entity = metrics
.iter()
.find(|m| m["label"] == "Entity top-10")
.expect("entity top-10 metric missing");
assert_eq!(
entity["value"], "≥52%",
"the metric tile must show a floor, not a point estimate"
);
}
#[test]
fn a_quiet_wallet_link_pass_still_states_its_limits() {
let mut section = v2_section();
section["findings"] = json!([]);
let r = result_with_cluster(section);
let app = solwatch::report::render_rokha_app(&r);
let headings: Vec<&str> = app["sections"]
.as_array()
.unwrap()
.iter()
.filter_map(|s| s["heading"].as_str())
.collect();
assert!(
headings.contains(&"What the wallet-link pass cannot see"),
"limits must survive an empty findings list: {headings:?}"
);
}
#[test]
fn the_bubblemap_payload_is_gone() {
let s = v2_section();
assert!(s.get("nodes").is_none(), "nodes must not be re-serialized");
assert!(s.get("edges").is_none(), "edges must not be re-serialized");
assert!(
s.get("linked_groups").is_none(),
"leads belong in `findings` at confidence=lead, not a parallel array"
);
assert_eq!(s["schema"], 2);
}