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![],
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![],
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"));
}
#[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);
}