Files
khadhroony-bot3/ks-pipeline/src/spl_token_2022_correlation.rs
2026-08-09 19:34:08 +02:00

243 lines
10 KiB
Rust

// file: ks-pipeline/src/spl_token_2022_correlation.rs
// version: 4
//! Deterministic Token-2022 instruction-to-state correlation.
/// Correlation outcome between one committed instruction fact and one final state snapshot.
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub enum Token2022CorrelationStatus {
/// The final snapshot contains the extension expected by the instruction fact.
Confirmed,
/// The final snapshot does not contain the extension expected by the instruction fact.
Contradicted,
/// The instruction output is outside the supported correlation inventory.
NotApplicable,
}
/// Deterministic correlation report for one instruction output and one final snapshot.
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct Token2022StateCorrelationReport {
/// Stable caller-provided correlation identity.
pub correlation_key: std::string::String,
/// Canonical account identity checked against the snapshot.
pub account: ks_lib::MdPubkey,
/// Instruction operation or risk fact used for correlation.
pub fact_code: std::string::String,
/// Extension expected from the fact when the fact is supported.
pub expected_extension: std::option::Option<std::string::String>,
/// Final correlation outcome.
pub status: Token2022CorrelationStatus,
/// Snapshot context slot.
pub snapshot_slot: u64,
/// Whether the snapshot contains the expected extension.
pub extension_present: std::option::Option<bool>,
/// Explicit semantic limitation of this correlation.
pub fact_only: bool,
}
/// Correlates one materialized instruction fact with one authoritative Token-2022 snapshot.
pub fn correlate_token_2022_instruction_with_snapshot(
correlation_key: &str,
account: &ks_lib::MdPubkey,
instruction_output: &ks_lib::MtApiMaterializedOutput,
snapshot: &crate::Token2022StatefulSnapshotBundle,
) -> ks_core::Result<Token2022StateCorrelationReport> {
if correlation_key.trim().is_empty() {
return std::result::Result::Err(ks_core::Error::config(
"Token-2022 correlation key must not be empty",
));
}
if snapshot.account_key != account.0 {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_correlation_account_mismatch",
format!(
"Token-2022 correlation expected account {}, got snapshot {}",
account.0, snapshot.account_key
),
));
}
let fact_code = match instruction_output.payload_json.get("riskKind") {
std::option::Option::Some(value) => value.as_str().unwrap_or("").to_string(),
std::option::Option::None => instruction_output
.payload_json
.get("operation")
.and_then(serde_json::Value::as_str)
.unwrap_or("")
.to_string(),
};
if fact_code.is_empty() {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_correlation_fact_missing",
"Token-2022 correlation requires riskKind or operation in the instruction output",
));
}
let expected_extension = expected_extension(fact_code.as_str());
let extension_present = expected_extension.map(|extension| {
return snapshot.extension_names.iter().any(|candidate| return candidate == extension);
});
let status = match extension_present {
std::option::Option::Some(true) => Token2022CorrelationStatus::Confirmed,
std::option::Option::Some(false) => Token2022CorrelationStatus::Contradicted,
std::option::Option::None => Token2022CorrelationStatus::NotApplicable,
};
return std::result::Result::Ok(Token2022StateCorrelationReport {
correlation_key: correlation_key.to_string(),
account: account.clone(),
fact_code,
expected_extension: expected_extension.map(str::to_string),
status,
snapshot_slot: snapshot.slot,
extension_present,
fact_only: true,
});
}
fn expected_extension(fact_code: &str) -> std::option::Option<&'static str> {
return match fact_code {
"default_account_state_configured"
| "initialize_default_account_state"
| "update_default_account_state" => std::option::Option::Some("default_account_state"),
"required_transfer_memos_enabled"
| "required_transfer_memos_disabled"
| "enable_required_transfer_memos"
| "disable_required_transfer_memos" => std::option::Option::Some("memo_transfer"),
"cpi_guard_enabled" | "cpi_guard_disabled" | "enable_cpi_guard" | "disable_cpi_guard" => {
std::option::Option::Some("cpi_guard")
},
"non_transferable_mint_configured" | "initialize_non_transferable_mint" => {
std::option::Option::Some("non_transferable")
},
"pausable_mint_configured"
| "mint_activity_paused"
| "mint_activity_resumed"
| "initialize_pausable_config"
| "pause"
| "resume" => std::option::Option::Some("pausable"),
"permissioned_burn_configured" | "initialize_permissioned_burn" => {
std::option::Option::Some("permissioned_burn")
},
"confidential_credits_enabled"
| "confidential_credits_disabled"
| "non_confidential_credits_enabled"
| "non_confidential_credits_disabled"
| "enable_confidential_credits"
| "disable_confidential_credits"
| "enable_non_confidential_credits"
| "disable_non_confidential_credits" => {
std::option::Option::Some("confidential_transfer_account")
},
"initialize_transfer_fee_config"
| "set_transfer_fee"
| "withdraw_withheld_tokens_from_mint"
| "harvest_withheld_tokens_to_mint" => std::option::Option::Some("transfer_fee_config"),
"withdraw_withheld_tokens_from_accounts" | "transfer_checked_with_fee" => {
std::option::Option::Some("transfer_fee_amount")
},
"initialize_confidential_transfer_fee_config"
| "withdraw_confidential_withheld_tokens_from_mint"
| "harvest_confidential_withheld_tokens_to_mint"
| "enable_confidential_harvest_to_mint"
| "disable_confidential_harvest_to_mint" => {
std::option::Option::Some("confidential_transfer_fee_config")
},
"withdraw_confidential_withheld_tokens_from_accounts"
| "transfer_confidential_tokens_with_fee" => {
std::option::Option::Some("confidential_transfer_fee_amount")
},
_ => std::option::Option::None,
};
}
#[cfg(test)]
mod tests {
fn snapshot(extension_names: &[&str]) -> crate::Token2022StatefulSnapshotBundle {
return crate::Token2022StatefulSnapshotBundle {
account_key: solana_pubkey::Pubkey::new_from_array([7u8; 32]).to_string(),
slot: 42,
state_kind: "mint".to_string(),
extension_names: extension_names
.iter()
.map(|value| return (*value).to_string())
.collect(),
outputs: std::vec::Vec::new(),
};
}
fn output(field: &str, value: &str) -> ks_lib::MtApiMaterializedOutput {
let mut payload = serde_json::Map::new();
payload.insert(field.to_string(), serde_json::Value::String(value.to_string()));
return ks_lib::MtApiMaterializedOutput {
output_key: "fact:0".to_string(),
family: ks_lib::MdMaterializedEventFamily::Risk,
payload_json: serde_json::Value::Object(payload),
};
}
#[test]
fn known_fact_is_confirmed_or_contradicted_by_the_final_extension_inventory() {
let snapshot = snapshot(&["pausable"]);
let account = ks_lib::MdPubkey(snapshot.account_key.clone());
let confirmed = super::correlate_token_2022_instruction_with_snapshot(
"corr:pause",
&account,
&output("riskKind", "mint_activity_paused"),
&snapshot,
);
assert!(confirmed.is_ok());
if let std::result::Result::Ok(confirmed) = confirmed {
assert_eq!(confirmed.status, super::Token2022CorrelationStatus::Confirmed);
assert_eq!(confirmed.extension_present, std::option::Option::Some(true));
}
let contradicted = super::correlate_token_2022_instruction_with_snapshot(
"corr:fee",
&account,
&output("operation", "initialize_transfer_fee_config"),
&snapshot,
);
assert!(contradicted.is_ok());
if let std::result::Result::Ok(contradicted) = contradicted {
assert_eq!(contradicted.status, super::Token2022CorrelationStatus::Contradicted);
assert_eq!(contradicted.extension_present, std::option::Option::Some(false));
}
}
#[test]
fn unsupported_fact_is_explicitly_not_applicable_without_inventing_a_conclusion() {
let snapshot = snapshot(&[]);
let account = ks_lib::MdPubkey(snapshot.account_key.clone());
let report = super::correlate_token_2022_instruction_with_snapshot(
"corr:unknown",
&account,
&output("operation", "unknown_future_operation"),
&snapshot,
);
assert!(report.is_ok());
if let std::result::Result::Ok(report) = report {
assert_eq!(report.status, super::Token2022CorrelationStatus::NotApplicable);
assert_eq!(report.extension_present, std::option::Option::None);
assert!(report.fact_only);
}
}
#[test]
fn correlation_identity_and_account_mismatch_fail_closed() {
let snapshot = snapshot(&["memo_transfer"]);
let account = ks_lib::MdPubkey(snapshot.account_key.clone());
let empty = super::correlate_token_2022_instruction_with_snapshot(
"",
&account,
&output("riskKind", "required_transfer_memos_enabled"),
&snapshot,
);
assert!(empty.is_err());
let wrong = ks_lib::MdPubkey(solana_pubkey::Pubkey::new_from_array([8u8; 32]).to_string());
let mismatch = super::correlate_token_2022_instruction_with_snapshot(
"corr:mismatch",
&wrong,
&output("riskKind", "required_transfer_memos_enabled"),
&snapshot,
);
assert!(mismatch.is_err());
}
}