v0.4.7-pre.007
This commit is contained in:
136
kb-pipeline/src/solana_metaplex_token_metadata_preflight.rs
Normal file
136
kb-pipeline/src/solana_metaplex_token_metadata_preflight.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user