Files
khadhroony-bot3/kb-pipeline/src/solana_metaplex_token_metadata_preflight.rs
2026-08-02 11:46:25 +02:00

137 lines
6.0 KiB
Rust

// file: kb-pipeline/src/solana_metaplex_token_metadata_preflight.rs
// version: 2
//! Stateful Metaplex Token Metadata preflight contracts.
/// Maximum number of correlated Metaplex snapshots accepted by one preflight.
pub const MAX_METAPLEX_TOKEN_METADATA_PREFLIGHT_ACCOUNTS: usize = 32;
/// Complete preflight request for one prepared Metaplex execution plan.
#[derive(Clone, Debug, PartialEq)]
pub struct MetaplexTokenMetadataPreflightRequest {
/// Exact prepared plan produced by `kb-lib`.
pub plan: kb_lib::ExApiPreparedExecutionPlan,
/// Confirmed bounded state snapshots correlated with the plan.
pub snapshots: std::vec::Vec<crate::MetaplexTokenMetadataStatefulReadResult>,
/// Whether deprecated execution was explicitly approved by the operator.
pub allow_deprecated_operation: bool,
}
/// Deterministic Metaplex preflight report.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct MetaplexTokenMetadataPreflightReport {
/// Stable operation code.
pub operation_code: std::string::String,
/// Highest confirmed context slot across supplied snapshots.
pub context_slot: u64,
/// Canonical accounts inspected by the preflight.
pub inspected_accounts: std::vec::Vec<kb_lib::MdPubkey>,
/// Ordered successful checks.
pub checks: std::vec::Vec<std::string::String>,
}
/// Validates program ownership, snapshot bounds, account correlation and deprecation policy.
pub fn inspect_metaplex_token_metadata_preflight(
request: &crate::MetaplexTokenMetadataPreflightRequest,
) -> kb_core::Result<crate::MetaplexTokenMetadataPreflightReport> {
if request.snapshots.len() > crate::MAX_METAPLEX_TOKEN_METADATA_PREFLIGHT_ACCOUNTS {
return std::result::Result::Err(kb_core::Error::new(
"metaplex_preflight_account_limit_exceeded",
"Metaplex preflight account limit exceeded",
));
}
if request.plan.instructions.is_empty() {
return std::result::Result::Err(kb_core::Error::new(
"metaplex_preflight_empty_plan",
"Metaplex execution plan must contain at least one instruction",
));
}
for instruction in &request.plan.instructions {
if instruction.program_id.0 != kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID {
return std::result::Result::Err(kb_core::Error::new(
"metaplex_preflight_program_mismatch",
"Every Metaplex plan instruction must target Token Metadata",
));
}
if instruction.operation_code != request.plan.operation_code {
return std::result::Result::Err(kb_core::Error::new(
"metaplex_preflight_operation_mismatch",
"Metaplex plan and instruction operation codes must match",
));
}
}
let deprecated = kb_lib::EX_METAPLEX_TOKEN_METADATA_DEPRECATED_OPERATION_CODES
.contains(&request.plan.operation_code.as_str());
if deprecated && !request.allow_deprecated_operation {
return std::result::Result::Err(kb_core::Error::new(
"metaplex_preflight_deprecated_operation_not_approved",
"Deprecated Metaplex execution requires explicit operator approval",
));
}
let mut inspected = std::collections::BTreeMap::<std::string::String, kb_lib::MdPubkey>::new();
let mut context_slot = 0_u64;
for snapshot in &request.snapshots {
if snapshot.commitment != "confirmed" {
return std::result::Result::Err(kb_core::Error::new(
"metaplex_preflight_commitment_mismatch",
"Metaplex stateful snapshots must use confirmed commitment",
));
}
context_slot = context_slot.max(snapshot.context_slot);
inspected
.entry(snapshot.snapshot.account.0.clone())
.or_insert_with(|| return snapshot.snapshot.account.clone());
}
return std::result::Result::Ok(crate::MetaplexTokenMetadataPreflightReport {
operation_code: request.plan.operation_code.clone(),
context_slot,
inspected_accounts: inspected.into_values().collect(),
checks: vec![
"program_id_exact".to_string(),
"operation_code_exact".to_string(),
"confirmed_state_snapshots".to_string(),
"deprecated_policy_explicit".to_string(),
"account_correlation_bounded".to_string(),
],
});
}
#[cfg(test)]
mod tests {
#[test]
fn rejects_empty_plans_and_unapproved_deprecated_operations() {
let mut plan = kb_lib::ExApiPreparedExecutionPlan {
executor_name: "kb-lib".to_string(),
executor_version: "0".to_string(),
intent_id: "i".to_string(),
operation_code: kb_lib::EX_METAPLEX_TOKEN_METADATA_PUFF_METADATA_OPERATION.to_string(),
fee_payer: kb_lib::MdPubkey("11111111111111111111111111111111".to_string()),
instructions: vec![],
required_signers: vec![],
policy: std::default::Default::default(),
requested_spend_lamports: 0,
requested_compute_unit_price_micro_lamports: std::option::Option::None,
};
let empty = crate::MetaplexTokenMetadataPreflightRequest {
plan: plan.clone(),
snapshots: vec![],
allow_deprecated_operation: false,
};
assert!(crate::inspect_metaplex_token_metadata_preflight(&empty).is_err());
plan.instructions.push(kb_lib::ExApiPlannedInstruction {
program_id: kb_lib::MdProgramId(
kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID.to_string(),
),
operation_code: plan.operation_code.clone(),
accounts: vec![],
data: vec![14],
});
let deprecated = crate::MetaplexTokenMetadataPreflightRequest {
plan,
snapshots: vec![],
allow_deprecated_operation: false,
};
assert!(crate::inspect_metaplex_token_metadata_preflight(&deprecated).is_err());
}
}