use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Exploit {
pub id: String,
pub protocol: String,
pub date: String,
pub loss_usd: u64,
pub chain: String,
pub invariant_ids: Vec<String>,
pub attack_summary: String,
pub tx_hash: String,
pub postmortem_url: String,
}
static EXPLOIT_REGISTRY_JSON: &[u8] = include_bytes!("exploits.json");
pub static EXPLOIT_REGISTRY: Lazy<ExploitRegistry> =
Lazy::new(|| ExploitRegistry::load().expect("Failed to parse exploit registry"));
#[derive(Debug, Clone)]
pub struct ExploitRegistry {
exploits: BTreeMap<String, Exploit>,
}
impl ExploitRegistry {
fn load() -> Result<Self, Box<dyn std::error::Error>> {
let exploits: Vec<Exploit> = serde_json::from_slice(EXPLOIT_REGISTRY_JSON)?;
let mut map = BTreeMap::new();
for exploit in exploits {
map.insert(exploit.id.clone(), exploit);
}
Ok(Self { exploits: map })
}
pub fn get(&self, id: &str) -> Option<&Exploit> {
self.exploits.get(id)
}
pub fn all(&self) -> Vec<&Exploit> {
self.exploits.values().collect()
}
pub fn by_chain(&self, chain: &str) -> Vec<&Exploit> {
self.exploits
.values()
.filter(|e| e.chain.eq_ignore_ascii_case(chain))
.collect()
}
pub fn by_invariant(&self, invariant_id: &str) -> Vec<&Exploit> {
self.exploits
.values()
.filter(|e| e.invariant_ids.iter().any(|id| id == invariant_id))
.collect()
}
pub fn total_loss(&self) -> u64 {
self.exploits.values().map(|e| e.loss_usd).sum()
}
pub fn count(&self) -> usize {
self.exploits.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_registry_loads() {
let registry = &*EXPLOIT_REGISTRY;
assert!(registry.count() > 0, "Registry should have exploits");
}
#[test]
fn test_registry_access() {
let registry = &*EXPLOIT_REGISTRY;
let all = registry.all();
assert!(!all.is_empty());
for exploit in all {
assert!(!exploit.id.is_empty());
assert!(!exploit.protocol.is_empty());
assert!(!exploit.chain.is_empty());
}
}
}