137 lines
6.0 KiB
Rust
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());
|
|
}
|
|
}
|