0.5.1-pre.002

This commit is contained in:
2026-08-09 19:34:08 +02:00
parent 816eee59a9
commit 6a680767ae
767 changed files with 12257 additions and 12195 deletions

1984
ks-pipeline/src/backfill.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
// file: ks-pipeline/src/constants.rs
// version: 3
//! Pipeline constants.
/// Canonical tracing target for pipeline orchestration.
pub(crate) const TRACING_TARGET: &str = "ks-pipeline";

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

321
ks-pipeline/src/lib.rs Normal file
View File

@@ -0,0 +1,321 @@
// file: ks-pipeline/src/lib.rs
// version: 22
#![forbid(unsafe_code)]
#![deny(unreachable_pub)]
#![warn(missing_docs)]
//! Pipeline orchestration boundary.
mod backfill;
mod constants;
mod core_extraction;
mod decode_replay;
mod metadata_metaplex_token_metadata_execution_orchestration;
mod metadata_metaplex_token_metadata_preflight;
mod metadata_metaplex_token_metadata_stateful;
mod metadata_solana_program_execution_orchestration;
mod metadata_solana_program_preflight;
mod metadata_solana_program_stateful;
mod plan;
mod solana_stateful;
mod spl_ata_stateful;
mod spl_elgamal_registry_stateful;
mod spl_token_2022_correlation;
mod spl_token_2022_crypto_preflight;
mod spl_token_2022_execution_orchestration;
mod spl_token_2022_metadata;
mod spl_token_2022_preflight;
mod spl_token_2022_proof_orchestration;
mod spl_token_2022_stateful;
mod spl_token_stateful;
/// Address category used by one targeted backfill campaign.
pub use self::backfill::BackfillAddressKind;
/// Chronological direction relative to one anchor signature.
pub use self::backfill::BackfillDirection;
/// Observer notified during HTTP backfill campaigns.
pub use self::backfill::BackfillObserver;
/// Progress event emitted by HTTP backfill campaigns.
pub use self::backfill::BackfillProgressEvent;
/// Severity of one HTTP backfill progress event.
pub use self::backfill::BackfillProgressLevel;
/// Complete bounded HTTP backfill request.
pub use self::backfill::BackfillRequest;
/// Candidate source used by one HTTP backfill campaign.
pub use self::backfill::BackfillSource;
/// Summary returned by one HTTP backfill campaign.
pub use self::backfill::BackfillSummary;
/// Executes one bounded HTTP transaction backfill campaign.
pub use self::backfill::execute_http_backfill;
/// Stable core extraction processor name.
pub use self::core_extraction::CORE_EXTRACTION_PROCESSOR_NAME;
/// Stable core extraction processor version.
pub use self::core_extraction::CORE_EXTRACTION_PROCESSOR_VERSION;
/// Stable core extraction stage code.
pub use self::core_extraction::CORE_EXTRACTION_STAGE;
/// Observer notified during core extraction campaigns.
pub use self::core_extraction::CoreExtractionObserver;
/// Progress event emitted by core extraction campaigns.
pub use self::core_extraction::CoreExtractionProgressEvent;
/// Severity of one core extraction progress event.
pub use self::core_extraction::CoreExtractionProgressLevel;
/// Bounded core extraction request.
pub use self::core_extraction::CoreExtractionRequest;
/// Source selection for a core extraction campaign.
pub use self::core_extraction::CoreExtractionSource;
/// Summary returned by a core extraction campaign.
pub use self::core_extraction::CoreExtractionSummary;
/// Executes one bounded core extraction campaign.
pub use self::core_extraction::execute_core_extraction;
/// Extracts one canonical raw transaction into normalized core rows.
pub use self::core_extraction::extract_raw_transaction_to_core;
/// Current common decode pipeline orchestration version.
pub use self::decode_replay::DECODE_PIPELINE_VERSION;
/// Deterministic decoder dispatch policy.
pub use self::decode_replay::DecodeDispatchPolicy;
/// Per-decoder terminal counters.
pub use self::decode_replay::DecodeProcessorSummary;
/// Decode replay observer contract.
pub use self::decode_replay::DecodeReplayObserver;
/// Decode replay progress event.
pub use self::decode_replay::DecodeReplayProgressEvent;
/// Decode replay progress severity.
pub use self::decode_replay::DecodeReplayProgressLevel;
/// Bounded contextual decode replay request.
pub use self::decode_replay::DecodeReplayRequest;
/// Final contextual decode replay summary.
pub use self::decode_replay::DecodeReplaySummary;
/// Stable materialization processing stage.
pub use self::decode_replay::EVENT_MATERIALIZATION_STAGE;
/// Stable instruction decode processing stage.
pub use self::decode_replay::INSTRUCTION_DECODE_STAGE;
/// Executes one contextual decode replay campaign.
pub use self::decode_replay::execute_decode_replay;
/// Creates one stable process-local decode campaign identifier.
pub use self::decode_replay::new_decode_campaign_id;
/// Maximum number of resolved signers accepted by one Metaplex execution envelope.
pub use self::metadata_metaplex_token_metadata_execution_orchestration::MAX_METAPLEX_TOKEN_METADATA_EXECUTION_SIGNERS;
/// Deterministic Metaplex execution-readiness report.
pub use self::metadata_metaplex_token_metadata_execution_orchestration::MetaplexTokenMetadataExecutionReadinessReport;
/// Complete Metaplex execution-readiness request.
pub use self::metadata_metaplex_token_metadata_execution_orchestration::MetaplexTokenMetadataExecutionReadinessRequest;
/// Result of one Metaplex stateful postcondition.
pub use self::metadata_metaplex_token_metadata_execution_orchestration::MetaplexTokenMetadataPostconditionStatus;
/// Aggregates Metaplex postconditions without inventing success.
pub use self::metadata_metaplex_token_metadata_execution_orchestration::summarize_metaplex_token_metadata_postconditions;
/// Validates the complete Metaplex execution envelope before signing.
pub use self::metadata_metaplex_token_metadata_execution_orchestration::validate_metaplex_token_metadata_execution_readiness;
/// Maximum number of correlated Metaplex snapshots accepted by one preflight.
pub use self::metadata_metaplex_token_metadata_preflight::MAX_METAPLEX_TOKEN_METADATA_PREFLIGHT_ACCOUNTS;
/// Deterministic Metaplex preflight report.
pub use self::metadata_metaplex_token_metadata_preflight::MetaplexTokenMetadataPreflightReport;
/// Complete Metaplex preflight request.
pub use self::metadata_metaplex_token_metadata_preflight::MetaplexTokenMetadataPreflightRequest;
/// Inspects one prepared Metaplex plan against confirmed state snapshots.
pub use self::metadata_metaplex_token_metadata_preflight::inspect_metaplex_token_metadata_preflight;
/// Maximum complete Metaplex account data accepted by one RPC read.
pub use self::metadata_metaplex_token_metadata_stateful::MAX_METAPLEX_TOKEN_METADATA_ACCOUNT_BYTES;
/// Supported Metaplex account category for one stateful read.
pub use self::metadata_metaplex_token_metadata_stateful::MetaplexTokenMetadataAccountKind;
/// One bounded Metaplex stateful read request.
pub use self::metadata_metaplex_token_metadata_stateful::MetaplexTokenMetadataStatefulReadRequest;
/// One bounded Metaplex stateful read result.
pub use self::metadata_metaplex_token_metadata_stateful::MetaplexTokenMetadataStatefulReadResult;
/// Canonical bounded Metaplex state snapshot.
pub use self::metadata_metaplex_token_metadata_stateful::MetaplexTokenMetadataStatefulSnapshot;
/// Validates one RPC account response and delegates parsing to `ks-lib`.
pub use self::metadata_metaplex_token_metadata_stateful::materialize_metaplex_token_metadata_account_info_result;
/// Reads one bounded Metaplex Token Metadata account.
pub use self::metadata_metaplex_token_metadata_stateful::read_metaplex_token_metadata_stateful_snapshot;
/// Maximum number of resolved signers accepted by one Program Metadata execution envelope.
pub use self::metadata_solana_program_execution_orchestration::MAX_SOLANA_PROGRAM_METADATA_EXECUTION_SIGNERS;
/// Complete Program Metadata execution-readiness report.
pub use self::metadata_solana_program_execution_orchestration::SolanaProgramMetadataExecutionReadinessReport;
/// Complete Program Metadata execution-readiness request.
pub use self::metadata_solana_program_execution_orchestration::SolanaProgramMetadataExecutionReadinessRequest;
/// Complete Program Metadata post-execution report.
pub use self::metadata_solana_program_execution_orchestration::SolanaProgramMetadataPostExecutionReport;
/// Complete Program Metadata post-execution request.
pub use self::metadata_solana_program_execution_orchestration::SolanaProgramMetadataPostExecutionRequest;
/// One Program Metadata postcondition.
pub use self::metadata_solana_program_execution_orchestration::SolanaProgramMetadataPostcondition;
/// Program Metadata postcondition status.
pub use self::metadata_solana_program_execution_orchestration::SolanaProgramMetadataPostconditionStatus;
/// Inspects confirmed Program Metadata post-execution state.
pub use self::metadata_solana_program_execution_orchestration::inspect_solana_program_metadata_post_execution;
/// Aggregates Program Metadata postconditions without invented success.
pub use self::metadata_solana_program_execution_orchestration::summarize_solana_program_metadata_postconditions;
/// Validates the complete Program Metadata execution envelope before signing.
pub use self::metadata_solana_program_execution_orchestration::validate_solana_program_metadata_execution_readiness;
/// Maximum number of bounded account snapshots accepted by one Program Metadata preflight.
pub use self::metadata_solana_program_preflight::MAX_SOLANA_PROGRAM_METADATA_PREFLIGHT_ACCOUNTS;
/// Deterministic Program Metadata preflight report.
pub use self::metadata_solana_program_preflight::SolanaProgramMetadataPreflightReport;
/// Complete Program Metadata preflight request.
pub use self::metadata_solana_program_preflight::SolanaProgramMetadataPreflightRequest;
/// One Program Metadata rent-exemption observation.
pub use self::metadata_solana_program_preflight::SolanaProgramMetadataRentObservation;
/// Inspects one Program Metadata plan against confirmed state.
pub use self::metadata_solana_program_preflight::inspect_solana_program_metadata_preflight;
/// Maximum complete Program Metadata account data accepted by one RPC read.
pub use self::metadata_solana_program_stateful::MAX_SOLANA_PROGRAM_METADATA_STATEFUL_ACCOUNT_BYTES;
/// Expected Program Metadata account state.
pub use self::metadata_solana_program_stateful::SolanaProgramMetadataExpectedAccountState;
/// Observed Program Metadata account state.
pub use self::metadata_solana_program_stateful::SolanaProgramMetadataObservedAccountState;
/// One bounded Program Metadata stateful read request.
pub use self::metadata_solana_program_stateful::SolanaProgramMetadataStatefulReadRequest;
/// One bounded Program Metadata stateful read result.
pub use self::metadata_solana_program_stateful::SolanaProgramMetadataStatefulReadResult;
/// Validates one Program Metadata RPC response and materializes its account state.
pub use self::metadata_solana_program_stateful::materialize_solana_program_metadata_account_info_result;
/// Reads one bounded Program Metadata account.
pub use self::metadata_solana_program_stateful::read_solana_program_metadata_stateful_snapshot;
/// Pipeline stage identifier.
pub use self::plan::PipelineStage;
/// Replay selection scope.
pub use self::plan::ReplayScope;
/// One machine-readable native Solana stateful readiness check.
pub use self::solana_stateful::SolanaCoreStatefulCheck;
/// One contextual fact measured during native Solana stateful readiness.
pub use self::solana_stateful::SolanaCoreStatefulFact;
/// Native Solana stateful readiness report.
pub use self::solana_stateful::SolanaCoreStatefulReadinessReport;
/// Native Solana stateful readiness request.
pub use self::solana_stateful::SolanaCoreStatefulReadinessRequest;
/// Native Solana stateful readiness status.
pub use self::solana_stateful::SolanaCoreStatefulReadinessStatus;
/// Inspects state required before simulating one native Solana operation.
pub use self::solana_stateful::inspect_solana_core_stateful_readiness;
/// Stateful invariants observed after one confirmed Associated Token Account execution.
pub use self::spl_ata_stateful::SplAssociatedTokenAccountPostExecutionReport;
/// One machine-readable Associated Token Account stateful check.
pub use self::spl_ata_stateful::SplAssociatedTokenAccountStatefulCheck;
/// One contextual Associated Token Account stateful fact.
pub use self::spl_ata_stateful::SplAssociatedTokenAccountStatefulFact;
/// Complete Associated Token Account stateful readiness report.
pub use self::spl_ata_stateful::SplAssociatedTokenAccountStatefulReadinessReport;
/// Complete request for one Associated Token Account stateful readiness inspection.
pub use self::spl_ata_stateful::SplAssociatedTokenAccountStatefulReadinessRequest;
/// Associated Token Account stateful readiness outcome.
pub use self::spl_ata_stateful::SplAssociatedTokenAccountStatefulReadinessStatus;
/// Verifies final Associated Token Account relationships after confirmed execution.
pub use self::spl_ata_stateful::inspect_spl_associated_token_account_post_execution;
/// Inspects state required before simulating one Associated Token Account operation.
pub use self::spl_ata_stateful::inspect_spl_associated_token_account_stateful_readiness;
/// Migrated ELGAMAL_REGISTRY_STATEFUL_ACCOUNT_BYTES contract.
pub use self::spl_elgamal_registry_stateful::ELGAMAL_REGISTRY_STATEFUL_ACCOUNT_BYTES;
/// Migrated ElGamalRegistryStatefulReadRequest contract.
pub use self::spl_elgamal_registry_stateful::ElGamalRegistryStatefulReadRequest;
/// Migrated ElGamalRegistryStatefulReadResult contract.
pub use self::spl_elgamal_registry_stateful::ElGamalRegistryStatefulReadResult;
/// Migrated ElGamalRegistryStatefulSnapshot contract.
pub use self::spl_elgamal_registry_stateful::ElGamalRegistryStatefulSnapshot;
/// Migrated materialize_elgamal_registry_account_info_result contract.
pub use self::spl_elgamal_registry_stateful::materialize_elgamal_registry_account_info_result;
/// Migrated materialize_elgamal_registry_stateful_snapshot contract.
pub use self::spl_elgamal_registry_stateful::materialize_elgamal_registry_stateful_snapshot;
/// Migrated read_elgamal_registry_stateful_snapshot contract.
pub use self::spl_elgamal_registry_stateful::read_elgamal_registry_stateful_snapshot;
/// Correlation outcome between one committed instruction fact and one final state snapshot.
pub use self::spl_token_2022_correlation::Token2022CorrelationStatus;
/// Deterministic correlation report for one instruction output and one final snapshot.
pub use self::spl_token_2022_correlation::Token2022StateCorrelationReport;
/// Correlates one materialized instruction fact with one authoritative Token-2022 snapshot.
pub use self::spl_token_2022_correlation::correlate_token_2022_instruction_with_snapshot;
/// Maximum number of distinct proof context-state accounts accepted by one preflight.
pub use self::spl_token_2022_crypto_preflight::MAX_TOKEN_2022_PROOF_CONTEXTS;
/// Token-2022 cryptographic preflight report.
pub use self::spl_token_2022_crypto_preflight::Token2022CryptographicPreflightReport;
/// Token-2022 cryptographic preflight request.
pub use self::spl_token_2022_crypto_preflight::Token2022CryptographicPreflightRequest;
/// One validated proof context report.
pub use self::spl_token_2022_crypto_preflight::Token2022ProofContextReport;
/// One required proof context-state account.
pub use self::spl_token_2022_crypto_preflight::Token2022ProofContextRequirement;
/// Encoded proof context metadata bytes.
pub use self::spl_token_2022_crypto_preflight::ZK_PROOF_CONTEXT_META_BYTES;
/// Inspects bounded Token-2022 cryptographic proof contexts.
pub use self::spl_token_2022_crypto_preflight::inspect_token_2022_cryptographic_preflight;
/// Maximum number of distinct signers accepted by one Token-2022 execution envelope.
pub use self::spl_token_2022_execution_orchestration::MAX_TOKEN_2022_EXECUTION_SIGNERS;
/// One explicit stateful postcondition retained after Token-2022 execution.
pub use self::spl_token_2022_execution_orchestration::Token2022ExecutionPostcondition;
/// Result of one stateful postcondition after Token-2022 execution.
pub use self::spl_token_2022_execution_orchestration::Token2022ExecutionPostconditionStatus;
/// Deterministic Token-2022 execution-readiness report.
pub use self::spl_token_2022_execution_orchestration::Token2022ExecutionReadinessReport;
/// Complete deterministic Token-2022 execution-readiness request.
pub use self::spl_token_2022_execution_orchestration::Token2022ExecutionReadinessRequest;
/// Aggregates Token-2022 postconditions without inventing success.
pub use self::spl_token_2022_execution_orchestration::summarize_token_2022_postconditions;
/// Validates the complete Token-2022 execution envelope before signing.
pub use self::spl_token_2022_execution_orchestration::validate_token_2022_execution_readiness;
/// Maximum Token Metadata bytes accepted from Solana return data.
pub use self::spl_token_2022_metadata::MAX_TOKEN_2022_METADATA_EMIT_BYTES;
/// Bounded evidence extracted from one successful Token Metadata `Emit` simulation.
pub use self::spl_token_2022_metadata::Token2022MetadataEmitEvidence;
/// Checks the final embedded metadata authority against one authoritative snapshot.
pub use self::spl_token_2022_metadata::inspect_token_2022_metadata_authority_postcondition;
/// Extracts and validates Token Metadata return data from one exact simulation.
pub use self::spl_token_2022_metadata::inspect_token_2022_metadata_emit_simulation;
/// Validates one Token Metadata `Emit` range against Solana return-data bounds.
pub use self::spl_token_2022_metadata::validate_token_2022_metadata_emit_range;
/// Migrated MAX_TOKEN_2022_PREFLIGHT_ACCOUNTS contract.
pub use self::spl_token_2022_preflight::MAX_TOKEN_2022_PREFLIGHT_ACCOUNTS;
/// Migrated MAX_TOKEN_2022_PREFLIGHT_TOTAL_BYTES contract.
pub use self::spl_token_2022_preflight::MAX_TOKEN_2022_PREFLIGHT_TOTAL_BYTES;
/// Migrated Token2022PreflightAccountReport contract.
pub use self::spl_token_2022_preflight::Token2022PreflightAccountReport;
/// Migrated Token2022PreflightReport contract.
pub use self::spl_token_2022_preflight::Token2022PreflightReport;
/// Migrated Token2022PreflightRequest contract.
pub use self::spl_token_2022_preflight::Token2022PreflightRequest;
/// Migrated Token2022PreflightRequirement contract.
pub use self::spl_token_2022_preflight::Token2022PreflightRequirement;
/// Migrated inspect_token_2022_preflight contract.
pub use self::spl_token_2022_preflight::inspect_token_2022_preflight;
/// Maximum number of proofs accepted for one Token-2022 operation.
pub use self::spl_token_2022_proof_orchestration::MAX_TOKEN_2022_OPERATION_PROOFS;
/// Token-2022 proof orchestration report.
pub use self::spl_token_2022_proof_orchestration::Token2022ProofOrchestrationReport;
/// Token-2022 proof orchestration request.
pub use self::spl_token_2022_proof_orchestration::Token2022ProofOrchestrationRequest;
/// Validates mixed inline and context-state proof orchestration.
pub use self::spl_token_2022_proof_orchestration::orchestrate_token_2022_proofs;
/// Migrated MAX_TOKEN_2022_STATEFUL_ACCOUNT_BYTES contract.
pub use self::spl_token_2022_stateful::MAX_TOKEN_2022_STATEFUL_ACCOUNT_BYTES;
/// Migrated Token2022StatefulContext contract.
pub use self::spl_token_2022_stateful::Token2022StatefulContext;
/// Migrated Token2022StatefulReadRequest contract.
pub use self::spl_token_2022_stateful::Token2022StatefulReadRequest;
/// Migrated Token2022StatefulReadResult contract.
pub use self::spl_token_2022_stateful::Token2022StatefulReadResult;
/// Migrated Token2022StatefulSnapshotBundle contract.
pub use self::spl_token_2022_stateful::Token2022StatefulSnapshotBundle;
/// Migrated materialize_parsed_token_2022_stateful_snapshot contract.
pub use self::spl_token_2022_stateful::materialize_parsed_token_2022_stateful_snapshot;
/// Migrated materialize_parsed_token_2022_stateful_snapshot_with_context contract.
pub use self::spl_token_2022_stateful::materialize_parsed_token_2022_stateful_snapshot_with_context;
/// Migrated materialize_token_2022_account_info_result contract.
pub use self::spl_token_2022_stateful::materialize_token_2022_account_info_result;
/// Migrated materialize_token_2022_stateful_snapshot contract.
pub use self::spl_token_2022_stateful::materialize_token_2022_stateful_snapshot;
/// Migrated read_token_2022_stateful_snapshot contract.
pub use self::spl_token_2022_stateful::read_token_2022_stateful_snapshot;
/// One machine-readable classic SPL Token stateful check.
pub use self::spl_token_stateful::SplTokenStatefulCheck;
/// One contextual classic SPL Token stateful fact.
pub use self::spl_token_stateful::SplTokenStatefulFact;
/// Complete classic SPL Token stateful readiness report.
pub use self::spl_token_stateful::SplTokenStatefulReadinessReport;
/// Complete request for one classic SPL Token stateful readiness inspection.
pub use self::spl_token_stateful::SplTokenStatefulReadinessRequest;
/// Classic SPL Token stateful readiness outcome.
pub use self::spl_token_stateful::SplTokenStatefulReadinessStatus;
/// Inspects Localnet or Devnet classic SPL Token state required before simulation.
pub use self::spl_token_stateful::inspect_spl_token_stateful_readiness;
/// Canonical tracing target for pipeline orchestration.
pub(crate) use self::constants::TRACING_TARGET;

View File

@@ -0,0 +1,240 @@
// file: ks-pipeline/src/metadata_metaplex_token_metadata_execution_orchestration.rs
// version: 4
//! Simulation-first Metaplex Token Metadata execution orchestration.
/// Maximum number of resolved signers accepted by one Metaplex execution envelope.
pub const MAX_METAPLEX_TOKEN_METADATA_EXECUTION_SIGNERS: usize = 32;
/// Complete request checked before signing or submitting one Metaplex transaction.
#[derive(Clone, Debug, PartialEq)]
pub struct MetaplexTokenMetadataExecutionReadinessRequest {
/// Exact prepared execution plan.
pub plan: ks_lib::ExApiPreparedExecutionPlan,
/// Successful stateful preflight report.
pub preflight: crate::MetaplexTokenMetadataPreflightReport,
/// Exact compiled message hash.
pub message_hash: std::string::String,
/// Hash retained by simulation evidence.
pub simulated_message_hash: std::string::String,
/// Whether simulation was performed.
pub simulated: bool,
/// Whether simulation succeeded.
pub simulation_succeeded: bool,
/// Public keys available to sign.
pub resolved_signers: std::vec::Vec<ks_lib::MdPubkey>,
/// Whether submission was requested.
pub submit: bool,
/// Whether the operator confirmed submission.
pub operator_confirmed: bool,
}
/// Deterministic readiness report emitted before signing.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct MetaplexTokenMetadataExecutionReadinessReport {
/// Stable operation code.
pub operation_code: std::string::String,
/// Exact message hash bound to simulation.
pub message_hash: std::string::String,
/// Confirmed preflight context slot.
pub context_slot: u64,
/// Deduplicated required signers.
pub required_signers: std::vec::Vec<ks_lib::MdPubkey>,
/// Whether signing and submission are authorized.
pub send_authorized: bool,
/// Ordered readiness checks, including a probe-only failed-simulation marker when applicable.
pub checks: std::vec::Vec<std::string::String>,
}
/// Validates preflight, exact simulation evidence, signers and submission policies.
///
/// Failed simulations remain observable only for `submit=false`; submission always requires success.
pub fn validate_metaplex_token_metadata_execution_readiness(
request: &crate::MetaplexTokenMetadataExecutionReadinessRequest,
) -> ks_core::Result<crate::MetaplexTokenMetadataExecutionReadinessReport> {
if request.plan.operation_code != request.preflight.operation_code {
return std::result::Result::Err(ks_core::Error::new(
"metaplex_execution_operation_mismatch",
"Metaplex plan and preflight operation codes must match",
));
}
if request.message_hash.trim().is_empty()
|| request.message_hash != request.simulated_message_hash
|| !request.simulated
{
return std::result::Result::Err(ks_core::Error::new(
"metaplex_execution_simulation_required",
"Metaplex execution requires exact-message simulation evidence",
));
}
if request.submit && !request.simulation_succeeded {
return std::result::Result::Err(ks_core::Error::new(
"metaplex_execution_simulation_required",
"Metaplex submission requires successful exact-message simulation",
));
}
if request.resolved_signers.len() > crate::MAX_METAPLEX_TOKEN_METADATA_EXECUTION_SIGNERS {
return std::result::Result::Err(ks_core::Error::new(
"metaplex_execution_signer_limit_exceeded",
"Metaplex execution signer limit exceeded",
));
}
let resolved = request
.resolved_signers
.iter()
.map(|item| return item.0.clone())
.collect::<std::collections::BTreeSet<std::string::String>>();
let mut required = std::collections::BTreeMap::<std::string::String, ks_lib::MdPubkey>::new();
for signer in &request.plan.required_signers {
if !resolved.contains(signer.pubkey.0.as_str()) {
return std::result::Result::Err(ks_core::Error::new(
"metaplex_execution_signer_unresolved",
format!("Metaplex signer {} is unresolved", signer.pubkey.0),
));
}
required
.entry(signer.pubkey.0.clone())
.or_insert_with(|| return signer.pubkey.clone());
}
if request.submit && !request.operator_confirmed {
return std::result::Result::Err(ks_core::Error::new(
"metaplex_execution_confirmation_required",
"Metaplex submission requires explicit operator confirmation",
));
}
if request.submit && request.plan.policy.dry_run {
return std::result::Result::Err(ks_core::Error::new(
"metaplex_execution_dry_run_blocks_submission",
"Metaplex dry-run plan cannot be submitted",
));
}
return std::result::Result::Ok(crate::MetaplexTokenMetadataExecutionReadinessReport {
operation_code: request.plan.operation_code.clone(),
message_hash: request.message_hash.clone(),
context_slot: request.preflight.context_slot,
required_signers: required.into_values().collect(),
send_authorized: request.submit
&& request.operator_confirmed
&& request.simulation_succeeded
&& !request.plan.policy.dry_run,
checks: vec![
"preflight_bound_to_operation".to_string(),
if request.simulation_succeeded {
"simulation_bound_to_exact_message".to_string()
} else {
"failed_simulation_retained_for_probe_only".to_string()
},
"all_required_signers_resolved".to_string(),
"submission_policy_consistent".to_string(),
],
});
}
/// Result of one explicit postcondition.
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum MetaplexTokenMetadataPostconditionStatus {
/// Final state confirms the expected effect.
Confirmed,
/// Final state contradicts the expected effect.
Contradicted,
/// No stateful assertion applies.
NotApplicable,
}
/// Aggregates Metaplex postconditions without turning absence into success.
pub fn summarize_metaplex_token_metadata_postconditions(
statuses: &[crate::MetaplexTokenMetadataPostconditionStatus],
) -> crate::MetaplexTokenMetadataPostconditionStatus {
if statuses
.iter()
.any(|value| return *value == crate::MetaplexTokenMetadataPostconditionStatus::Contradicted)
{
return crate::MetaplexTokenMetadataPostconditionStatus::Contradicted;
}
if statuses
.iter()
.any(|value| return *value == crate::MetaplexTokenMetadataPostconditionStatus::Confirmed)
{
return crate::MetaplexTokenMetadataPostconditionStatus::Confirmed;
}
return crate::MetaplexTokenMetadataPostconditionStatus::NotApplicable;
}
#[cfg(test)]
mod tests {
#[test]
fn failed_simulation_is_reportable_only_without_submission() {
let fee_payer = ks_lib::MdPubkey("11111111111111111111111111111111".to_string());
let plan = ks_lib::ExApiPreparedExecutionPlan {
executor_name: "kb-lib.executor.metadata.metaplex_token_metadata".to_string(),
executor_version: "0.4.8".to_string(),
intent_id: "metaplex-failed-simulation-probe".to_string(),
operation_code: ks_lib::EX_METAPLEX_TOKEN_METADATA_USE_OPERATION.to_string(),
fee_payer: fee_payer.clone(),
instructions: vec![ks_lib::ExApiPlannedInstruction {
program_id: ks_lib::MdProgramId(
ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID.to_string(),
),
operation_code: ks_lib::EX_METAPLEX_TOKEN_METADATA_USE_OPERATION.to_string(),
accounts: vec![],
data: vec![51],
}],
required_signers: vec![],
policy: ks_lib::ExApiExecutionPolicy {
dry_run: false,
..ks_lib::ExApiExecutionPolicy::default()
},
requested_spend_lamports: 0,
requested_compute_unit_price_micro_lamports: std::option::Option::None,
};
let request = crate::MetaplexTokenMetadataExecutionReadinessRequest {
plan: plan.clone(),
preflight: crate::MetaplexTokenMetadataPreflightReport {
operation_code: plan.operation_code.clone(),
context_slot: 42,
inspected_accounts: vec![],
checks: vec![],
},
message_hash: "message-hash".to_string(),
simulated_message_hash: "message-hash".to_string(),
simulated: true,
simulation_succeeded: false,
resolved_signers: vec![],
submit: false,
operator_confirmed: false,
};
let report = crate::validate_metaplex_token_metadata_execution_readiness(&request);
assert!(report.is_ok());
if let std::result::Result::Ok(report) = report {
assert!(!report.send_authorized);
assert!(
report
.checks
.iter()
.any(|check| return check == "failed_simulation_retained_for_probe_only")
);
}
let mut submission = request;
submission.submit = true;
submission.operator_confirmed = true;
assert!(crate::validate_metaplex_token_metadata_execution_readiness(&submission).is_err());
}
#[test]
fn postconditions_never_convert_not_applicable_into_success() {
assert_eq!(
crate::summarize_metaplex_token_metadata_postconditions(&[
crate::MetaplexTokenMetadataPostconditionStatus::NotApplicable,
]),
crate::MetaplexTokenMetadataPostconditionStatus::NotApplicable,
);
assert_eq!(
crate::summarize_metaplex_token_metadata_postconditions(&[
crate::MetaplexTokenMetadataPostconditionStatus::Confirmed,
crate::MetaplexTokenMetadataPostconditionStatus::Contradicted,
]),
crate::MetaplexTokenMetadataPostconditionStatus::Contradicted,
);
}
}

View File

@@ -0,0 +1,136 @@
// file: ks-pipeline/src/metadata_metaplex_token_metadata_preflight.rs
// version: 4
//! 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 `ks-lib`.
pub plan: ks_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<ks_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,
) -> ks_core::Result<crate::MetaplexTokenMetadataPreflightReport> {
if request.snapshots.len() > crate::MAX_METAPLEX_TOKEN_METADATA_PREFLIGHT_ACCOUNTS {
return std::result::Result::Err(ks_core::Error::new(
"metaplex_preflight_account_limit_exceeded",
"Metaplex preflight account limit exceeded",
));
}
if request.plan.instructions.is_empty() {
return std::result::Result::Err(ks_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 != ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID {
return std::result::Result::Err(ks_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(ks_core::Error::new(
"metaplex_preflight_operation_mismatch",
"Metaplex plan and instruction operation codes must match",
));
}
}
let deprecated = ks_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(ks_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, ks_lib::MdPubkey>::new();
let mut context_slot = 0_u64;
for snapshot in &request.snapshots {
if snapshot.commitment != "confirmed" {
return std::result::Result::Err(ks_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 = ks_lib::ExApiPreparedExecutionPlan {
executor_name: "ks-lib".to_string(),
executor_version: "0".to_string(),
intent_id: "i".to_string(),
operation_code: ks_lib::EX_METAPLEX_TOKEN_METADATA_PUFF_METADATA_OPERATION.to_string(),
fee_payer: ks_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(ks_lib::ExApiPlannedInstruction {
program_id: ks_lib::MdProgramId(
ks_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());
}
}

View File

@@ -0,0 +1,600 @@
// file: ks-pipeline/src/metadata_metaplex_token_metadata_stateful.rs
// version: 10
//! Bounded Metaplex Token Metadata account reads and canonical state projections.
/// Maximum complete Metaplex account data accepted by one RPC read.
///
/// This bound follows the complete `getAccountInfo` contract of the execution transport. Decoder
/// limits remain independent for already-available offline bytes or future chunked readers.
pub const MAX_METAPLEX_TOKEN_METADATA_ACCOUNT_BYTES: usize =
ks_onchain_transport::MAX_COMPLETE_ACCOUNT_DATA_BYTES;
/// Supported Metaplex account category for one bounded stateful read.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum MetaplexTokenMetadataAccountKind {
/// Metadata PDA derived from the mint encoded in the account.
Metadata,
/// Master edition or printed edition PDA derived from the supplied mint.
Edition {
/// Mint used to derive the edition PDA.
mint: ks_lib::MdPubkey,
},
/// Edition marker PDA derived from the master mint and printed edition number.
EditionMarker {
/// Master mint used to derive the edition marker PDA.
mint: ks_lib::MdPubkey,
/// Printed edition number whose marker bit must be decoded.
edition: u64,
},
/// Programmable token record PDA derived from mint and token account.
TokenRecord {
/// Mint used to derive the token-record PDA.
mint: ks_lib::MdPubkey,
/// Token account used to derive the token-record PDA.
token: ks_lib::MdPubkey,
},
/// Token Owned Escrow PDA whose authority seeds and bump are self-validated by the decoder.
TokenOwnedEscrow,
}
/// One bounded Metaplex account read request.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct MetaplexTokenMetadataStatefulReadRequest {
/// Endpoint role used for the HTTP RPC request.
pub query_role: std::string::String,
/// Canonical account address.
pub account: ks_lib::MdPubkey,
/// Expected account category and derivation inputs.
pub kind: crate::MetaplexTokenMetadataAccountKind,
/// Optional minimum RPC context slot.
pub min_context_slot: std::option::Option<u64>,
/// Maximum decoded account bytes accepted from the endpoint.
pub max_data_bytes: usize,
}
impl crate::MetaplexTokenMetadataStatefulReadRequest {
/// Validates the bounded complete-account RPC request.
pub fn validate(&self) -> ks_core::Result<()> {
if self.query_role.trim().is_empty() {
return std::result::Result::Err(ks_core::Error::config(
"Metaplex stateful read query_role must not be empty",
));
}
let account_result = ks_onchain_transport::validate_solana_pubkey_text(
self.account.0.as_str(),
"Metaplex stateful account",
);
if let std::result::Result::Err(error) = account_result {
return std::result::Result::Err(error);
}
let derivation_result = match &self.kind {
crate::MetaplexTokenMetadataAccountKind::Metadata => std::result::Result::Ok(()),
crate::MetaplexTokenMetadataAccountKind::Edition { mint }
| crate::MetaplexTokenMetadataAccountKind::EditionMarker { mint, .. } => {
ks_onchain_transport::validate_solana_pubkey_text(
mint.0.as_str(),
"Metaplex stateful derivation mint",
)
},
crate::MetaplexTokenMetadataAccountKind::TokenRecord { mint, token } => {
let mint_result = ks_onchain_transport::validate_solana_pubkey_text(
mint.0.as_str(),
"Metaplex stateful derivation mint",
);
if let std::result::Result::Err(error) = mint_result {
return std::result::Result::Err(error);
}
ks_onchain_transport::validate_solana_pubkey_text(
token.0.as_str(),
"Metaplex stateful derivation token",
)
},
crate::MetaplexTokenMetadataAccountKind::TokenOwnedEscrow => {
std::result::Result::Ok(())
},
};
if let std::result::Result::Err(error) = derivation_result {
return std::result::Result::Err(error);
}
if self.max_data_bytes == 0
|| self.max_data_bytes > crate::MAX_METAPLEX_TOKEN_METADATA_ACCOUNT_BYTES
{
return std::result::Result::Err(ks_core::Error::config(format!(
"Metaplex stateful max_data_bytes must be between 1 and {}",
crate::MAX_METAPLEX_TOKEN_METADATA_ACCOUNT_BYTES
)));
}
return std::result::Result::Ok(());
}
}
/// Canonical bounded Metaplex state snapshot.
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct MetaplexTokenMetadataStatefulSnapshot {
/// Canonical account address.
pub account: ks_lib::MdPubkey,
/// Stable account category.
pub account_kind: std::string::String,
/// Mint correlated with this account when available.
pub mint: std::option::Option<ks_lib::MdPubkey>,
/// RPC context slot.
pub slot: u64,
/// Bounded decoder-owned projection.
pub payload_json: serde_json::Value,
}
/// One bounded RPC read and its validated snapshot.
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct MetaplexTokenMetadataStatefulReadResult {
/// Commitment used by the read.
pub commitment: std::string::String,
/// Context slot returned by the endpoint.
pub context_slot: u64,
/// Validated canonical snapshot.
pub snapshot: crate::MetaplexTokenMetadataStatefulSnapshot,
}
/// Reads and validates one bounded Metaplex Token Metadata account.
pub async fn read_metaplex_token_metadata_stateful_snapshot(
pool: &ks_onchain_transport::HttpEndpointPool,
request: &crate::MetaplexTokenMetadataStatefulReadRequest,
) -> ks_core::Result<crate::MetaplexTokenMetadataStatefulReadResult> {
if let std::result::Result::Err(error) = request.validate() {
return std::result::Result::Err(error);
}
let config = match ks_onchain_transport::GetAccountInfoConfig::new_with_data(
ks_onchain_transport::RpcCommitmentLevel::Confirmed,
request.min_context_slot,
request.max_data_bytes,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let result = match pool
.get_account_info_for_role(request.query_role.as_str(), &request.account, &config)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return crate::materialize_metaplex_token_metadata_account_info_result(request, &result);
}
/// Validates one RPC account response and delegates parsing to `ks-lib`.
pub fn materialize_metaplex_token_metadata_account_info_result(
request: &crate::MetaplexTokenMetadataStatefulReadRequest,
result: &ks_onchain_transport::AccountInfoResult,
) -> ks_core::Result<crate::MetaplexTokenMetadataStatefulReadResult> {
if let std::result::Result::Err(error) = request.validate() {
return std::result::Result::Err(error);
}
if let std::option::Option::Some(minimum) = request.min_context_slot {
if result.context.slot < minimum {
return std::result::Result::Err(ks_core::Error::new(
"metaplex_stateful_context_slot_too_old",
"Metaplex account context slot is below the requested minimum",
));
}
}
let account = match result.account.as_ref() {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(ks_core::Error::new(
"metaplex_stateful_account_missing",
format!("Metaplex account {} does not exist", request.account.0),
));
},
};
if account.executable {
return std::result::Result::Err(ks_core::Error::new(
"metaplex_stateful_account_executable",
"Metaplex state accounts must not be executable",
));
}
if account.owner.0 != ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID {
return std::result::Result::Err(ks_core::Error::new(
"metaplex_stateful_owner_mismatch",
"Metaplex state account owner does not match Token Metadata",
));
}
if account.space != account.data.len() as u64 || account.data.len() > request.max_data_bytes {
return std::result::Result::Err(ks_core::Error::new(
"metaplex_stateful_account_data_invalid",
"Metaplex account data is incomplete or above the configured bound",
));
}
let snapshot = match decode_snapshot(request, result.context.slot, account.data.as_slice()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(crate::MetaplexTokenMetadataStatefulReadResult {
commitment: "confirmed".to_string(),
context_slot: result.context.slot,
snapshot,
});
}
fn stateful_decode_error(
request: &crate::MetaplexTokenMetadataStatefulReadRequest,
error: ks_lib::DcMetadataMtmMetadataAccountDecodeError,
) -> ks_core::Error {
return ks_core::Error::new(
"metaplex_stateful_decode_failed",
format!(
"Metaplex stateful decode failed for account {} kind {:?}: {error}",
request.account.0, request.kind
),
);
}
fn decode_snapshot(
request: &crate::MetaplexTokenMetadataStatefulReadRequest,
slot: u64,
data: &[u8],
) -> ks_core::Result<crate::MetaplexTokenMetadataStatefulSnapshot> {
let owner = ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID;
return match &request.kind {
crate::MetaplexTokenMetadataAccountKind::Metadata => {
match ks_lib::decoder_metadata_metaplex_token_metadata_decode_metadata_account(
request.account.0.as_str(),
owner,
data,
) {
std::result::Result::Ok(value) => {
std::result::Result::Ok(crate::MetaplexTokenMetadataStatefulSnapshot {
account: request.account.clone(),
account_kind: "metadata".to_string(),
mint: std::option::Option::Some(ks_lib::MdPubkey(value.mint)),
slot,
payload_json: value.payload_json,
})
},
std::result::Result::Err(error) => {
std::result::Result::Err(stateful_decode_error(request, error))
},
}
},
crate::MetaplexTokenMetadataAccountKind::Edition { mint } => {
match ks_lib::decoder_metadata_metaplex_token_metadata_decode_edition_account(
request.account.0.as_str(),
owner,
mint.0.as_str(),
data,
) {
std::result::Result::Ok(value) => {
std::result::Result::Ok(crate::MetaplexTokenMetadataStatefulSnapshot {
account: request.account.clone(),
account_kind: "edition".to_string(),
mint: std::option::Option::Some(mint.clone()),
slot,
payload_json: value.payload_json,
})
},
std::result::Result::Err(error) => {
std::result::Result::Err(stateful_decode_error(request, error))
},
}
},
crate::MetaplexTokenMetadataAccountKind::EditionMarker { mint, edition } => {
match ks_lib::decoder_metadata_metaplex_token_metadata_decode_edition_marker_account(
request.account.0.as_str(),
owner,
mint.0.as_str(),
*edition,
data,
) {
std::result::Result::Ok(value) => {
std::result::Result::Ok(crate::MetaplexTokenMetadataStatefulSnapshot {
account: request.account.clone(),
account_kind: "edition_marker".to_string(),
mint: std::option::Option::Some(mint.clone()),
slot,
payload_json: serde_json::json!({
"account": value.account,
"bump": value.bump,
"mint": value.mint,
"kind": match value.kind {
ks_lib::DcMetadataMtmEditionMarkerAccountKind::EditionMarkerV1 => "edition_marker_v1",
ks_lib::DcMetadataMtmEditionMarkerAccountKind::EditionMarkerV2 => "edition_marker_v2",
},
"marker_group": value.marker_group,
"edition": value.edition,
"byte_index": value.byte_index,
"bit_mask": value.bit_mask,
"edition_taken": value.edition_taken,
"ledger": value.ledger,
"payload": value.payload_json,
}),
})
},
std::result::Result::Err(error) => {
std::result::Result::Err(stateful_decode_error(request, error))
},
}
},
crate::MetaplexTokenMetadataAccountKind::TokenRecord { mint, token } => {
match ks_lib::decoder_metadata_metaplex_token_metadata_decode_token_record_account(
request.account.0.as_str(),
owner,
mint.0.as_str(),
token.0.as_str(),
data,
) {
std::result::Result::Ok(value) => {
std::result::Result::Ok(crate::MetaplexTokenMetadataStatefulSnapshot {
account: request.account.clone(),
account_kind: "token_record".to_string(),
mint: std::option::Option::Some(mint.clone()),
slot,
payload_json: value.payload_json,
})
},
std::result::Result::Err(error) => {
std::result::Result::Err(stateful_decode_error(request, error))
},
}
},
crate::MetaplexTokenMetadataAccountKind::TokenOwnedEscrow => {
match ks_lib::decoder_metadata_metaplex_token_metadata_decode_token_owned_escrow_account(
request.account.0.as_str(),
owner,
data,
) {
std::result::Result::Ok(value) => {
let authority_kind = match value.authority_kind {
ks_lib::DcMetadataMtmTokenOwnedEscrowAuthorityKind::TokenOwner => {
"token_owner"
},
ks_lib::DcMetadataMtmTokenOwnedEscrowAuthorityKind::Creator => "creator",
};
std::result::Result::Ok(crate::MetaplexTokenMetadataStatefulSnapshot {
account: request.account.clone(),
account_kind: "token_owned_escrow".to_string(),
mint: std::option::Option::Some(ks_lib::MdPubkey(value.base_token.clone())),
slot,
payload_json: serde_json::json!({
"account": value.account,
"bump": value.bump,
"base_token": value.base_token,
"authority_kind": authority_kind,
"creator": value.creator,
"stored_bump": value.stored_bump,
"payload": value.payload_json,
}),
})
},
std::result::Result::Err(error) => {
std::result::Result::Err(stateful_decode_error(request, error))
},
}
},
};
}
#[cfg(test)]
mod tests {
#[test]
fn stateful_read_request_round_trips_through_json() {
let request = crate::MetaplexTokenMetadataStatefulReadRequest {
query_role: "rpc".to_string(),
account: ks_lib::MdPubkey("11111111111111111111111111111111".to_string()),
kind: crate::MetaplexTokenMetadataAccountKind::Metadata,
min_context_slot: std::option::Option::Some(42),
max_data_bytes: 1024,
};
let json = match serde_json::to_string(&request) {
std::result::Result::Ok(json) => json,
std::result::Result::Err(error) => panic!("request serialization failed: {error}"),
};
let decoded = match serde_json::from_str::<crate::MetaplexTokenMetadataStatefulReadRequest>(
json.as_str(),
) {
std::result::Result::Ok(decoded) => decoded,
std::result::Result::Err(error) => panic!("request deserialization failed: {error}"),
};
assert_eq!(decoded, request);
let marker = crate::MetaplexTokenMetadataStatefulReadRequest {
query_role: "rpc".to_string(),
account: ks_lib::MdPubkey("11111111111111111111111111111111".to_string()),
kind: crate::MetaplexTokenMetadataAccountKind::EditionMarker {
mint: ks_lib::MdPubkey("11111111111111111111111111111111".to_string()),
edition: 1,
},
min_context_slot: std::option::Option::Some(43),
max_data_bytes: 1024,
};
let marker_json = match serde_json::to_string(&marker) {
std::result::Result::Ok(json) => json,
std::result::Result::Err(error) => {
panic!("marker request serialization failed: {error}")
},
};
let marker_decoded = match serde_json::from_str::<
crate::MetaplexTokenMetadataStatefulReadRequest,
>(marker_json.as_str())
{
std::result::Result::Ok(decoded) => decoded,
std::result::Result::Err(error) => {
panic!("marker request deserialization failed: {error}")
},
};
assert_eq!(marker_decoded, marker);
let escrow = crate::MetaplexTokenMetadataStatefulReadRequest {
query_role: "rpc".to_string(),
account: ks_lib::MdPubkey("11111111111111111111111111111111".to_string()),
kind: crate::MetaplexTokenMetadataAccountKind::TokenOwnedEscrow,
min_context_slot: std::option::Option::Some(44),
max_data_bytes: 1024,
};
let escrow_json = match serde_json::to_string(&escrow) {
std::result::Result::Ok(json) => json,
std::result::Result::Err(error) => {
panic!("escrow request serialization failed: {error}")
},
};
let escrow_decoded = match serde_json::from_str::<
crate::MetaplexTokenMetadataStatefulReadRequest,
>(escrow_json.as_str())
{
std::result::Result::Ok(decoded) => decoded,
std::result::Result::Err(error) => {
panic!("escrow request deserialization failed: {error}")
},
};
assert_eq!(escrow_decoded, escrow);
let invalid_marker = crate::MetaplexTokenMetadataStatefulReadRequest {
query_role: "rpc".to_string(),
account: ks_lib::MdPubkey("11111111111111111111111111111111".to_string()),
kind: crate::MetaplexTokenMetadataAccountKind::EditionMarker {
mint: ks_lib::MdPubkey("invalid".to_string()),
edition: 1,
},
min_context_slot: std::option::Option::None,
max_data_bytes: 1024,
};
assert!(invalid_marker.validate().is_err());
}
#[test]
fn rejects_zero_and_oversize_bounds_before_account_decoding() {
let request = crate::MetaplexTokenMetadataStatefulReadRequest {
query_role: "rpc".to_string(),
account: ks_lib::MdPubkey("11111111111111111111111111111111".to_string()),
kind: crate::MetaplexTokenMetadataAccountKind::Metadata,
min_context_slot: std::option::Option::None,
max_data_bytes: 0,
};
let result = ks_onchain_transport::AccountInfoResult {
context: ks_onchain_transport::RpcResponseContext {
slot: 1,
api_version: std::option::Option::None,
},
account: std::option::Option::None,
};
assert!(
crate::materialize_metaplex_token_metadata_account_info_result(&request, &result)
.is_err()
);
let mut oversized = request;
oversized.max_data_bytes = ks_onchain_transport::MAX_COMPLETE_ACCOUNT_DATA_BYTES + 1;
assert!(oversized.validate().is_err());
}
#[test]
fn token_owned_escrow_materializes_with_canonical_authority_projection() {
let program = match ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID
.parse::<solana_pubkey::Pubkey>()
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("program parse failed: {error}"),
};
let mint = solana_pubkey::Pubkey::new_from_array([6_u8; 32]);
let (account, bump) = solana_pubkey::Pubkey::find_program_address(
&[b"metadata", program.as_ref(), mint.as_ref(), &[0], b"escrow"],
&program,
);
let mut data = std::vec![10_u8];
data.extend_from_slice(mint.as_ref());
data.push(0);
data.push(bump);
assert_eq!(data.len(), 35);
let request = crate::MetaplexTokenMetadataStatefulReadRequest {
query_role: "rpc".to_string(),
account: ks_lib::MdPubkey(account.to_string()),
kind: crate::MetaplexTokenMetadataAccountKind::TokenOwnedEscrow,
min_context_slot: std::option::Option::Some(11),
max_data_bytes: 1024,
};
let result = ks_onchain_transport::AccountInfoResult {
context: ks_onchain_transport::RpcResponseContext {
slot: 11,
api_version: std::option::Option::None,
},
account: std::option::Option::Some(ks_onchain_transport::AccountInfoValue {
lamports: 1_000,
owner: ks_lib::MdProgramId(program.to_string()),
executable: false,
rent_epoch: 0,
space: data.len() as u64,
data,
}),
};
let output =
crate::materialize_metaplex_token_metadata_account_info_result(&request, &result);
let snapshot = match output {
std::result::Result::Ok(value) => value.snapshot,
std::result::Result::Err(error) => panic!("escrow stateful decode failed: {error}"),
};
assert_eq!(snapshot.account_kind, "token_owned_escrow");
assert_eq!(snapshot.mint, std::option::Option::Some(ks_lib::MdPubkey(mint.to_string())));
assert_eq!(snapshot.payload_json["base_token"], serde_json::json!(mint.to_string()));
assert_eq!(snapshot.payload_json["authority_kind"], serde_json::json!("token_owner"));
assert_eq!(snapshot.payload_json["creator"], serde_json::Value::Null);
assert_eq!(snapshot.payload_json["stored_bump"], serde_json::json!(bump));
}
#[test]
fn compact_printed_edition_account_materializes_from_current_network_allocation() {
let program = match ks_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID
.parse::<solana_pubkey::Pubkey>()
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("program parse failed: {error}"),
};
let mint = solana_pubkey::Pubkey::new_from_array([7_u8; 32]);
let parent = solana_pubkey::Pubkey::new_from_array([8_u8; 32]);
let (account, _) = solana_pubkey::Pubkey::find_program_address(
&[b"metadata", program.as_ref(), mint.as_ref(), b"edition"],
&program,
);
let mut data = vec![1_u8];
data.extend_from_slice(parent.as_ref());
data.extend_from_slice(&1_u64.to_le_bytes());
data.push(0);
assert_eq!(data.len(), 42);
let request = crate::MetaplexTokenMetadataStatefulReadRequest {
query_role: "rpc".to_string(),
account: ks_lib::MdPubkey(account.to_string()),
kind: crate::MetaplexTokenMetadataAccountKind::Edition {
mint: ks_lib::MdPubkey(mint.to_string()),
},
min_context_slot: std::option::Option::Some(12),
max_data_bytes: 1024,
};
let result = ks_onchain_transport::AccountInfoResult {
context: ks_onchain_transport::RpcResponseContext {
slot: 12,
api_version: std::option::Option::None,
},
account: std::option::Option::Some(ks_onchain_transport::AccountInfoValue {
lamports: 1_000,
owner: ks_lib::MdProgramId(program.to_string()),
executable: false,
rent_epoch: 0,
space: 42,
data,
}),
};
let output =
crate::materialize_metaplex_token_metadata_account_info_result(&request, &result);
let snapshot = match output {
std::result::Result::Ok(value) => value.snapshot,
std::result::Result::Err(error) => {
panic!("compact edition stateful decode failed: {error}")
},
};
assert_eq!(snapshot.account_kind, "edition");
assert_eq!(snapshot.payload_json["edition"], serde_json::json!(1));
assert_eq!(snapshot.payload_json["parent"], serde_json::json!(parent.to_string()));
}
#[test]
fn stateful_complete_read_bound_matches_the_transport_contract() {
assert_eq!(
crate::MAX_METAPLEX_TOKEN_METADATA_ACCOUNT_BYTES,
ks_onchain_transport::MAX_COMPLETE_ACCOUNT_DATA_BYTES
);
}
}

View File

@@ -0,0 +1,831 @@
// file: ks-pipeline/src/metadata_solana_program_execution_orchestration.rs
// version: 3
//! Simulation-first Solana Program Metadata execution orchestration and postconditions.
/// Maximum number of resolved signers accepted by one Program Metadata envelope.
pub const MAX_SOLANA_PROGRAM_METADATA_EXECUTION_SIGNERS: usize = 16;
/// Complete request checked before signing or submitting one Program Metadata transaction.
#[derive(Clone, Debug, PartialEq)]
pub struct SolanaProgramMetadataExecutionReadinessRequest {
/// Exact prepared execution plan.
pub plan: ks_lib::ExApiPreparedExecutionPlan,
/// Successful stateful preflight report.
pub preflight: crate::SolanaProgramMetadataPreflightReport,
/// Exact compiled message hash.
pub message_hash: std::string::String,
/// Hash retained by simulation evidence.
pub simulated_message_hash: std::string::String,
/// Whether simulation was performed.
pub simulated: bool,
/// Whether simulation succeeded.
pub simulation_succeeded: bool,
/// Public keys available to sign.
pub resolved_signers: std::vec::Vec<ks_lib::MdPubkey>,
/// Whether submission was requested.
pub submit: bool,
/// Whether the operator confirmed submission.
pub operator_confirmed: bool,
}
/// Deterministic readiness report emitted before signing.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct SolanaProgramMetadataExecutionReadinessReport {
/// Stable operation code.
pub operation_code: std::string::String,
/// Exact message hash bound to simulation.
pub message_hash: std::string::String,
/// Confirmed preflight context slot.
pub context_slot: u64,
/// Deduplicated required signers.
pub required_signers: std::vec::Vec<ks_lib::MdPubkey>,
/// Whether signing and submission are authorized.
pub send_authorized: bool,
/// Ordered successful checks.
pub checks: std::vec::Vec<std::string::String>,
}
/// Result of one Program Metadata postcondition.
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum SolanaProgramMetadataPostconditionStatus {
/// Final state confirms the expected effect.
Confirmed,
/// Final state contradicts the expected effect.
Contradicted,
/// No stateful assertion applies.
NotApplicable,
}
/// One machine-readable Program Metadata postcondition.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct SolanaProgramMetadataPostcondition {
/// Stable check code.
pub code: std::string::String,
/// Account inspected by the check.
pub account: ks_lib::MdPubkey,
/// Terminal status.
pub status: crate::SolanaProgramMetadataPostconditionStatus,
/// Human-readable diagnostic.
pub diagnostic: std::string::String,
}
/// Complete request for one confirmed Program Metadata post-execution inspection.
#[derive(Clone, Debug, PartialEq)]
pub struct SolanaProgramMetadataPostExecutionRequest {
/// Exact operation that was submitted.
pub operation: ks_lib::ExMetadataSpmOperation,
/// Confirmed snapshots captured before execution.
pub before: std::vec::Vec<crate::SolanaProgramMetadataStatefulReadResult>,
/// Confirmed snapshots captured after execution.
pub after: std::vec::Vec<crate::SolanaProgramMetadataStatefulReadResult>,
}
/// Aggregated Program Metadata post-execution report.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct SolanaProgramMetadataPostExecutionReport {
/// Stable operation code.
pub operation_code: std::string::String,
/// Aggregate status without invented success.
pub status: crate::SolanaProgramMetadataPostconditionStatus,
/// Individual postconditions.
pub postconditions: std::vec::Vec<crate::SolanaProgramMetadataPostcondition>,
}
/// Validates simulation, preflight, signer and submission policies.
pub fn validate_solana_program_metadata_execution_readiness(
request: &crate::SolanaProgramMetadataExecutionReadinessRequest,
) -> ks_core::Result<crate::SolanaProgramMetadataExecutionReadinessReport> {
if request.plan.operation_code != request.preflight.operation_code {
return std::result::Result::Err(ks_core::Error::new(
"solana_program_metadata_execution_operation_mismatch",
"Program Metadata plan and preflight operation codes must match",
));
}
if request.preflight.safety.decision == ks_lib::ExSafetyDecision::Deny {
return std::result::Result::Err(ks_core::Error::new(
"solana_program_metadata_execution_safety_denied",
"Program Metadata preflight safety decision denied execution",
));
}
if request.preflight.safety.decision == ks_lib::ExSafetyDecision::RequireConfirmation
&& !request.operator_confirmed
{
return std::result::Result::Err(ks_core::Error::new(
"solana_program_metadata_execution_safety_confirmation_required",
"Program Metadata safety policy requires explicit operator confirmation",
));
}
if request.message_hash.trim().is_empty()
|| request.message_hash != request.simulated_message_hash
|| !request.simulated
|| !request.simulation_succeeded
{
return std::result::Result::Err(ks_core::Error::new(
"solana_program_metadata_execution_simulation_required",
"Program Metadata execution requires successful exact-message simulation",
));
}
if request.resolved_signers.len() > crate::MAX_SOLANA_PROGRAM_METADATA_EXECUTION_SIGNERS {
return std::result::Result::Err(ks_core::Error::new(
"solana_program_metadata_execution_signer_limit_exceeded",
"Program Metadata execution signer limit exceeded",
));
}
let resolved = request
.resolved_signers
.iter()
.map(|item| return item.0.clone())
.collect::<std::collections::BTreeSet<std::string::String>>();
let mut required = std::collections::BTreeMap::<std::string::String, ks_lib::MdPubkey>::new();
for signer in &request.plan.required_signers {
if !resolved.contains(signer.pubkey.0.as_str()) {
return std::result::Result::Err(ks_core::Error::new(
"solana_program_metadata_execution_signer_unresolved",
format!("Program Metadata signer {} is unresolved", signer.pubkey.0),
));
}
required
.entry(signer.pubkey.0.clone())
.or_insert_with(|| return signer.pubkey.clone());
}
if request.submit && !request.operator_confirmed {
return std::result::Result::Err(ks_core::Error::new(
"solana_program_metadata_execution_confirmation_required",
"Program Metadata submission requires explicit operator confirmation",
));
}
if request.submit && request.plan.policy.dry_run {
return std::result::Result::Err(ks_core::Error::new(
"solana_program_metadata_execution_dry_run_blocks_submission",
"Program Metadata dry-run plan cannot be submitted",
));
}
tracing::debug!(
target: crate::TRACING_TARGET,
action = "validate_solana_program_metadata_execution_readiness",
operation_code = %request.plan.operation_code,
context_slot = request.preflight.context_slot,
signer_count = required.len(),
submit = request.submit,
send_authorized = request.submit && request.operator_confirmed && !request.plan.policy.dry_run,
"validated Solana Program Metadata execution readiness"
);
return std::result::Result::Ok(crate::SolanaProgramMetadataExecutionReadinessReport {
operation_code: request.plan.operation_code.clone(),
message_hash: request.message_hash.clone(),
context_slot: request.preflight.context_slot,
required_signers: required.into_values().collect(),
send_authorized: request.submit
&& request.operator_confirmed
&& !request.plan.policy.dry_run,
checks: vec![
"preflight_bound_to_operation".to_string(),
"common_safety_allowed".to_string(),
"simulation_bound_to_exact_message".to_string(),
"all_required_signers_resolved".to_string(),
"submission_policy_consistent".to_string(),
],
});
}
/// Inspects confirmed state after one Program Metadata execution.
pub fn inspect_solana_program_metadata_post_execution(
request: &crate::SolanaProgramMetadataPostExecutionRequest,
) -> ks_core::Result<crate::SolanaProgramMetadataPostExecutionReport> {
if let std::result::Result::Err(error) =
validate_snapshot_set(request.before.as_slice(), "before")
{
return std::result::Result::Err(error);
}
if let std::result::Result::Err(error) =
validate_snapshot_set(request.after.as_slice(), "after")
{
return std::result::Result::Err(error);
}
let target = operation_target(&request.operation).clone();
let before = find_snapshot(request.before.as_slice(), &target);
let after = find_snapshot(request.after.as_slice(), &target);
let postcondition = evaluate_operation_postcondition(
&request.operation,
before,
after,
request.before.as_slice(),
);
let status =
summarize_solana_program_metadata_postconditions(std::slice::from_ref(&postcondition));
tracing::debug!(
target: crate::TRACING_TARGET,
action = "inspect_solana_program_metadata_post_execution",
operation_code = request.operation.operation_code(),
target_account = %target.0,
status = ?status,
"inspected Solana Program Metadata post-execution state"
);
return std::result::Result::Ok(crate::SolanaProgramMetadataPostExecutionReport {
operation_code: request.operation.operation_code().to_string(),
status,
postconditions: vec![postcondition],
});
}
/// Aggregates Program Metadata postconditions without converting absence into success.
pub fn summarize_solana_program_metadata_postconditions(
postconditions: &[crate::SolanaProgramMetadataPostcondition],
) -> crate::SolanaProgramMetadataPostconditionStatus {
if postconditions.iter().any(|item| {
return item.status == crate::SolanaProgramMetadataPostconditionStatus::Contradicted;
}) {
return crate::SolanaProgramMetadataPostconditionStatus::Contradicted;
}
if postconditions.iter().any(|item| {
return item.status == crate::SolanaProgramMetadataPostconditionStatus::Confirmed;
}) {
return crate::SolanaProgramMetadataPostconditionStatus::Confirmed;
}
return crate::SolanaProgramMetadataPostconditionStatus::NotApplicable;
}
fn validate_snapshot_set(
snapshots: &[crate::SolanaProgramMetadataStatefulReadResult],
phase: &str,
) -> ks_core::Result<()> {
if snapshots.len() > crate::MAX_SOLANA_PROGRAM_METADATA_PREFLIGHT_ACCOUNTS {
return std::result::Result::Err(ks_core::Error::new(
"solana_program_metadata_post_execution_account_limit_exceeded",
format!("Program Metadata {phase} snapshot limit exceeded"),
));
}
let mut accounts = std::collections::BTreeSet::new();
for snapshot in snapshots {
if snapshot.commitment != "confirmed" {
return std::result::Result::Err(ks_core::Error::new(
"solana_program_metadata_post_execution_commitment_mismatch",
format!("Program Metadata {phase} snapshots must use confirmed commitment"),
));
}
if !accounts.insert(snapshot.account.0.as_str()) {
return std::result::Result::Err(ks_core::Error::new(
"solana_program_metadata_post_execution_duplicate_account",
format!("duplicate Program Metadata {phase} snapshot for {}", snapshot.account.0),
));
}
}
return std::result::Result::Ok(());
}
fn operation_target(operation: &ks_lib::ExMetadataSpmOperation) -> &ks_lib::MdPubkey {
return match operation {
ks_lib::ExMetadataSpmOperation::Write { buffer, .. }
| ks_lib::ExMetadataSpmOperation::Allocate { buffer, .. } => buffer,
ks_lib::ExMetadataSpmOperation::Initialize { metadata, .. }
| ks_lib::ExMetadataSpmOperation::SetData { metadata, .. }
| ks_lib::ExMetadataSpmOperation::SetImmutable { metadata, .. } => metadata,
ks_lib::ExMetadataSpmOperation::SetAuthority { account, .. }
| ks_lib::ExMetadataSpmOperation::Trim { account, .. }
| ks_lib::ExMetadataSpmOperation::Close { account, .. }
| ks_lib::ExMetadataSpmOperation::Extend { account, .. } => account,
};
}
fn find_snapshot<'a>(
snapshots: &'a [crate::SolanaProgramMetadataStatefulReadResult],
account: &ks_lib::MdPubkey,
) -> std::option::Option<&'a crate::SolanaProgramMetadataStatefulReadResult> {
return snapshots.iter().find(|value| return value.account == *account);
}
fn evaluate_operation_postcondition(
operation: &ks_lib::ExMetadataSpmOperation,
before: std::option::Option<&crate::SolanaProgramMetadataStatefulReadResult>,
after: std::option::Option<&crate::SolanaProgramMetadataStatefulReadResult>,
before_all: &[crate::SolanaProgramMetadataStatefulReadResult],
) -> crate::SolanaProgramMetadataPostcondition {
let target = operation_target(operation).clone();
let confirmed = match operation {
ks_lib::ExMetadataSpmOperation::Write { offset, source, .. } => {
write_postcondition(*offset, source, after, before_all)
},
ks_lib::ExMetadataSpmOperation::Initialize {
program,
canonical,
seed,
encoding,
compression,
format,
data_source,
data,
..
} => initialize_postcondition(
program,
*canonical,
seed.as_slice(),
*encoding,
*compression,
*format,
*data_source,
data.as_ref(),
after,
),
ks_lib::ExMetadataSpmOperation::SetAuthority { new_authority, .. } => {
authority_postcondition(new_authority.as_ref(), after)
},
ks_lib::ExMetadataSpmOperation::SetData {
encoding, compression, format, source, ..
} => set_data_postcondition(
*encoding,
*compression,
*format,
source,
before,
after,
before_all,
),
ks_lib::ExMetadataSpmOperation::SetImmutable { .. } => match after {
std::option::Option::Some(value) => matches!(
&value.state,
crate::SolanaProgramMetadataObservedAccountState::Metadata(metadata) if !metadata.mutable
),
std::option::Option::None => false,
},
ks_lib::ExMetadataSpmOperation::Trim { .. } => trim_postcondition(before, after),
ks_lib::ExMetadataSpmOperation::Close { .. } => match after {
std::option::Option::Some(value) => {
matches!(&value.state, crate::SolanaProgramMetadataObservedAccountState::Missing)
},
std::option::Option::None => false,
},
ks_lib::ExMetadataSpmOperation::Allocate { authority, seed, canonical, .. } => {
allocate_postcondition(authority, seed.as_deref(), *canonical, after)
},
ks_lib::ExMetadataSpmOperation::Extend { length, .. } => {
extend_postcondition(*length, before, after)
},
};
let status = if after.is_none() {
crate::SolanaProgramMetadataPostconditionStatus::NotApplicable
} else if confirmed {
crate::SolanaProgramMetadataPostconditionStatus::Confirmed
} else {
crate::SolanaProgramMetadataPostconditionStatus::Contradicted
};
return crate::SolanaProgramMetadataPostcondition {
code: format!("{}.state", operation.operation_code()),
account: target,
status,
diagnostic: match status {
crate::SolanaProgramMetadataPostconditionStatus::Confirmed => {
"confirmed state matches the expected Program Metadata effect".to_string()
},
crate::SolanaProgramMetadataPostconditionStatus::Contradicted => {
"confirmed state contradicts the expected Program Metadata effect".to_string()
},
crate::SolanaProgramMetadataPostconditionStatus::NotApplicable => {
"no confirmed after-state snapshot was supplied".to_string()
},
},
};
}
fn write_postcondition(
offset: u32,
source: &ks_lib::ExMetadataSpmWriteSource,
after: std::option::Option<&crate::SolanaProgramMetadataStatefulReadResult>,
before_all: &[crate::SolanaProgramMetadataStatefulReadResult],
) -> bool {
let buffer = match after.map(|value| return &value.state) {
std::option::Option::Some(crate::SolanaProgramMetadataObservedAccountState::Buffer(
value,
)) => value,
_ => return false,
};
let start = match usize::try_from(offset) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return false,
};
let expected = match source {
ks_lib::ExMetadataSpmWriteSource::Inline { data } => data.as_slice(),
ks_lib::ExMetadataSpmWriteSource::Buffer { source_buffer } => {
let source_snapshot = match find_snapshot(before_all, source_buffer) {
std::option::Option::Some(value) => value,
std::option::Option::None => return false,
};
match &source_snapshot.state {
crate::SolanaProgramMetadataObservedAccountState::Buffer(value) => {
value.data.as_slice()
},
_ => return false,
}
},
};
let end = match start.checked_add(expected.len()) {
std::option::Option::Some(value) => value,
std::option::Option::None => return false,
};
return buffer.data.get(start..end) == std::option::Option::Some(expected);
}
#[allow(clippy::too_many_arguments)]
fn initialize_postcondition(
program: &ks_lib::MdPubkey,
canonical: bool,
seed: &[u8],
encoding: ks_lib::ExMetadataSpmEncoding,
compression: ks_lib::ExMetadataSpmCompression,
format: ks_lib::ExMetadataSpmFormat,
data_source: ks_lib::ExMetadataSpmDataSource,
data: std::option::Option<&ks_lib::ExMetadataSpmDataInput>,
after: std::option::Option<&crate::SolanaProgramMetadataStatefulReadResult>,
) -> bool {
let metadata = match after.map(|value| return &value.state) {
std::option::Option::Some(crate::SolanaProgramMetadataObservedAccountState::Metadata(
value,
)) => value,
_ => return false,
};
if metadata.program.as_str() != program.0.as_str()
|| metadata.canonical != canonical
|| &metadata.seed.as_bytes()[..] != seed
|| metadata.encoding.wire_value() != encoding.wire_value()
|| metadata.compression.wire_value() != compression.wire_value()
|| metadata.format.wire_value() != format.wire_value()
{
return false;
}
let expected_source = match data {
std::option::Option::Some(value) => value.data_source(),
std::option::Option::None => data_source,
};
if metadata.data_source.wire_value() != expected_source.wire_value() {
return false;
}
return match data {
std::option::Option::Some(value) => data_matches(value, &metadata.data),
std::option::Option::None => true,
};
}
fn authority_postcondition(
new_authority: std::option::Option<&ks_lib::MdPubkey>,
after: std::option::Option<&crate::SolanaProgramMetadataStatefulReadResult>,
) -> bool {
let observed = match after.map(|value| return &value.state) {
std::option::Option::Some(crate::SolanaProgramMetadataObservedAccountState::Buffer(
value,
)) => value.authority.as_deref(),
std::option::Option::Some(crate::SolanaProgramMetadataObservedAccountState::Metadata(
value,
)) => value.authority.as_deref(),
_ => return false,
};
return observed == new_authority.map(|value| return value.0.as_str());
}
fn set_data_postcondition(
encoding: ks_lib::ExMetadataSpmEncoding,
compression: ks_lib::ExMetadataSpmCompression,
format: ks_lib::ExMetadataSpmFormat,
source: &ks_lib::ExMetadataSpmSetDataSource,
before: std::option::Option<&crate::SolanaProgramMetadataStatefulReadResult>,
after: std::option::Option<&crate::SolanaProgramMetadataStatefulReadResult>,
before_all: &[crate::SolanaProgramMetadataStatefulReadResult],
) -> bool {
let metadata = match after.map(|value| return &value.state) {
std::option::Option::Some(crate::SolanaProgramMetadataObservedAccountState::Metadata(
value,
)) => value,
_ => return false,
};
if metadata.encoding.wire_value() != encoding.wire_value()
|| metadata.compression.wire_value() != compression.wire_value()
|| metadata.format.wire_value() != format.wire_value()
{
return false;
}
return match source {
ks_lib::ExMetadataSpmSetDataSource::PreserveExisting => {
match before.map(|value| return &value.state) {
std::option::Option::Some(
crate::SolanaProgramMetadataObservedAccountState::Metadata(value),
) => metadata.data == value.data && metadata.data_source == value.data_source,
_ => false,
}
},
ks_lib::ExMetadataSpmSetDataSource::Inline { data } => data_matches(data, &metadata.data),
ks_lib::ExMetadataSpmSetDataSource::Buffer { buffer, data_source } => {
if metadata.data_source.wire_value() != data_source.wire_value() {
return false;
}
let source_snapshot = match find_snapshot(before_all, buffer) {
std::option::Option::Some(value) => value,
std::option::Option::None => return false,
};
let bytes = match &source_snapshot.state {
crate::SolanaProgramMetadataObservedAccountState::Buffer(value) => {
value.data.as_slice()
},
_ => return false,
};
return match &metadata.data {
ks_lib::DcMetadataSpmData::Direct(value) => value.as_slice() == bytes,
ks_lib::DcMetadataSpmData::Url(value) => value.as_bytes() == bytes,
ks_lib::DcMetadataSpmData::External(value) => &value.to_wire_bytes()[..] == bytes,
};
},
};
}
fn trim_postcondition(
before: std::option::Option<&crate::SolanaProgramMetadataStatefulReadResult>,
after: std::option::Option<&crate::SolanaProgramMetadataStatefulReadResult>,
) -> bool {
let before = match before {
std::option::Option::Some(value) => value,
std::option::Option::None => return false,
};
let after = match after {
std::option::Option::Some(value) => value,
std::option::Option::None => return false,
};
let same_kind = before.state.code() == after.state.code();
let size_reduced = match (before.space, after.space) {
(std::option::Option::Some(before_space), std::option::Option::Some(after_space)) => {
after_space <= before_space
},
_ => false,
};
return same_kind && size_reduced;
}
fn allocate_postcondition(
authority: &ks_lib::MdPubkey,
seed: std::option::Option<&[u8]>,
canonical: bool,
after: std::option::Option<&crate::SolanaProgramMetadataStatefulReadResult>,
) -> bool {
let buffer = match after.map(|value| return &value.state) {
std::option::Option::Some(crate::SolanaProgramMetadataObservedAccountState::Buffer(
value,
)) => value,
_ => return false,
};
if buffer.authority.as_deref() != std::option::Option::Some(authority.0.as_str()) {
return false;
}
return match seed {
std::option::Option::Some(value) => {
buffer.canonical == canonical && &buffer.seed.as_bytes()[..] == value
},
std::option::Option::None => !buffer.canonical,
};
}
fn extend_postcondition(
length: u16,
before: std::option::Option<&crate::SolanaProgramMetadataStatefulReadResult>,
after: std::option::Option<&crate::SolanaProgramMetadataStatefulReadResult>,
) -> bool {
let before = match before {
std::option::Option::Some(value) => value,
std::option::Option::None => return false,
};
let after = match after {
std::option::Option::Some(value) => value,
std::option::Option::None => return false,
};
let expected = match before.space.and_then(|value| return value.checked_add(u64::from(length)))
{
std::option::Option::Some(value) => value,
std::option::Option::None => return false,
};
return before.state.code() == after.state.code()
&& after.space == std::option::Option::Some(expected);
}
fn data_matches(
input: &ks_lib::ExMetadataSpmDataInput,
observed: &ks_lib::DcMetadataSpmData,
) -> bool {
return match (input, observed) {
(
ks_lib::ExMetadataSpmDataInput::Direct { bytes },
ks_lib::DcMetadataSpmData::Direct(value),
) => bytes == value,
(ks_lib::ExMetadataSpmDataInput::Url { url }, ks_lib::DcMetadataSpmData::Url(value)) => {
url == value
},
(
ks_lib::ExMetadataSpmDataInput::External { address, offset, length },
ks_lib::DcMetadataSpmData::External(value),
) => {
value.address.to_string().as_str() == address.0.as_str()
&& value.offset == *offset
&& value.length.map(std::num::NonZeroU32::get) == *length
},
_ => false,
};
}
#[cfg(test)]
mod tests {
fn pubkey(byte: u8) -> ks_lib::MdPubkey {
return ks_lib::MdPubkey(solana_pubkey::Pubkey::new_from_array([byte; 32]).to_string());
}
#[test]
fn readiness_requires_exact_simulation_and_resolved_signers() {
let signer = pubkey(1);
let plan = ks_lib::ExApiPreparedExecutionPlan {
executor_name: "kb-lib.executor.metadata.solana_program_metadata".to_string(),
executor_version: "0.4.8".to_string(),
intent_id: "spm-readiness".to_string(),
operation_code: ks_lib::EX_METADATA_SPM_WRITE_OPERATION.to_string(),
fee_payer: signer.clone(),
instructions: vec![ks_lib::ExApiPlannedInstruction {
program_id: ks_lib::MdProgramId(
ks_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID.to_string(),
),
operation_code: ks_lib::EX_METADATA_SPM_WRITE_OPERATION.to_string(),
accounts: vec![],
data: vec![0],
}],
required_signers: vec![ks_lib::ExApiRequiredSigner {
pubkey: signer.clone(),
role: "authority".to_string(),
}],
policy: ks_lib::ExApiExecutionPolicy {
dry_run: false,
..ks_lib::ExApiExecutionPolicy::default()
},
requested_spend_lamports: 0,
requested_compute_unit_price_micro_lamports: std::option::Option::None,
};
let request = crate::SolanaProgramMetadataExecutionReadinessRequest {
plan,
preflight: crate::SolanaProgramMetadataPreflightReport {
operation_code: ks_lib::EX_METADATA_SPM_WRITE_OPERATION.to_string(),
target_account: pubkey(2),
context_slot: 99,
inspected_accounts: vec![],
safety: ks_lib::ExSafetyEvaluation {
decision: ks_lib::ExSafetyDecision::Allow,
violations: vec![],
},
runtime_authority_validation_required: false,
checks: vec![],
},
message_hash: "message".to_string(),
simulated_message_hash: "message".to_string(),
simulated: true,
simulation_succeeded: true,
resolved_signers: vec![signer],
submit: true,
operator_confirmed: true,
};
let report = crate::validate_solana_program_metadata_execution_readiness(&request);
assert!(report.is_ok());
if let std::result::Result::Ok(value) = report {
assert!(value.send_authorized);
assert_eq!(value.context_slot, 99);
}
}
#[test]
fn pipeline_matrix_is_machine_readable_and_matches_nine_operations() {
let matrix_result = serde_json::from_str::<serde_json::Value>(include_str!(
"../../test-fixtures/contract-matrices/METADATA_SOLANA_PROGRAM_PIPELINE_MATRIX.json"
));
assert!(matrix_result.is_ok());
if let std::result::Result::Ok(matrix) = matrix_result {
assert_eq!(
matrix["programId"],
ks_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID
);
assert_eq!(
matrix["operations"].as_array().map(std::vec::Vec::len),
std::option::Option::Some(ks_lib::EX_METADATA_SPM_SUPPORTED_OPERATION_CODES.len())
);
assert_eq!(matrix["pipelineCrate"], "ks-pipeline");
assert_eq!(matrix["demoScenarioCrate"], "ks-pipeline-demo-scenarios");
}
}
#[test]
fn every_stable_operation_has_one_explicit_postcondition_path() {
let account = pubkey(4);
let authority = pubkey(5);
let program = pubkey(6);
let destination = pubkey(7);
let operations = vec![
ks_lib::ExMetadataSpmOperation::Write {
buffer: account.clone(),
authority: authority.clone(),
offset: 0,
source: ks_lib::ExMetadataSpmWriteSource::Inline { data: vec![1] },
},
ks_lib::ExMetadataSpmOperation::Initialize {
metadata: account.clone(),
authority: authority.clone(),
program: program.clone(),
program_data: std::option::Option::None,
canonical: false,
seed: vec![0_u8; ks_lib::DC_METADATA_SPM_SEED_BYTES],
encoding: ks_lib::ExMetadataSpmEncoding::Utf8,
compression: ks_lib::ExMetadataSpmCompression::None,
format: ks_lib::ExMetadataSpmFormat::Json,
data_source: ks_lib::ExMetadataSpmDataSource::Direct,
data: std::option::Option::Some(ks_lib::ExMetadataSpmDataInput::Direct {
bytes: vec![1],
}),
allocate_account: true,
},
ks_lib::ExMetadataSpmOperation::SetAuthority {
account: account.clone(),
authority: authority.clone(),
new_authority: std::option::Option::Some(pubkey(8)),
program_context: std::option::Option::None,
},
ks_lib::ExMetadataSpmOperation::SetData {
metadata: account.clone(),
authority: authority.clone(),
encoding: ks_lib::ExMetadataSpmEncoding::Utf8,
compression: ks_lib::ExMetadataSpmCompression::None,
format: ks_lib::ExMetadataSpmFormat::Json,
source: ks_lib::ExMetadataSpmSetDataSource::PreserveExisting,
program_context: std::option::Option::None,
},
ks_lib::ExMetadataSpmOperation::SetImmutable {
metadata: account.clone(),
authority: authority.clone(),
program_context: std::option::Option::None,
},
ks_lib::ExMetadataSpmOperation::Trim {
account: account.clone(),
authority: authority.clone(),
destination: destination.clone(),
program_context: std::option::Option::None,
},
ks_lib::ExMetadataSpmOperation::Close {
account: account.clone(),
authority: authority.clone(),
destination,
program_context: std::option::Option::None,
},
ks_lib::ExMetadataSpmOperation::Allocate {
buffer: account.clone(),
authority: authority.clone(),
seed: std::option::Option::None,
program_context: std::option::Option::None,
canonical: false,
allocate_account: true,
},
ks_lib::ExMetadataSpmOperation::Extend {
account,
authority,
length: 1,
program_context: std::option::Option::None,
},
];
let mut codes = operations
.iter()
.map(|operation| return operation.operation_code())
.collect::<std::vec::Vec<_>>();
codes.sort();
let mut expected = ks_lib::EX_METADATA_SPM_SUPPORTED_OPERATION_CODES.to_vec();
expected.sort();
assert_eq!(codes, expected);
for operation in operations {
let report = crate::inspect_solana_program_metadata_post_execution(
&crate::SolanaProgramMetadataPostExecutionRequest {
operation,
before: vec![],
after: vec![],
},
);
assert!(report.is_ok());
if let std::result::Result::Ok(value) = report {
assert_eq!(
value.status,
crate::SolanaProgramMetadataPostconditionStatus::NotApplicable
);
assert_eq!(value.postconditions.len(), 1);
}
}
}
#[test]
fn postcondition_summary_never_invents_success() {
let account = pubkey(3);
let item = crate::SolanaProgramMetadataPostcondition {
code: "spm.none".to_string(),
account,
status: crate::SolanaProgramMetadataPostconditionStatus::NotApplicable,
diagnostic: "missing after state".to_string(),
};
assert_eq!(
crate::summarize_solana_program_metadata_postconditions(&[item]),
crate::SolanaProgramMetadataPostconditionStatus::NotApplicable
);
}
}

View File

@@ -0,0 +1,761 @@
// file: ks-pipeline/src/metadata_solana_program_preflight.rs
// version: 3
//! Stateful preflight for Solana Program Metadata execution plans.
/// Maximum number of bounded account snapshots accepted by one preflight.
pub const MAX_SOLANA_PROGRAM_METADATA_PREFLIGHT_ACCOUNTS: usize = 16;
/// One rent-exemption observation supplied by the caller for a growth operation.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct SolanaProgramMetadataRentObservation {
/// Account whose allocation or growth is being validated.
pub account: ks_lib::MdPubkey,
/// Target account-data size used for the rent query.
pub target_space: u64,
/// Lamports observed on the account before execution.
pub observed_lamports: u64,
/// Minimum lamports returned for `target_space`.
pub required_lamports: u64,
}
impl crate::SolanaProgramMetadataRentObservation {
/// Returns whether the account is sufficiently pre-funded.
pub const fn is_satisfied(&self) -> bool {
return self.observed_lamports >= self.required_lamports;
}
}
/// Complete stateful preflight request for one prepared Program Metadata plan.
#[derive(Clone, Debug, PartialEq)]
pub struct SolanaProgramMetadataPreflightRequest {
/// Exact typed intent used to build the plan.
pub intent: ks_lib::ExMetadataSpmExecutionIntent,
/// Exact prepared plan produced by `ks-lib`.
pub plan: ks_lib::ExApiPreparedExecutionPlan,
/// Confirmed account snapshots observed before simulation.
pub before: std::vec::Vec<crate::SolanaProgramMetadataStatefulReadResult>,
/// Explicit rent observations required by account growth operations.
pub rent_observations: std::vec::Vec<crate::SolanaProgramMetadataRentObservation>,
}
/// Deterministic Program Metadata preflight report.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct SolanaProgramMetadataPreflightReport {
/// Stable operation code.
pub operation_code: std::string::String,
/// Primary account mutated by the instruction.
pub target_account: ks_lib::MdPubkey,
/// Highest confirmed context slot across supplied snapshots.
pub context_slot: u64,
/// Canonical accounts inspected by the preflight.
pub inspected_accounts: std::vec::Vec<ks_lib::MdPubkey>,
/// Safety decision applied before simulation.
pub safety: ks_lib::ExSafetyEvaluation,
/// Whether the on-chain program must validate an upgrade authority at runtime.
pub runtime_authority_validation_required: bool,
/// Ordered successful checks.
pub checks: std::vec::Vec<std::string::String>,
}
/// Validates one Program Metadata plan against confirmed pre-execution state.
pub fn inspect_solana_program_metadata_preflight(
request: &crate::SolanaProgramMetadataPreflightRequest,
) -> ks_core::Result<crate::SolanaProgramMetadataPreflightReport> {
if request.before.len() > crate::MAX_SOLANA_PROGRAM_METADATA_PREFLIGHT_ACCOUNTS {
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_preflight_account_limit_exceeded",
"Solana Program Metadata preflight account limit exceeded",
));
}
let operation_code = request.intent.operation.operation_code();
if request.plan.operation_code != operation_code {
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_preflight_operation_mismatch",
"Program Metadata intent and plan operation codes must match",
));
}
if request.plan.instructions.len() != 1 {
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_preflight_instruction_count_invalid",
"Program Metadata plans must contain exactly one instruction",
));
}
let instruction = match request.plan.instructions.first() {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_preflight_instruction_missing",
"Program Metadata plan instruction is missing",
));
},
};
if instruction.program_id.0 != ks_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID
|| instruction.operation_code != operation_code
|| !ks_lib::EX_METADATA_SPM_SUPPORTED_OPERATION_CODES.contains(&operation_code)
{
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_preflight_instruction_contract_mismatch",
"Program Metadata plan does not preserve the exact program and operation contract",
));
}
if request.intent.operation.requires_explicit_approval()
&& !request.intent.allow_destructive_operation
{
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_preflight_destructive_approval_required",
"destructive Program Metadata execution requires explicit approval",
));
}
let safety = match ks_lib::ExSafetyChecker.evaluate_prepared_plan(&request.plan) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if safety.decision == ks_lib::ExSafetyDecision::Deny {
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_preflight_safety_denied",
safety_violation_codes(safety.violations.as_slice()),
));
}
let mut snapshots = std::collections::BTreeMap::<
std::string::String,
&crate::SolanaProgramMetadataStatefulReadResult,
>::new();
let mut context_slot = 0_u64;
for snapshot in &request.before {
if snapshot.commitment != "confirmed" {
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_preflight_commitment_mismatch",
"Program Metadata stateful snapshots must use confirmed commitment",
));
}
if snapshots.insert(snapshot.account.0.clone(), snapshot).is_some() {
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_preflight_duplicate_account",
format!("duplicate Program Metadata snapshot for {}", snapshot.account.0),
));
}
context_slot = context_slot.max(snapshot.context_slot);
}
let target_account = operation_target(&request.intent.operation).clone();
let target = match snapshots.get(target_account.0.as_str()) {
std::option::Option::Some(value) => *value,
std::option::Option::None => {
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_preflight_target_snapshot_missing",
format!("missing preflight snapshot for {}", target_account.0),
));
},
};
if let std::result::Result::Err(error) =
validate_target_state(&request.intent.operation, target)
{
return std::result::Result::Err(error);
}
if let std::result::Result::Err(error) =
validate_source_state(&request.intent.operation, &snapshots, target)
{
return std::result::Result::Err(error);
}
let runtime_authority_validation_required =
match validate_authority_state(&request.intent.operation, target) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if let std::result::Result::Err(error) = validate_rent_observations(
&request.intent.operation,
target,
&snapshots,
request.rent_observations.as_slice(),
) {
return std::result::Result::Err(error);
}
let inspected_accounts = snapshots
.values()
.map(|value| return value.account.clone())
.collect::<std::vec::Vec<_>>();
tracing::debug!(
target: crate::TRACING_TARGET,
action = "inspect_solana_program_metadata_preflight",
operation_code,
target_account = %target_account.0,
context_slot,
inspected_account_count = inspected_accounts.len(),
runtime_authority_validation_required,
safety_decision = ?safety.decision,
"validated Solana Program Metadata stateful preflight"
);
return std::result::Result::Ok(crate::SolanaProgramMetadataPreflightReport {
operation_code: operation_code.to_string(),
target_account,
context_slot,
inspected_accounts,
safety,
runtime_authority_validation_required,
checks: vec![
"program_id_exact".to_string(),
"operation_code_exact".to_string(),
"single_instruction_plan".to_string(),
"confirmed_state_snapshots".to_string(),
"target_state_compatible".to_string(),
"source_state_compatible".to_string(),
"authority_contract_checked".to_string(),
"rent_prefunding_checked_when_required".to_string(),
"common_safety_checked_before_simulation".to_string(),
],
});
}
fn operation_target(operation: &ks_lib::ExMetadataSpmOperation) -> &ks_lib::MdPubkey {
return match operation {
ks_lib::ExMetadataSpmOperation::Write { buffer, .. }
| ks_lib::ExMetadataSpmOperation::Allocate { buffer, .. } => buffer,
ks_lib::ExMetadataSpmOperation::Initialize { metadata, .. }
| ks_lib::ExMetadataSpmOperation::SetData { metadata, .. }
| ks_lib::ExMetadataSpmOperation::SetImmutable { metadata, .. } => metadata,
ks_lib::ExMetadataSpmOperation::SetAuthority { account, .. }
| ks_lib::ExMetadataSpmOperation::Trim { account, .. }
| ks_lib::ExMetadataSpmOperation::Close { account, .. }
| ks_lib::ExMetadataSpmOperation::Extend { account, .. } => account,
};
}
fn validate_target_state(
operation: &ks_lib::ExMetadataSpmOperation,
target: &crate::SolanaProgramMetadataStatefulReadResult,
) -> ks_core::Result<()> {
let valid = match operation {
ks_lib::ExMetadataSpmOperation::Write { .. } => {
matches!(&target.state, crate::SolanaProgramMetadataObservedAccountState::Buffer(_))
},
ks_lib::ExMetadataSpmOperation::Initialize { data, .. } => {
if data.is_some() {
is_prefunded_uninitialized(target)
} else {
matches!(&target.state, crate::SolanaProgramMetadataObservedAccountState::Buffer(_))
}
},
ks_lib::ExMetadataSpmOperation::SetData { .. }
| ks_lib::ExMetadataSpmOperation::SetImmutable { .. } => {
matches!(&target.state, crate::SolanaProgramMetadataObservedAccountState::Metadata(_))
},
ks_lib::ExMetadataSpmOperation::Allocate { .. } => is_prefunded_uninitialized(target),
ks_lib::ExMetadataSpmOperation::SetAuthority { .. }
| ks_lib::ExMetadataSpmOperation::Trim { .. }
| ks_lib::ExMetadataSpmOperation::Close { .. }
| ks_lib::ExMetadataSpmOperation::Extend { .. } => matches!(
&target.state,
crate::SolanaProgramMetadataObservedAccountState::Buffer(_)
| crate::SolanaProgramMetadataObservedAccountState::Metadata(_)
),
};
if !valid {
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_preflight_target_state_invalid",
format!(
"operation {} cannot use target state {}",
operation.operation_code(),
target.state.code()
),
));
}
return std::result::Result::Ok(());
}
fn is_prefunded_uninitialized(target: &crate::SolanaProgramMetadataStatefulReadResult) -> bool {
return match &target.state {
crate::SolanaProgramMetadataObservedAccountState::Uninitialized {
owner,
lamports,
space: _,
} => {
*lamports > 0
&& (owner.0 == ks_program_ids::SYSTEM_PROGRAM_ID
|| owner.0 == ks_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID)
},
_ => false,
};
}
fn validate_source_state(
operation: &ks_lib::ExMetadataSpmOperation,
snapshots: &std::collections::BTreeMap<
std::string::String,
&crate::SolanaProgramMetadataStatefulReadResult,
>,
target: &crate::SolanaProgramMetadataStatefulReadResult,
) -> ks_core::Result<()> {
match operation {
ks_lib::ExMetadataSpmOperation::Write { offset, source, .. } => {
let target_buffer = match &target.state {
crate::SolanaProgramMetadataObservedAccountState::Buffer(value) => value,
_ => return std::result::Result::Ok(()),
};
let source_length = match source {
ks_lib::ExMetadataSpmWriteSource::Inline { data } => data.len(),
ks_lib::ExMetadataSpmWriteSource::Buffer { source_buffer } => {
let source_snapshot = match snapshots.get(source_buffer.0.as_str()) {
std::option::Option::Some(value) => *value,
std::option::Option::None => {
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_preflight_source_buffer_missing",
format!("missing source Buffer snapshot for {}", source_buffer.0),
));
},
};
match &source_snapshot.state {
crate::SolanaProgramMetadataObservedAccountState::Buffer(value) => {
value.data.len()
},
_ => {
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_preflight_source_buffer_invalid",
"Write source account must be an initialized Buffer",
));
},
}
},
};
let offset = match usize::try_from(*offset) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_preflight_write_offset_invalid",
error.to_string(),
));
},
};
if offset.saturating_add(source_length) > target_buffer.allocated_data_bytes {
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_preflight_write_out_of_bounds",
"Write source exceeds the allocated Buffer capacity",
));
}
},
ks_lib::ExMetadataSpmOperation::SetData {
source: ks_lib::ExMetadataSpmSetDataSource::Buffer { buffer, .. },
..
} => {
let source_snapshot = match snapshots.get(buffer.0.as_str()) {
std::option::Option::Some(value) => *value,
std::option::Option::None => {
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_preflight_source_buffer_missing",
format!("missing source Buffer snapshot for {}", buffer.0),
));
},
};
if !matches!(
&source_snapshot.state,
crate::SolanaProgramMetadataObservedAccountState::Buffer(_)
) {
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_preflight_source_buffer_invalid",
"SetData source account must be an initialized Buffer",
));
}
},
_ => {},
}
return std::result::Result::Ok(());
}
fn validate_authority_state(
operation: &ks_lib::ExMetadataSpmOperation,
target: &crate::SolanaProgramMetadataStatefulReadResult,
) -> ks_core::Result<bool> {
let (authority, has_program_context) = match operation {
ks_lib::ExMetadataSpmOperation::Write { authority: _, .. } => {
return std::result::Result::Ok(false);
},
ks_lib::ExMetadataSpmOperation::Initialize { canonical, .. } => {
return std::result::Result::Ok(*canonical);
},
ks_lib::ExMetadataSpmOperation::Allocate { seed, canonical, .. } => {
return std::result::Result::Ok(seed.is_some() && *canonical);
},
ks_lib::ExMetadataSpmOperation::SetAuthority { authority, program_context, .. }
| ks_lib::ExMetadataSpmOperation::SetData { authority, program_context, .. }
| ks_lib::ExMetadataSpmOperation::SetImmutable { authority, program_context, .. }
| ks_lib::ExMetadataSpmOperation::Trim { authority, program_context, .. }
| ks_lib::ExMetadataSpmOperation::Close { authority, program_context, .. }
| ks_lib::ExMetadataSpmOperation::Extend { authority, program_context, .. } => {
(authority, program_context.is_some())
},
};
let (current_authority, canonical) = match &target.state {
crate::SolanaProgramMetadataObservedAccountState::Buffer(value) => {
(value.authority.as_deref(), value.canonical)
},
crate::SolanaProgramMetadataObservedAccountState::Metadata(value) => {
(value.authority.as_deref(), value.canonical)
},
_ => return std::result::Result::Ok(false),
};
if current_authority == std::option::Option::Some(authority.0.as_str()) {
return std::result::Result::Ok(false);
}
if canonical && has_program_context {
return std::result::Result::Ok(true);
}
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_preflight_authority_mismatch",
"the supplied authority does not match account state and no canonical program context was provided",
));
}
fn validate_rent_observations(
operation: &ks_lib::ExMetadataSpmOperation,
target: &crate::SolanaProgramMetadataStatefulReadResult,
snapshots: &std::collections::BTreeMap<
std::string::String,
&crate::SolanaProgramMetadataStatefulReadResult,
>,
observations: &[crate::SolanaProgramMetadataRentObservation],
) -> ks_core::Result<()> {
let expected_space = match expected_rent_target_space(operation, target, snapshots) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let expected_space = match expected_space {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::result::Result::Ok(()),
};
let observation = match observations.iter().find(|value| return value.account == target.account)
{
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_preflight_rent_observation_missing",
format!("missing rent observation for {}", target.account.0),
));
},
};
let observed_lamports = match target.lamports {
std::option::Option::Some(value) => value,
std::option::Option::None => 0,
};
if observation.target_space != expected_space
|| observation.observed_lamports != observed_lamports
|| !observation.is_satisfied()
{
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_preflight_rent_not_satisfied",
format!(
"account {} has {} lamports but requires {} for exact target space {}",
observation.account.0,
observation.observed_lamports,
observation.required_lamports,
expected_space
),
));
}
return std::result::Result::Ok(());
}
fn expected_rent_target_space(
operation: &ks_lib::ExMetadataSpmOperation,
target: &crate::SolanaProgramMetadataStatefulReadResult,
snapshots: &std::collections::BTreeMap<
std::string::String,
&crate::SolanaProgramMetadataStatefulReadResult,
>,
) -> ks_core::Result<std::option::Option<u64>> {
let header = match u64::try_from(ks_lib::DC_METADATA_SPM_METADATA_HEADER_BYTES) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_preflight_header_size_overflow",
error.to_string(),
));
},
};
return match operation {
ks_lib::ExMetadataSpmOperation::Initialize {
data: std::option::Option::Some(data),
..
} => add_metadata_header(header, metadata_input_wire_len(data)),
ks_lib::ExMetadataSpmOperation::SetData {
source: ks_lib::ExMetadataSpmSetDataSource::PreserveExisting,
..
} => std::result::Result::Ok(std::option::Option::None),
ks_lib::ExMetadataSpmOperation::SetData {
source: ks_lib::ExMetadataSpmSetDataSource::Inline { data },
..
} => add_metadata_header(header, metadata_input_wire_len(data)),
ks_lib::ExMetadataSpmOperation::SetData {
source: ks_lib::ExMetadataSpmSetDataSource::Buffer { buffer, .. },
..
} => {
let source = match snapshots.get(buffer.0.as_str()) {
std::option::Option::Some(value) => *value,
std::option::Option::None => {
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_preflight_source_buffer_missing",
format!("missing source Buffer snapshot for {}", buffer.0),
));
},
};
let length = match &source.state {
crate::SolanaProgramMetadataObservedAccountState::Buffer(value) => {
match u64::try_from(value.data.len()) {
std::result::Result::Ok(length) => length,
std::result::Result::Err(error) => {
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_preflight_source_length_overflow",
error.to_string(),
));
},
}
},
_ => {
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_preflight_source_buffer_invalid",
"SetData source account must be an initialized Buffer",
));
},
};
return add_metadata_header(header, std::result::Result::Ok(length));
},
ks_lib::ExMetadataSpmOperation::Allocate { .. } => {
let current = match target.space {
std::option::Option::Some(value) => value,
std::option::Option::None => 0,
};
std::result::Result::Ok(std::option::Option::Some(current.max(header)))
},
ks_lib::ExMetadataSpmOperation::Extend { length, .. } => {
let current = match target.space {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_preflight_target_space_missing",
"Extend requires the current account space",
));
},
};
return match current.checked_add(u64::from(*length)) {
std::option::Option::Some(value) => {
std::result::Result::Ok(std::option::Option::Some(value))
},
std::option::Option::None => std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_preflight_target_space_overflow",
"Extend target space overflowed u64",
)),
};
},
_ => std::result::Result::Ok(std::option::Option::None),
};
}
fn metadata_input_wire_len(data: &ks_lib::ExMetadataSpmDataInput) -> ks_core::Result<u64> {
let length = match data {
ks_lib::ExMetadataSpmDataInput::Direct { bytes } => bytes.len(),
ks_lib::ExMetadataSpmDataInput::Url { url } => url.len(),
ks_lib::ExMetadataSpmDataInput::External { .. } => {
ks_lib::DC_METADATA_SPM_EXTERNAL_DATA_BYTES
},
};
return match u64::try_from(length) {
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(error) => std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_preflight_data_length_overflow",
error.to_string(),
)),
};
}
fn add_metadata_header(
header: u64,
data_length: ks_core::Result<u64>,
) -> ks_core::Result<std::option::Option<u64>> {
let data_length = match data_length {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return match header.checked_add(data_length) {
std::option::Option::Some(value) => {
std::result::Result::Ok(std::option::Option::Some(value))
},
std::option::Option::None => std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_preflight_target_space_overflow",
"Program Metadata target space overflowed u64",
)),
};
}
fn safety_violation_codes(violations: &[ks_lib::ExSafetyViolation]) -> std::string::String {
if violations.is_empty() {
return "Program Metadata execution safety denied without a diagnostic".to_string();
}
return violations
.iter()
.map(|value| return value.code.as_str())
.collect::<std::vec::Vec<_>>()
.join(",");
}
#[cfg(test)]
mod tests {
fn pubkey(value: u8) -> ks_lib::MdPubkey {
return ks_lib::MdPubkey(solana_pubkey::Pubkey::new_from_array([value; 32]).to_string());
}
fn policy(authority: &ks_lib::MdPubkey) -> ks_lib::ExApiExecutionPolicy {
return ks_lib::ExApiExecutionPolicy {
cost_limit: ks_lib::ExApiExecutionCostLimit {
max_spend_lamports: std::option::Option::Some(0),
max_fee_lamports: std::option::Option::Some(20_000),
max_compute_unit_price_micro_lamports: std::option::Option::None,
},
authorized_signers: vec![authority.clone()],
post_execution_validation: ks_lib::ExApiPostExecutionValidationPolicy {
canonical_insert_required: true,
core_extraction_required: true,
decode_replay_required: true,
materialization_required: true,
},
..ks_lib::ExApiExecutionPolicy::default()
};
}
fn buffer_read(
account: ks_lib::MdPubkey,
authority: ks_lib::MdPubkey,
) -> crate::SolanaProgramMetadataStatefulReadResult {
return crate::SolanaProgramMetadataStatefulReadResult {
commitment: "confirmed".to_string(),
context_slot: 42,
account: account.clone(),
lamports: std::option::Option::Some(1_000),
owner: std::option::Option::Some(ks_lib::MdProgramId(
ks_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID.to_string(),
)),
space: std::option::Option::Some(100),
state: crate::SolanaProgramMetadataObservedAccountState::Buffer(
ks_lib::DcMetadataSpmBufferAccountSnapshot {
account: account.0,
program: std::option::Option::None,
authority: std::option::Option::Some(authority.0),
canonical: false,
seed: ks_lib::DcMetadataSpmSeed::default(),
data: vec![0_u8; 4],
allocated_data_bytes: 4,
},
),
materialized_output: std::option::Option::None,
};
}
#[test]
fn write_preflight_requires_buffer_capacity_and_common_safety() {
let authority = pubkey(2);
let buffer = pubkey(3);
let intent = ks_lib::ExMetadataSpmExecutionIntent {
intent_id: "spm-preflight-write".to_string(),
fee_payer: authority.clone(),
policy: policy(&authority),
allow_destructive_operation: true,
operation: ks_lib::ExMetadataSpmOperation::Write {
buffer: buffer.clone(),
authority: authority.clone(),
offset: 2,
source: ks_lib::ExMetadataSpmWriteSource::Inline { data: vec![8, 9] },
},
};
let plan = ks_lib::ExApiTypedInstructionExecutor::build_prepared_plan(
&ks_lib::ExMetadataSolanaProgramMetadataExecutor,
&intent,
);
assert!(plan.is_ok());
let plan = match plan {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let report = crate::inspect_solana_program_metadata_preflight(
&crate::SolanaProgramMetadataPreflightRequest {
intent,
plan,
before: vec![buffer_read(buffer, authority)],
rent_observations: vec![],
},
);
assert!(report.is_ok());
if let std::result::Result::Ok(value) = report {
assert_eq!(value.operation_code, ks_lib::EX_METADATA_SPM_WRITE_OPERATION);
assert_eq!(value.safety.decision, ks_lib::ExSafetyDecision::Allow);
}
}
#[test]
fn allocate_requires_vacancy_and_explicit_rent_evidence() {
let authority = pubkey(4);
let buffer = authority.clone();
let intent = ks_lib::ExMetadataSpmExecutionIntent {
intent_id: "spm-preflight-allocate".to_string(),
fee_payer: authority.clone(),
policy: policy(&authority),
allow_destructive_operation: false,
operation: ks_lib::ExMetadataSpmOperation::Allocate {
buffer: buffer.clone(),
authority: authority.clone(),
seed: std::option::Option::None,
program_context: std::option::Option::None,
canonical: false,
allocate_account: true,
},
};
let plan = ks_lib::ExApiTypedInstructionExecutor::build_prepared_plan(
&ks_lib::ExMetadataSolanaProgramMetadataExecutor,
&intent,
);
assert!(plan.is_ok());
let plan = match plan {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let before = crate::SolanaProgramMetadataStatefulReadResult {
commitment: "confirmed".to_string(),
context_slot: 9,
account: buffer.clone(),
lamports: std::option::Option::Some(1_000),
owner: std::option::Option::Some(ks_lib::MdProgramId(
ks_program_ids::SYSTEM_PROGRAM_ID.to_string(),
)),
space: std::option::Option::Some(0),
state: crate::SolanaProgramMetadataObservedAccountState::Uninitialized {
owner: ks_lib::MdProgramId(ks_program_ids::SYSTEM_PROGRAM_ID.to_string()),
lamports: 1_000,
space: 0,
},
materialized_output: std::option::Option::None,
};
let missing_rent = crate::inspect_solana_program_metadata_preflight(
&crate::SolanaProgramMetadataPreflightRequest {
intent: intent.clone(),
plan: plan.clone(),
before: vec![before.clone()],
rent_observations: vec![],
},
);
assert!(missing_rent.is_err());
let target_space = match u64::try_from(ks_lib::DC_METADATA_SPM_BUFFER_HEADER_BYTES) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let report = crate::inspect_solana_program_metadata_preflight(
&crate::SolanaProgramMetadataPreflightRequest {
intent,
plan,
before: vec![before],
rent_observations: vec![crate::SolanaProgramMetadataRentObservation {
account: buffer,
target_space,
observed_lamports: 1_000,
required_lamports: 900,
}],
},
);
assert!(report.is_ok());
}
}

View File

@@ -0,0 +1,442 @@
// file: ks-pipeline/src/metadata_solana_program_stateful.rs
// version: 5
//! Bounded Solana Program Metadata account reads and authoritative state projections.
/// Maximum complete Solana Program Metadata account data accepted by one RPC read.
///
/// The decoder can validate larger account byte arrays supplied by offline or chunked sources, but
/// the current execution RPC adapter intentionally bounds one complete `getAccountInfo` response.
pub const MAX_SOLANA_PROGRAM_METADATA_STATEFUL_ACCOUNT_BYTES: usize =
ks_onchain_transport::MAX_COMPLETE_ACCOUNT_DATA_BYTES;
/// Expected state of one Solana Program Metadata account read.
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum SolanaProgramMetadataExpectedAccountState {
/// Accept every supported state.
Any,
/// Accept an absent or zeroed account before initialization.
Vacant,
/// Require an absent account.
Missing,
/// Require an initialized Buffer account.
Buffer,
/// Require an initialized Metadata account.
Metadata,
}
/// Supported state observed for one Solana Program Metadata account.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(tag = "state", content = "snapshot", rename_all = "snake_case")]
pub enum SolanaProgramMetadataObservedAccountState {
/// The address does not currently exist.
Missing,
/// The address exists but contains only zeroed or empty data and is not initialized.
Uninitialized {
/// Current owner before Program Metadata assignment.
owner: ks_lib::MdProgramId,
/// Current lamports retained by the account.
lamports: u64,
/// Current allocated account-data bytes.
space: u64,
},
/// Decoded Program Metadata Buffer account.
Buffer(ks_lib::DcMetadataSpmBufferAccountSnapshot),
/// Decoded Program Metadata Metadata account.
Metadata(ks_lib::DcMetadataSpmMetadataAccountSnapshot),
}
impl crate::SolanaProgramMetadataObservedAccountState {
/// Returns the stable lowercase state code.
pub const fn code(&self) -> &'static str {
return match self {
Self::Missing => "missing",
Self::Uninitialized { owner: _, lamports: _, space: _ } => "uninitialized",
Self::Buffer(_) => "buffer",
Self::Metadata(_) => "metadata",
};
}
/// Returns whether the state is absent or uninitialized.
pub const fn is_vacant(&self) -> bool {
return matches!(
self,
Self::Missing | Self::Uninitialized { owner: _, lamports: _, space: _ }
);
}
}
/// One bounded Solana Program Metadata account read request.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct SolanaProgramMetadataStatefulReadRequest {
/// Endpoint role used for the HTTP RPC request.
pub query_role: std::string::String,
/// Account address to inspect.
pub account: ks_lib::MdPubkey,
/// Expected account state.
pub expected_state: crate::SolanaProgramMetadataExpectedAccountState,
/// Optional minimum RPC context slot.
pub min_context_slot: std::option::Option<u64>,
/// Maximum decoded account bytes accepted from the endpoint.
pub max_data_bytes: usize,
}
impl crate::SolanaProgramMetadataStatefulReadRequest {
/// Validates the bounded request.
pub fn validate(&self) -> ks_core::Result<()> {
if self.query_role.trim().is_empty() {
return std::result::Result::Err(ks_core::Error::config(
"Solana Program Metadata stateful query_role must not be empty",
));
}
let account_result = ks_onchain_transport::validate_solana_pubkey_text(
self.account.0.as_str(),
"Solana Program Metadata stateful account",
);
if let std::result::Result::Err(error) = account_result {
return std::result::Result::Err(error);
}
if self.max_data_bytes == 0
|| self.max_data_bytes > crate::MAX_SOLANA_PROGRAM_METADATA_STATEFUL_ACCOUNT_BYTES
{
return std::result::Result::Err(ks_core::Error::config(format!(
"Solana Program Metadata stateful max_data_bytes must be between 1 and {}",
crate::MAX_SOLANA_PROGRAM_METADATA_STATEFUL_ACCOUNT_BYTES
)));
}
return std::result::Result::Ok(());
}
}
/// One bounded RPC read and its validated Program Metadata state.
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct SolanaProgramMetadataStatefulReadResult {
/// Commitment used by the read.
pub commitment: std::string::String,
/// Context slot returned by the endpoint.
pub context_slot: u64,
/// Account address requested by the caller.
pub account: ks_lib::MdPubkey,
/// Current lamports when the account exists.
pub lamports: std::option::Option<u64>,
/// Current owner when the account exists.
pub owner: std::option::Option<ks_lib::MdProgramId>,
/// Current account-data allocation when the account exists.
pub space: std::option::Option<u64>,
/// Validated account state.
pub state: crate::SolanaProgramMetadataObservedAccountState,
/// Authoritative materialized projection for Buffer or Metadata state.
pub materialized_output: std::option::Option<ks_lib::MtApiMaterializedOutput>,
}
/// Reads one bounded Solana Program Metadata account.
pub async fn read_solana_program_metadata_stateful_snapshot(
pool: &ks_onchain_transport::HttpEndpointPool,
request: &crate::SolanaProgramMetadataStatefulReadRequest,
) -> ks_core::Result<crate::SolanaProgramMetadataStatefulReadResult> {
if let std::result::Result::Err(error) = request.validate() {
return std::result::Result::Err(error);
}
let config = match ks_onchain_transport::GetAccountInfoConfig::new_with_data(
ks_onchain_transport::RpcCommitmentLevel::Confirmed,
request.min_context_slot,
request.max_data_bytes,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let result = match pool
.get_account_info_for_role(request.query_role.as_str(), &request.account, &config)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return crate::materialize_solana_program_metadata_account_info_result(request, &result);
}
/// Validates one RPC account response, decodes its state and emits the authoritative projection.
pub fn materialize_solana_program_metadata_account_info_result(
request: &crate::SolanaProgramMetadataStatefulReadRequest,
result: &ks_onchain_transport::AccountInfoResult,
) -> ks_core::Result<crate::SolanaProgramMetadataStatefulReadResult> {
if let std::result::Result::Err(error) = request.validate() {
return std::result::Result::Err(error);
}
if let std::option::Option::Some(minimum) = request.min_context_slot {
if result.context.slot < minimum {
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_stateful_context_slot_too_old",
"Solana Program Metadata account context slot is below the requested minimum",
));
}
}
let output = match result.account.as_ref() {
std::option::Option::None => crate::SolanaProgramMetadataStatefulReadResult {
commitment: "confirmed".to_string(),
context_slot: result.context.slot,
account: request.account.clone(),
lamports: std::option::Option::None,
owner: std::option::Option::None,
space: std::option::Option::None,
state: crate::SolanaProgramMetadataObservedAccountState::Missing,
materialized_output: std::option::Option::None,
},
std::option::Option::Some(account) => {
if account.executable {
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_stateful_account_executable",
"Solana Program Metadata state accounts must not be executable",
));
}
let data_len = match u64::try_from(account.data.len()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_stateful_account_length_overflow",
error.to_string(),
));
},
};
if account.space != data_len || account.data.len() > request.max_data_bytes {
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_stateful_account_data_invalid",
"Solana Program Metadata account data is incomplete or above the configured bound",
));
}
let zeroed = account.data.iter().all(|byte| return *byte == 0);
if account.data.is_empty() || zeroed {
crate::SolanaProgramMetadataStatefulReadResult {
commitment: "confirmed".to_string(),
context_slot: result.context.slot,
account: request.account.clone(),
lamports: std::option::Option::Some(account.lamports),
owner: std::option::Option::Some(account.owner.clone()),
space: std::option::Option::Some(account.space),
state: crate::SolanaProgramMetadataObservedAccountState::Uninitialized {
owner: account.owner.clone(),
lamports: account.lamports,
space: account.space,
},
materialized_output: std::option::Option::None,
}
} else {
if account.owner.0 != ks_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID {
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_stateful_owner_mismatch",
"initialized Solana Program Metadata state must be owned by ProgM6",
));
}
let snapshot = match ks_lib::decoder_metadata_solana_program_metadata_decode_account(
request.account.0.as_str(),
account.owner.0.as_str(),
account.data.as_slice(),
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_stateful_decode_failed",
error.to_string(),
));
},
};
let materialized = match ks_lib::materializer_metadata_materialize_solana_program_metadata_account_snapshot(
result.context.slot,
&snapshot,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_stateful_materialization_failed",
error,
));
},
};
let state = match snapshot {
ks_lib::DcMetadataSpmAccountSnapshot::Buffer(value) => {
crate::SolanaProgramMetadataObservedAccountState::Buffer(value)
},
ks_lib::DcMetadataSpmAccountSnapshot::Metadata(value) => {
crate::SolanaProgramMetadataObservedAccountState::Metadata(value)
},
};
crate::SolanaProgramMetadataStatefulReadResult {
commitment: "confirmed".to_string(),
context_slot: result.context.slot,
account: request.account.clone(),
lamports: std::option::Option::Some(account.lamports),
owner: std::option::Option::Some(account.owner.clone()),
space: std::option::Option::Some(account.space),
state,
materialized_output: std::option::Option::Some(materialized),
}
}
},
};
if !expected_state_matches(request.expected_state, &output.state) {
return std::result::Result::Err(ks_core::Error::new(
"metadata_solana_program_stateful_expected_state_mismatch",
format!(
"expected {:?}, observed {} for {}",
request.expected_state,
output.state.code(),
request.account.0
),
));
}
tracing::debug!(
target: crate::TRACING_TARGET,
action = "read_solana_program_metadata_state",
account = %request.account.0,
context_slot = result.context.slot,
observed_state = output.state.code(),
materialized = output.materialized_output.is_some(),
"validated Solana Program Metadata account state"
);
return std::result::Result::Ok(output);
}
fn expected_state_matches(
expected: crate::SolanaProgramMetadataExpectedAccountState,
observed: &crate::SolanaProgramMetadataObservedAccountState,
) -> bool {
return match expected {
crate::SolanaProgramMetadataExpectedAccountState::Any => true,
crate::SolanaProgramMetadataExpectedAccountState::Vacant => observed.is_vacant(),
crate::SolanaProgramMetadataExpectedAccountState::Missing => {
matches!(observed, crate::SolanaProgramMetadataObservedAccountState::Missing)
},
crate::SolanaProgramMetadataExpectedAccountState::Buffer => {
matches!(observed, crate::SolanaProgramMetadataObservedAccountState::Buffer(_))
},
crate::SolanaProgramMetadataExpectedAccountState::Metadata => {
matches!(observed, crate::SolanaProgramMetadataObservedAccountState::Metadata(_))
},
};
}
#[cfg(test)]
mod tests {
fn request(
expected_state: crate::SolanaProgramMetadataExpectedAccountState,
) -> crate::SolanaProgramMetadataStatefulReadRequest {
return crate::SolanaProgramMetadataStatefulReadRequest {
query_role: "http_queries".to_string(),
account: ks_lib::MdPubkey(
solana_pubkey::Pubkey::new_from_array([3_u8; 32]).to_string(),
),
expected_state,
min_context_slot: std::option::Option::None,
max_data_bytes: 1024,
};
}
#[test]
fn missing_and_zeroed_accounts_are_distinguished_and_bounded() {
let missing = ks_onchain_transport::AccountInfoResult {
context: ks_onchain_transport::RpcResponseContext {
slot: 10,
api_version: std::option::Option::None,
},
account: std::option::Option::None,
};
let missing_result = crate::materialize_solana_program_metadata_account_info_result(
&request(crate::SolanaProgramMetadataExpectedAccountState::Vacant),
&missing,
);
assert!(matches!(
missing_result.as_ref().map(|value| return &value.state),
std::result::Result::Ok(crate::SolanaProgramMetadataObservedAccountState::Missing)
));
let zeroed = ks_onchain_transport::AccountInfoResult {
context: ks_onchain_transport::RpcResponseContext {
slot: 11,
api_version: std::option::Option::None,
},
account: std::option::Option::Some(ks_onchain_transport::AccountInfoValue {
lamports: 100,
owner: ks_lib::MdProgramId(ks_program_ids::SYSTEM_PROGRAM_ID.to_string()),
executable: false,
rent_epoch: 0,
space: 96,
data: vec![0_u8; 96],
}),
};
let zeroed_result = crate::materialize_solana_program_metadata_account_info_result(
&request(crate::SolanaProgramMetadataExpectedAccountState::Vacant),
&zeroed,
);
assert!(matches!(
zeroed_result.as_ref().map(|value| return &value.state),
std::result::Result::Ok(
crate::SolanaProgramMetadataObservedAccountState::Uninitialized { .. }
)
));
}
#[test]
fn buffer_state_is_decoded_and_materialized_authoritatively() {
let program = solana_pubkey::Pubkey::new_from_array([4_u8; 32]);
let authority = solana_pubkey::Pubkey::new_from_array([5_u8; 32]);
let mut data = vec![0_u8; ks_lib::DC_METADATA_SPM_BUFFER_HEADER_BYTES];
data[0] = ks_lib::DcMetadataSpmAccountDiscriminator::Buffer.wire_value();
data[1..33].copy_from_slice(&program.to_bytes());
data[33..65].copy_from_slice(&authority.to_bytes());
data[65] = 1;
data[66..82].fill(7);
data.extend_from_slice(&[8_u8, 9_u8]);
let space = match u64::try_from(data.len()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
let result = ks_onchain_transport::AccountInfoResult {
context: ks_onchain_transport::RpcResponseContext {
slot: 12,
api_version: std::option::Option::None,
},
account: std::option::Option::Some(ks_onchain_transport::AccountInfoValue {
lamports: 1_000,
owner: ks_lib::MdProgramId(
ks_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID.to_string(),
),
executable: false,
rent_epoch: 0,
space,
data,
}),
};
let output = crate::materialize_solana_program_metadata_account_info_result(
&request(crate::SolanaProgramMetadataExpectedAccountState::Buffer),
&result,
);
assert!(output.is_ok());
if let std::result::Result::Ok(value) = output {
assert!(matches!(
value.state,
crate::SolanaProgramMetadataObservedAccountState::Buffer(_)
));
assert_eq!(
value
.materialized_output
.as_ref()
.map(|item| return item.payload_json["projectionKind"].clone()),
std::option::Option::Some(serde_json::json!("buffer_state"))
);
}
}
#[test]
fn stateful_complete_read_bound_matches_the_transport_contract() {
assert_eq!(
crate::MAX_SOLANA_PROGRAM_METADATA_STATEFUL_ACCOUNT_BYTES,
ks_onchain_transport::MAX_COMPLETE_ACCOUNT_DATA_BYTES
);
const {
assert!(
crate::MAX_SOLANA_PROGRAM_METADATA_STATEFUL_ACCOUNT_BYTES
< ks_lib::DC_METADATA_SPM_MAX_ACCOUNT_BYTES
);
}
}
}

44
ks-pipeline/src/plan.rs Normal file
View File

@@ -0,0 +1,44 @@
// file: ks-pipeline/src/plan.rs
// version: 3
//! Pipeline planning primitives.
/// Pipeline stage identifier.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum PipelineStage {
/// Ingest raw transactions.
Ingest,
/// Extract generic Solana structures.
Extract,
/// Build program observations.
Observe,
/// Decode protocol events.
Decode,
/// Materialize business events.
Materialize,
/// Aggregate materialized events.
Aggregate,
/// Validate outputs.
Validate,
}
/// Replay selection scope.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ReplayScope {
/// Optional module name.
pub module_name: std::option::Option<std::string::String>,
/// Optional module version.
pub module_version: std::option::Option<std::string::String>,
/// Optional program id.
pub program_id: std::option::Option<std::string::String>,
/// Optional surface code.
pub surface_code: std::option::Option<std::string::String>,
/// Optional 8-byte discriminator in hexadecimal.
pub discriminator_8: std::option::Option<std::string::String>,
/// Optional inclusive start slot.
pub slot_start: std::option::Option<u64>,
/// Optional inclusive end slot.
pub slot_end: std::option::Option<u64>,
/// Whether to ignore existing ledger rows.
pub force: bool,
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,393 @@
// file: ks-pipeline/src/spl_elgamal_registry_stateful.rs
// version: 6
//! Contextual SPL ElGamal registry account-state validation and materialization routing.
use std::str::FromStr; // rust-rules: trait-import
/// Exact byte length of one SPL ElGamal registry account.
pub const ELGAMAL_REGISTRY_STATEFUL_ACCOUNT_BYTES: usize = 64;
/// One bounded SPL ElGamal registry RPC read request.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ElGamalRegistryStatefulReadRequest {
/// Endpoint role used for the HTTP RPC request.
pub query_role: std::string::String,
/// Canonical registry PDA.
pub registry_account: ks_lib::MdPubkey,
/// Optional minimum RPC context slot.
pub min_context_slot: std::option::Option<u64>,
}
/// One bounded registry RPC read and validated administrative projection.
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct ElGamalRegistryStatefulReadResult {
/// Commitment used for the RPC read.
pub commitment: std::string::String,
/// Context slot returned by the endpoint.
pub context_slot: u64,
/// Complete validated registry snapshot.
pub snapshot: crate::ElGamalRegistryStatefulSnapshot,
}
/// Reads, validates, and routes one exact SPL ElGamal registry account.
pub async fn read_elgamal_registry_stateful_snapshot(
pool: &ks_onchain_transport::HttpEndpointPool,
request: &crate::ElGamalRegistryStatefulReadRequest,
) -> ks_core::Result<crate::ElGamalRegistryStatefulReadResult> {
if request.query_role.trim().is_empty() {
return std::result::Result::Err(ks_core::Error::config(
"SPL ElGamal registry stateful read query_role must not be empty",
));
}
let config = match ks_onchain_transport::GetAccountInfoConfig::new_with_data(
ks_onchain_transport::RpcCommitmentLevel::Confirmed,
request.min_context_slot,
crate::ELGAMAL_REGISTRY_STATEFUL_ACCOUNT_BYTES,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let result = match pool
.get_account_info_for_role(request.query_role.as_str(), &request.registry_account, &config)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return crate::materialize_elgamal_registry_account_info_result(request, &result);
}
/// Validates one complete RPC registry response before parsing and routing.
pub fn materialize_elgamal_registry_account_info_result(
request: &crate::ElGamalRegistryStatefulReadRequest,
result: &ks_onchain_transport::AccountInfoResult,
) -> ks_core::Result<crate::ElGamalRegistryStatefulReadResult> {
if let std::option::Option::Some(min_context_slot) = request.min_context_slot {
if result.context.slot < min_context_slot {
return std::result::Result::Err(ks_core::Error::new(
"elgamal_registry_stateful_context_slot_too_old",
format!(
"SPL ElGamal registry context slot {} is below requested minimum {min_context_slot}",
result.context.slot
),
));
}
}
let account = match result.account.as_ref() {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(ks_core::Error::new(
"elgamal_registry_stateful_account_missing",
format!(
"SPL ElGamal registry account {} does not exist",
request.registry_account.0
),
));
},
};
if account.executable {
return std::result::Result::Err(ks_core::Error::new(
"elgamal_registry_stateful_account_executable",
format!(
"SPL ElGamal registry state account {} must not be executable",
request.registry_account.0
),
));
}
if account.owner.0 != ks_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID {
return std::result::Result::Err(ks_core::Error::new(
"elgamal_registry_stateful_owner_mismatch",
format!(
"SPL ElGamal registry owner must be {}, got {}",
ks_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID,
account.owner.0
),
));
}
if account.space != crate::ELGAMAL_REGISTRY_STATEFUL_ACCOUNT_BYTES as u64
|| account.data.len() != crate::ELGAMAL_REGISTRY_STATEFUL_ACCOUNT_BYTES
{
return std::result::Result::Err(ks_core::Error::new(
"elgamal_registry_stateful_account_length_invalid",
format!(
"SPL ElGamal registry account must report and return exactly {} bytes, got space {} and data {}",
crate::ELGAMAL_REGISTRY_STATEFUL_ACCOUNT_BYTES,
account.space,
account.data.len()
),
));
}
let snapshot = match crate::materialize_elgamal_registry_stateful_snapshot(
request.registry_account.0.as_str(),
account.owner.0.as_str(),
result.context.slot,
account.data.as_slice(),
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(ks_core::Error::new(
"elgamal_registry_stateful_projection_failed",
error,
));
},
};
return std::result::Result::Ok(crate::ElGamalRegistryStatefulReadResult {
commitment: "confirmed".to_string(),
context_slot: result.context.slot,
snapshot,
});
}
/// One contextually validated SPL ElGamal registry state snapshot.
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct ElGamalRegistryStatefulSnapshot {
/// Canonical registry PDA identity.
pub registry_account_key: std::string::String,
/// Wallet address stored as the registry owner.
pub owner: std::string::String,
/// Context slot associated with the account read.
pub slot: u64,
/// Processor-owned administrative projection.
pub output: ks_lib::MtApiMaterializedOutput,
}
/// Parse, validate the official owner-derived PDA, and materialize one registry snapshot.
pub fn materialize_elgamal_registry_stateful_snapshot(
registry_account_key: &str,
owner_program_id: &str,
slot: u64,
data: &[u8],
) -> std::result::Result<crate::ElGamalRegistryStatefulSnapshot, String> {
if owner_program_id != ks_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID {
return std::result::Result::Err(format!(
"SPL ElGamal registry snapshot owner must be {}, got {owner_program_id}",
ks_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID
));
}
let state = match ks_lib::decoder_spl_elgamal_registry_parse_elgamal_registry_state(data) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let owner = match solana_pubkey::Pubkey::from_str(state.owner.as_str()) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(format!(
"SPL ElGamal registry owner is not a valid address: {error}"
));
},
};
let program_id = match solana_pubkey::Pubkey::from_str(
ks_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(format!(
"Configured SPL ElGamal registry program ID is invalid: {error}"
));
},
};
let expected =
spl_elgamal_registry_interface::get_elgamal_registry_address(&owner, &program_id);
if expected.to_string() != registry_account_key {
return std::result::Result::Err(format!(
"SPL ElGamal registry account {registry_account_key} does not match owner-derived PDA {expected}"
));
}
let output = match ks_lib::materializer_admin_materialize_elgamal_registry_state_snapshot(
registry_account_key,
slot,
&state,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return std::result::Result::Ok(crate::ElGamalRegistryStatefulSnapshot {
registry_account_key: registry_account_key.to_string(),
owner: state.owner,
slot,
output,
});
}
#[cfg(test)]
mod tests {
use std::str::FromStr; // rust-rules: trait-import
fn registry_fixture(owner: &solana_pubkey::Pubkey) -> (std::string::String, [u8; 64]) {
let program_id = match solana_pubkey::Pubkey::from_str(
ks_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => solana_pubkey::Pubkey::default(),
};
let registry =
spl_elgamal_registry_interface::get_elgamal_registry_address(owner, &program_id);
let mut data = [0u8; 64];
data[..32].copy_from_slice(owner.as_ref());
data[32..].copy_from_slice(&[9u8; 32]);
return (registry.to_string(), data);
}
#[test]
fn exact_owner_derived_registry_pda_routes_one_admin_snapshot() {
let owner = solana_pubkey::Pubkey::new_from_array([7u8; 32]);
let (registry, data) = registry_fixture(&owner);
let snapshot = crate::materialize_elgamal_registry_stateful_snapshot(
registry.as_str(),
ks_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID,
55,
&data,
);
assert_eq!(
snapshot.as_ref().map(|value| return value.owner.clone()),
std::result::Result::Ok(owner.to_string())
);
assert_eq!(
snapshot
.as_ref()
.map(|value| return value.output.payload_json["finalAccountStateCaptured"].clone()),
std::result::Result::Ok(serde_json::json!(true))
);
}
#[test]
fn foreign_owner_program_wrong_pda_and_invalid_length_fail_closed() {
let owner = solana_pubkey::Pubkey::new_from_array([8u8; 32]);
let (registry, data) = registry_fixture(&owner);
let foreign = crate::materialize_elgamal_registry_stateful_snapshot(
registry.as_str(),
ks_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
55,
&data,
);
assert!(foreign.is_err());
let wrong = crate::materialize_elgamal_registry_stateful_snapshot(
solana_pubkey::Pubkey::new_from_array([10u8; 32]).to_string().as_str(),
ks_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID,
55,
&data,
);
assert!(wrong.is_err());
let invalid = crate::materialize_elgamal_registry_stateful_snapshot(
registry.as_str(),
ks_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID,
55,
&data[..63],
);
assert!(invalid.is_err());
}
#[test]
fn exact_rpc_registry_routes_after_context_owner_and_length_validation() {
let owner = solana_pubkey::Pubkey::new_from_array([13u8; 32]);
let (registry, data) = registry_fixture(&owner);
let request = crate::ElGamalRegistryStatefulReadRequest {
query_role: "execution".to_string(),
registry_account: ks_lib::MdPubkey(registry),
min_context_slot: std::option::Option::Some(70),
};
let result = ks_onchain_transport::AccountInfoResult {
context: ks_onchain_transport::RpcResponseContext {
slot: 71,
api_version: std::option::Option::None,
},
account: std::option::Option::Some(ks_onchain_transport::AccountInfoValue {
lamports: 1,
owner: ks_lib::MdProgramId(
ks_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID.to_string(),
),
executable: false,
rent_epoch: 0,
space: 64,
data: data.to_vec(),
}),
};
let materialized =
crate::materialize_elgamal_registry_account_info_result(&request, &result);
assert_eq!(
materialized.as_ref().map(|value| return value.context_slot),
std::result::Result::Ok(71)
);
assert_eq!(
materialized.as_ref().map(|value| return value.snapshot.owner.clone()),
std::result::Result::Ok(owner.to_string())
);
}
#[test]
fn stale_missing_foreign_executable_and_wrong_length_registry_reads_fail_closed() {
let owner = solana_pubkey::Pubkey::new_from_array([14u8; 32]);
let (registry, data) = registry_fixture(&owner);
let request = crate::ElGamalRegistryStatefulReadRequest {
query_role: "execution".to_string(),
registry_account: ks_lib::MdPubkey(registry),
min_context_slot: std::option::Option::Some(80),
};
let base = ks_onchain_transport::AccountInfoValue {
lamports: 1,
owner: ks_lib::MdProgramId(
ks_program_ids::SPL_TOKEN_2022_ELGAMAL_REGISTRY_PROGRAM_ID.to_string(),
),
executable: false,
rent_epoch: 0,
space: 64,
data: data.to_vec(),
};
let stale = ks_onchain_transport::AccountInfoResult {
context: ks_onchain_transport::RpcResponseContext {
slot: 79,
api_version: std::option::Option::None,
},
account: std::option::Option::Some(base.clone()),
};
assert!(crate::materialize_elgamal_registry_account_info_result(&request, &stale).is_err());
let missing = ks_onchain_transport::AccountInfoResult {
context: ks_onchain_transport::RpcResponseContext {
slot: 80,
api_version: std::option::Option::None,
},
account: std::option::Option::None,
};
assert!(
crate::materialize_elgamal_registry_account_info_result(&request, &missing).is_err()
);
let mut foreign = base.clone();
foreign.owner = ks_lib::MdProgramId(ks_program_ids::SPL_TOKEN_2022_PROGRAM_ID.to_string());
let foreign = ks_onchain_transport::AccountInfoResult {
context: ks_onchain_transport::RpcResponseContext {
slot: 80,
api_version: std::option::Option::None,
},
account: std::option::Option::Some(foreign),
};
assert!(
crate::materialize_elgamal_registry_account_info_result(&request, &foreign).is_err()
);
let mut executable = base.clone();
executable.executable = true;
let executable = ks_onchain_transport::AccountInfoResult {
context: ks_onchain_transport::RpcResponseContext {
slot: 80,
api_version: std::option::Option::None,
},
account: std::option::Option::Some(executable),
};
assert!(
crate::materialize_elgamal_registry_account_info_result(&request, &executable).is_err()
);
let mut wrong_length = base;
wrong_length.data.pop();
let wrong_length = ks_onchain_transport::AccountInfoResult {
context: ks_onchain_transport::RpcResponseContext {
slot: 80,
api_version: std::option::Option::None,
},
account: std::option::Option::Some(wrong_length),
};
assert!(
crate::materialize_elgamal_registry_account_info_result(&request, &wrong_length)
.is_err()
);
}
}

View File

@@ -0,0 +1,242 @@
// 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());
}
}

View File

@@ -0,0 +1,319 @@
// file: ks-pipeline/src/spl_token_2022_crypto_preflight.rs
// version: 5
//! Bounded cryptographic preflight for Token-2022 proof context-state accounts.
/// Maximum number of distinct proof context-state accounts accepted by one request.
pub const MAX_TOKEN_2022_PROOF_CONTEXTS: usize = 8;
/// Generic metadata bytes retained by every ZK ElGamal proof context-state account.
pub const ZK_PROOF_CONTEXT_META_BYTES: usize = 33;
/// One exact pre-verified proof context-state requirement.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Token2022ProofContextRequirement {
/// Stable semantic role used in diagnostics.
pub role: std::string::String,
/// Canonical proof context-state account.
pub account: ks_lib::MdPubkey,
/// Exact proof kind expected by the Token-2022 builder.
pub proof_type: ks_lib::ExSolanaCoreZkElGamalProofType,
/// Optional authority retained by the context-state account.
pub expected_authority: std::option::Option<ks_lib::MdPubkey>,
}
/// One bounded cryptographic preflight request.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Token2022CryptographicPreflightRequest {
/// Endpoint role used by every account read.
pub query_role: std::string::String,
/// Optional minimum RPC context slot.
pub min_context_slot: std::option::Option<u64>,
/// Ordered proof context-state requirements.
pub proof_contexts: std::vec::Vec<Token2022ProofContextRequirement>,
}
/// One validated proof context-state result.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct Token2022ProofContextReport {
/// Stable semantic role.
pub role: std::string::String,
/// Canonical proof context-state account.
pub account: ks_lib::MdPubkey,
/// Official one-byte proof discriminator.
pub proof_discriminator: u8,
/// Exact context-state size for the proof type.
pub context_state_bytes: usize,
/// Context slot returned by the endpoint.
pub context_slot: u64,
/// Ordered successful checks.
pub checks: std::vec::Vec<std::string::String>,
}
/// Complete cryptographic preflight report.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct Token2022CryptographicPreflightReport {
/// Commitment used for all reads.
pub commitment: std::string::String,
/// Highest context slot observed across all reads.
pub context_slot: u64,
/// Ordered validated proof contexts.
pub proof_contexts: std::vec::Vec<Token2022ProofContextReport>,
}
/// Reads and validates all pre-verified proof context-state accounts required by one operation.
pub async fn inspect_token_2022_cryptographic_preflight(
pool: &ks_onchain_transport::HttpEndpointPool,
request: &crate::Token2022CryptographicPreflightRequest,
) -> ks_core::Result<crate::Token2022CryptographicPreflightReport> {
let requirements = match validate_proof_context_requirements(request) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut context_slot = request.min_context_slot.unwrap_or(0);
let mut reports = std::vec::Vec::with_capacity(requirements.len());
for requirement in requirements {
let expected_size = requirement.proof_type.context_state_size();
let config = match ks_onchain_transport::GetAccountInfoConfig::new_with_data(
ks_onchain_transport::RpcCommitmentLevel::Confirmed,
request.min_context_slot,
expected_size,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let result = match pool
.get_account_info_for_role(request.query_role.as_str(), &requirement.account, &config)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let report = match validate_proof_context_account(&requirement, &result) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
context_slot = context_slot.max(report.context_slot);
reports.push(report);
}
return std::result::Result::Ok(crate::Token2022CryptographicPreflightReport {
commitment: "confirmed".to_string(),
context_slot,
proof_contexts: reports,
});
}
fn validate_proof_context_requirements(
request: &crate::Token2022CryptographicPreflightRequest,
) -> ks_core::Result<std::vec::Vec<crate::Token2022ProofContextRequirement>> {
if request.query_role.trim().is_empty() {
return std::result::Result::Err(ks_core::Error::config(
"Token-2022 cryptographic preflight query_role must not be empty",
));
}
if request.proof_contexts.len() > crate::MAX_TOKEN_2022_PROOF_CONTEXTS {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_proof_context_limit_exceeded",
format!(
"Token-2022 cryptographic preflight accepts at most {} proof contexts",
crate::MAX_TOKEN_2022_PROOF_CONTEXTS
),
));
}
let mut indexes = std::collections::BTreeMap::<std::string::String, usize>::new();
let mut unique = std::vec::Vec::<crate::Token2022ProofContextRequirement>::new();
for requirement in &request.proof_contexts {
if requirement.role.trim().is_empty() {
return std::result::Result::Err(ks_core::Error::config(
"Token-2022 proof context role must not be empty",
));
}
if let std::option::Option::Some(index) = indexes.get(requirement.account.0.as_str()) {
if &unique[*index] != requirement {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_proof_context_conflicting_duplicate",
format!(
"Token-2022 proof context {} has conflicting requirements",
requirement.account.0
),
));
}
continue;
}
indexes.insert(requirement.account.0.clone(), unique.len());
unique.push(requirement.clone());
}
return std::result::Result::Ok(unique);
}
fn validate_proof_context_account(
requirement: &crate::Token2022ProofContextRequirement,
result: &ks_onchain_transport::AccountInfoResult,
) -> ks_core::Result<crate::Token2022ProofContextReport> {
let account = match result.account.as_ref() {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_proof_context_missing",
format!(
"Token-2022 proof context account {} does not exist",
requirement.account.0
),
));
},
};
if account.owner.0 != ks_program_ids::ZK_ELGAMAL_PROOF_PROGRAM_ID {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_proof_context_owner_mismatch",
format!(
"Token-2022 proof context owner must be {}, got {}",
ks_program_ids::ZK_ELGAMAL_PROOF_PROGRAM_ID,
account.owner.0
),
));
}
if account.executable {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_proof_context_executable",
"Token-2022 proof context account must not be executable",
));
}
let expected_size = requirement.proof_type.context_state_size();
if account.space != expected_size as u64 || account.data.len() != expected_size {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_proof_context_size_mismatch",
format!(
"Token-2022 proof context {} must contain exactly {expected_size} bytes, got space {} and data {}",
requirement.account.0,
account.space,
account.data.len()
),
));
}
if account.data.len() < crate::ZK_PROOF_CONTEXT_META_BYTES {
return std::result::Result::Err(ks_core::Error::invalid_state(
"Token-2022 proof context is shorter than its generic metadata",
));
}
let discriminator = account.data[32];
if discriminator != requirement.proof_type.discriminator() {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_proof_context_type_mismatch",
format!(
"Token-2022 proof context {} discriminator {} does not match expected {}",
requirement.account.0,
discriminator,
requirement.proof_type.discriminator()
),
));
}
let mut checks = std::vec![
"zk_program_owner".to_string(),
"not_executable".to_string(),
"exact_context_state_size".to_string(),
"proof_type_discriminator".to_string(),
];
if let std::option::Option::Some(expected_authority) = requirement.expected_authority.as_ref() {
let retained_authority = bs58::encode(&account.data[..32]).into_string();
if retained_authority != expected_authority.0 {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_proof_context_authority_mismatch",
format!(
"Token-2022 proof context {} retains authority {}, expected {}",
requirement.account.0, retained_authority, expected_authority.0
),
));
}
checks.push("retained_authority".to_string());
}
return std::result::Result::Ok(crate::Token2022ProofContextReport {
role: requirement.role.clone(),
account: requirement.account.clone(),
proof_discriminator: discriminator,
context_state_bytes: expected_size,
context_slot: result.context.slot,
checks,
});
}
#[cfg(test)]
mod tests {
fn pubkey(byte: u8) -> ks_lib::MdPubkey {
return ks_lib::MdPubkey(bs58::encode([byte; 32]).into_string());
}
fn requirement(
account: ks_lib::MdPubkey,
proof_type: ks_lib::ExSolanaCoreZkElGamalProofType,
) -> crate::Token2022ProofContextRequirement {
return crate::Token2022ProofContextRequirement {
role: "equality_proof".to_string(),
account,
proof_type,
expected_authority: std::option::Option::Some(pubkey(9)),
};
}
fn account_result(
proof_type: ks_lib::ExSolanaCoreZkElGamalProofType,
) -> ks_onchain_transport::AccountInfoResult {
let mut data = std::vec![0u8; proof_type.context_state_size()];
data[..32].copy_from_slice(&[9u8; 32]);
data[32] = proof_type.discriminator();
return ks_onchain_transport::AccountInfoResult {
context: ks_onchain_transport::RpcResponseContext {
slot: 77,
api_version: std::option::Option::None,
},
account: std::option::Option::Some(ks_onchain_transport::AccountInfoValue {
lamports: 1,
owner: ks_lib::MdProgramId(ks_program_ids::ZK_ELGAMAL_PROOF_PROGRAM_ID.to_string()),
executable: false,
rent_epoch: 0,
space: data.len() as u64,
data,
}),
};
}
#[test]
fn exact_duplicates_are_deduplicated_and_conflicts_fail_closed() {
let item = requirement(
pubkey(1),
ks_lib::ExSolanaCoreZkElGamalProofType::CiphertextCiphertextEquality,
);
let request = crate::Token2022CryptographicPreflightRequest {
query_role: "query".to_string(),
min_context_slot: std::option::Option::Some(7),
proof_contexts: std::vec![item.clone(), item.clone()],
};
let unique = super::validate_proof_context_requirements(&request);
assert_eq!(unique.as_ref().map(std::vec::Vec::len), std::result::Result::Ok(1));
let conflict = crate::Token2022CryptographicPreflightRequest {
query_role: "query".to_string(),
min_context_slot: std::option::Option::None,
proof_contexts: std::vec![
item,
requirement(
pubkey(1),
ks_lib::ExSolanaCoreZkElGamalProofType::BatchedRangeProofU128,
),
],
};
assert!(super::validate_proof_context_requirements(&conflict).is_err());
}
#[test]
fn proof_context_validation_checks_owner_size_type_and_authority() {
let proof_type = ks_lib::ExSolanaCoreZkElGamalProofType::CiphertextCiphertextEquality;
let context_requirement = requirement(pubkey(1), proof_type);
let result = account_result(proof_type);
let report = super::validate_proof_context_account(&context_requirement, &result);
assert_eq!(
report.as_ref().map(|value| return value.checks.len()),
std::result::Result::Ok(5)
);
let wrong_type =
requirement(pubkey(1), ks_lib::ExSolanaCoreZkElGamalProofType::BatchedRangeProofU128);
assert!(super::validate_proof_context_account(&wrong_type, &result,).is_err());
}
}

View File

@@ -0,0 +1,331 @@
// file: ks-pipeline/src/spl_token_2022_execution_orchestration.rs
// version: 5
//! Final Token-2022 execution-readiness and stateful postcondition contracts.
/// Maximum number of distinct transaction signers accepted by one Token-2022 execution envelope.
pub const MAX_TOKEN_2022_EXECUTION_SIGNERS: usize = 16;
/// Result of one stateful postcondition checked after a confirmed Token-2022 transaction.
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Token2022ExecutionPostconditionStatus {
/// The observed final state confirms the expected operation effect.
Confirmed,
/// The observed final state contradicts the expected operation effect.
Contradicted,
/// The operation has no supported stateful postcondition in the current contract.
NotApplicable,
}
/// One explicit stateful postcondition retained by the execution orchestrator.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct Token2022ExecutionPostcondition {
/// Stable semantic role such as `mint`, `source`, `destination`, or `registry`.
pub role: std::string::String,
/// Canonical account whose final state was inspected.
pub account: ks_lib::MdPubkey,
/// Explicit result of the postcondition.
pub status: crate::Token2022ExecutionPostconditionStatus,
/// Bounded diagnostic explaining the result without retaining complete account data.
pub diagnostic: std::string::String,
}
/// Complete deterministic request checked before Token-2022 transaction signing.
#[derive(Clone, Debug, PartialEq)]
pub struct Token2022ExecutionReadinessRequest {
/// Exact prepared executor plan.
pub plan: ks_lib::ExApiPreparedExecutionPlan,
/// Exact hash of the compiled Solana message.
pub message_hash: std::string::String,
/// Hash retained by the simulation evidence.
pub simulated_message_hash: std::string::String,
/// Whether the exact compiled message was simulated.
pub simulated: bool,
/// Whether the exact simulation succeeded.
pub simulation_succeeded: bool,
/// Stateful Token-2022 account preflight report.
pub stateful_preflight: crate::Token2022PreflightReport,
/// Cryptographic proof context-state preflight report.
pub cryptographic_preflight: crate::Token2022CryptographicPreflightReport,
/// Ordered proof orchestration report.
pub proof_orchestration: crate::Token2022ProofOrchestrationReport,
/// Public keys actually available to sign the transaction.
pub resolved_signers: std::vec::Vec<ks_lib::MdPubkey>,
/// Whether submission was explicitly requested.
pub submit: bool,
/// Whether submission received explicit operator confirmation.
pub operator_confirmed: bool,
}
/// Deterministic execution-readiness report produced before signing.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct Token2022ExecutionReadinessReport {
/// Stable operation code.
pub operation_code: std::string::String,
/// Exact compiled message hash bound to simulation.
pub message_hash: std::string::String,
/// Highest RPC context slot across stateful and cryptographic preflights.
pub context_slot: u64,
/// Ordered signer public keys required by the plan.
pub required_signers: std::vec::Vec<ks_lib::MdPubkey>,
/// Whether transaction signing and submission are authorized.
pub send_authorized: bool,
/// Ordered successful checks.
pub checks: std::vec::Vec<std::string::String>,
}
/// Validates the complete Token-2022 execution envelope before transaction signing.
pub fn validate_token_2022_execution_readiness(
request: &crate::Token2022ExecutionReadinessRequest,
) -> ks_core::Result<crate::Token2022ExecutionReadinessReport> {
if request.plan.operation_code.trim().is_empty() {
return std::result::Result::Err(ks_core::Error::config(
"Token-2022 execution plan operation_code must not be empty",
));
}
if request.plan.operation_code != request.proof_orchestration.operation_code {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_execution_operation_mismatch",
"Token-2022 plan and proof orchestration operation codes must match",
));
}
if request.message_hash.trim().is_empty()
|| request.simulated_message_hash.trim().is_empty()
|| request.message_hash != request.simulated_message_hash
{
return std::result::Result::Err(ks_core::Error::new(
"token_2022_execution_simulation_message_mismatch",
"Token-2022 simulation must be bound to the exact compiled message hash",
));
}
if !request.simulated || !request.simulation_succeeded {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_execution_simulation_required",
"Token-2022 execution requires one successful exact-message simulation",
));
}
if !request.proof_orchestration.simulation_required {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_execution_proof_policy_mismatch",
"Token-2022 proof orchestration must require simulation",
));
}
if request.stateful_preflight.commitment != "confirmed"
|| request.cryptographic_preflight.commitment != "confirmed"
{
return std::result::Result::Err(ks_core::Error::new(
"token_2022_execution_preflight_commitment_mismatch",
"Token-2022 stateful and cryptographic preflights must use confirmed commitment",
));
}
let required_signers = match validate_signers(
request.plan.required_signers.as_slice(),
request.resolved_signers.as_slice(),
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
if request.submit && !request.operator_confirmed {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_execution_confirmation_required",
"Token-2022 submission requires explicit operator confirmation",
));
}
if request.submit && !request.proof_orchestration.send_authorized {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_execution_proof_send_not_authorized",
"Token-2022 proof orchestration did not authorize submission",
));
}
if request.submit && request.plan.policy.dry_run {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_execution_dry_run_blocks_submission",
"Token-2022 plan remains dry-run and cannot be submitted",
));
}
let context_slot = request
.stateful_preflight
.context_slot
.max(request.cryptographic_preflight.context_slot);
return std::result::Result::Ok(crate::Token2022ExecutionReadinessReport {
operation_code: request.plan.operation_code.clone(),
message_hash: request.message_hash.clone(),
context_slot,
required_signers,
send_authorized: request.submit
&& request.operator_confirmed
&& request.proof_orchestration.send_authorized
&& !request.plan.policy.dry_run,
checks: vec![
"operation_matches_proof_orchestration".to_string(),
"simulation_bound_to_exact_message".to_string(),
"confirmed_stateful_preflight".to_string(),
"confirmed_cryptographic_preflight".to_string(),
"all_required_signers_resolved".to_string(),
"submission_policy_consistent".to_string(),
],
});
}
/// Aggregates explicit postconditions without converting unsupported checks into success.
pub fn summarize_token_2022_postconditions(
postconditions: &[crate::Token2022ExecutionPostcondition],
) -> crate::Token2022ExecutionPostconditionStatus {
if postconditions.iter().any(|item| {
return item.status == crate::Token2022ExecutionPostconditionStatus::Contradicted;
}) {
return crate::Token2022ExecutionPostconditionStatus::Contradicted;
}
if postconditions.iter().any(|item| {
return item.status == crate::Token2022ExecutionPostconditionStatus::Confirmed;
}) {
return crate::Token2022ExecutionPostconditionStatus::Confirmed;
}
return crate::Token2022ExecutionPostconditionStatus::NotApplicable;
}
fn validate_signers(
required: &[ks_lib::ExApiRequiredSigner],
resolved: &[ks_lib::MdPubkey],
) -> ks_core::Result<std::vec::Vec<ks_lib::MdPubkey>> {
if required.len() > crate::MAX_TOKEN_2022_EXECUTION_SIGNERS
|| resolved.len() > crate::MAX_TOKEN_2022_EXECUTION_SIGNERS
{
return std::result::Result::Err(ks_core::Error::new(
"token_2022_execution_signer_limit_exceeded",
format!(
"Token-2022 execution accepts at most {} signers",
crate::MAX_TOKEN_2022_EXECUTION_SIGNERS
),
));
}
let mut required_unique =
std::collections::BTreeMap::<std::string::String, ks_lib::MdPubkey>::new();
for signer in required {
required_unique
.entry(signer.pubkey.0.clone())
.or_insert_with(|| return signer.pubkey.clone());
}
let resolved_set = resolved
.iter()
.map(|signer| return signer.0.clone())
.collect::<std::collections::BTreeSet<std::string::String>>();
for signer in required_unique.values() {
if !resolved_set.contains(signer.0.as_str()) {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_execution_signer_unresolved",
format!("Token-2022 required signer {} is unresolved", signer.0),
));
}
}
return std::result::Result::Ok(required_unique.into_values().collect());
}
#[cfg(test)]
mod tests {
fn pubkey(byte: u8) -> ks_lib::MdPubkey {
return ks_lib::MdPubkey(bs58::encode([byte; 32]).into_string());
}
fn request() -> crate::Token2022ExecutionReadinessRequest {
let signer = pubkey(1);
let policy = ks_lib::ExApiExecutionPolicy {
dry_run: false,
..std::default::Default::default()
};
let plan = ks_lib::ExApiPreparedExecutionPlan {
executor_name: "kb-lib.executor.spl.token_2022".to_string(),
executor_version: "0.4.6".to_string(),
intent_id: "intent".to_string(),
operation_code: "spl.token_2022.confidential_transfer".to_string(),
fee_payer: signer.clone(),
instructions: vec![],
required_signers: vec![ks_lib::ExApiRequiredSigner {
pubkey: signer.clone(),
role: "authority".to_string(),
}],
policy,
requested_spend_lamports: 0,
requested_compute_unit_price_micro_lamports: std::option::Option::None,
};
return crate::Token2022ExecutionReadinessRequest {
plan,
message_hash: "message-hash".to_string(),
simulated_message_hash: "message-hash".to_string(),
simulated: true,
simulation_succeeded: true,
stateful_preflight: crate::Token2022PreflightReport {
commitment: "confirmed".to_string(),
context_slot: 100,
requested_data_bytes: 0,
accounts: vec![],
elgamal_registry_validated: false,
},
cryptographic_preflight: crate::Token2022CryptographicPreflightReport {
commitment: "confirmed".to_string(),
context_slot: 101,
proof_contexts: vec![],
},
proof_orchestration: crate::Token2022ProofOrchestrationReport {
operation_code: "spl.token_2022.confidential_transfer".to_string(),
instructions_sysvar_required: true,
inline_offsets: vec![1],
context_requirements: vec![],
simulation_required: true,
send_authorized: true,
checks: vec![],
},
resolved_signers: vec![signer],
submit: true,
operator_confirmed: true,
};
}
#[test]
fn exact_message_preflights_and_signers_authorize_submission() {
let report = super::validate_token_2022_execution_readiness(&request())
.expect("complete Token-2022 readiness must succeed");
assert!(report.send_authorized);
assert_eq!(report.context_slot, 101);
assert_eq!(report.required_signers.len(), 1);
}
#[test]
fn message_mismatch_missing_signer_and_dry_run_fail_closed() {
let mut mismatched = request();
mismatched.simulated_message_hash = "other".to_string();
assert!(super::validate_token_2022_execution_readiness(&mismatched).is_err());
let mut missing = request();
missing.resolved_signers.clear();
assert!(super::validate_token_2022_execution_readiness(&missing).is_err());
let mut dry_run = request();
dry_run.plan.policy.dry_run = true;
assert!(super::validate_token_2022_execution_readiness(&dry_run).is_err());
}
#[test]
fn postcondition_summary_preserves_contradicted_and_not_applicable() {
let account = pubkey(2);
let not_applicable = crate::Token2022ExecutionPostcondition {
role: "mint".to_string(),
account: account.clone(),
status: crate::Token2022ExecutionPostconditionStatus::NotApplicable,
diagnostic: "no supported final-state assertion".to_string(),
};
assert_eq!(
super::summarize_token_2022_postconditions(&[not_applicable]),
crate::Token2022ExecutionPostconditionStatus::NotApplicable
);
let contradicted = crate::Token2022ExecutionPostcondition {
role: "source".to_string(),
account,
status: crate::Token2022ExecutionPostconditionStatus::Contradicted,
diagnostic: "final extension inventory contradicts the expected effect".to_string(),
};
assert_eq!(
super::summarize_token_2022_postconditions(&[contradicted]),
crate::Token2022ExecutionPostconditionStatus::Contradicted
);
}
}

View File

@@ -0,0 +1,376 @@
// file: ks-pipeline/src/spl_token_2022_metadata.rs
// version: 4
//! Token-2022 Token Metadata return-data and authority postcondition contracts.
use base64::Engine; // rust-rules: trait-import
/// Maximum Token Metadata bytes accepted from Solana return data.
pub const MAX_TOKEN_2022_METADATA_EMIT_BYTES: usize = 1_024;
/// Bounded evidence extracted from one successful Token Metadata `Emit` simulation.
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct Token2022MetadataEmitEvidence {
/// Program that produced the return data.
pub program_id: std::string::String,
/// Optional requested range start.
pub start: std::option::Option<u64>,
/// Optional requested range end.
pub end: std::option::Option<u64>,
/// Decoded return-data bytes.
pub data: std::vec::Vec<u8>,
/// Exact complete Token Metadata projection, only for an un-ranged emit.
pub decoded_metadata: std::option::Option<serde_json::Value>,
}
/// Validates one Token Metadata `Emit` range against Solana return-data bounds.
pub fn validate_token_2022_metadata_emit_range(
start: std::option::Option<u64>,
end: std::option::Option<u64>,
) -> ks_core::Result<()> {
if let (std::option::Option::Some(start), std::option::Option::Some(end)) = (start, end) {
if start > end {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_metadata_emit_range_invalid",
format!("Token Metadata Emit start {start} exceeds end {end}"),
));
}
if end - start > crate::MAX_TOKEN_2022_METADATA_EMIT_BYTES as u64 {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_metadata_emit_range_too_large",
format!(
"Token Metadata Emit range exceeds {} bytes",
crate::MAX_TOKEN_2022_METADATA_EMIT_BYTES
),
));
}
}
if let (std::option::Option::None, std::option::Option::Some(end)) = (start, end) {
if end > crate::MAX_TOKEN_2022_METADATA_EMIT_BYTES as u64 {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_metadata_emit_range_too_large",
format!(
"Token Metadata Emit range from zero exceeds {} bytes",
crate::MAX_TOKEN_2022_METADATA_EMIT_BYTES
),
));
}
}
if let (std::option::Option::Some(_), std::option::Option::None) = (start, end) {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_metadata_emit_range_end_required",
"Token Metadata Emit with a start offset requires an explicit end to remain bounded",
));
}
return std::result::Result::Ok(());
}
/// Extracts and validates Token Metadata return data from one exact simulation.
pub fn inspect_token_2022_metadata_emit_simulation(
simulation: &ks_lib::ExApiExecutionSimulationResult,
start: std::option::Option<u64>,
end: std::option::Option<u64>,
) -> ks_core::Result<crate::Token2022MetadataEmitEvidence> {
match crate::validate_token_2022_metadata_emit_range(start, end) {
std::result::Result::Ok(()) => {},
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
if !simulation.simulated || !simulation.success {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_metadata_emit_simulation_required",
"Token Metadata Emit evidence requires a successful simulation",
));
}
let return_data = match simulation.return_data.as_ref() {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_metadata_emit_return_data_missing",
"Token Metadata Emit simulation did not return program data",
));
},
};
let program_id = match return_data.get("programId").and_then(serde_json::Value::as_str) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_metadata_emit_program_id_missing",
"Token Metadata Emit return data is missing programId",
));
},
};
if program_id != ks_program_ids::SPL_TOKEN_2022_PROGRAM_ID {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_metadata_emit_program_id_mismatch",
format!("Token Metadata Emit return data came from {program_id}"),
));
}
let data = match return_data.get("data").and_then(serde_json::Value::as_array) {
std::option::Option::Some(value) if value.len() == 2 => value,
_ => {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_metadata_emit_data_invalid",
"Token Metadata Emit return data must be [payload, encoding]",
));
},
};
let payload = match data.first().and_then(|value| return value.as_str()) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_metadata_emit_payload_invalid",
"Token Metadata Emit return payload must be a base64 string",
));
},
};
let encoding = match data.get(1).and_then(|value| return value.as_str()) {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_metadata_emit_encoding_invalid",
"Token Metadata Emit return data encoding is missing",
));
},
};
if encoding != "base64" {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_metadata_emit_encoding_invalid",
"Token Metadata Emit return data encoding must be base64",
));
}
let decoded = match base64::engine::general_purpose::STANDARD.decode(payload) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_metadata_emit_base64_invalid",
format!("Token Metadata Emit return payload is invalid base64: {error}"),
));
},
};
if decoded.len() > crate::MAX_TOKEN_2022_METADATA_EMIT_BYTES {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_metadata_emit_data_too_large",
format!(
"Token Metadata Emit returned {} bytes, maximum is {}",
decoded.len(),
crate::MAX_TOKEN_2022_METADATA_EMIT_BYTES
),
));
}
let decoded_metadata = if start.is_none() && end.is_none() {
match ks_lib::decoder_spl_token_2022_parse_token_metadata_value(decoded.as_slice()) {
std::result::Result::Ok(value) => std::option::Option::Some(value),
std::result::Result::Err(error) => {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_metadata_emit_payload_decode_failed",
error,
));
},
}
} else {
std::option::Option::None
};
return std::result::Result::Ok(crate::Token2022MetadataEmitEvidence {
program_id: program_id.to_string(),
start,
end,
data: decoded,
decoded_metadata,
});
}
/// Checks the final embedded metadata authority against one authoritative snapshot.
pub fn inspect_token_2022_metadata_authority_postcondition(
account: &ks_lib::MdPubkey,
expected_authority: std::option::Option<&ks_lib::MdPubkey>,
snapshot: &crate::Token2022StatefulSnapshotBundle,
) -> crate::Token2022ExecutionPostcondition {
if snapshot.account_key != account.0 {
return crate::Token2022ExecutionPostcondition {
role: "metadata".to_string(),
account: account.clone(),
status: crate::Token2022ExecutionPostconditionStatus::Contradicted,
diagnostic: "Token-2022 metadata snapshot belongs to a different account".to_string(),
};
}
let output = snapshot.outputs.iter().find(|output| {
return output.family == ks_lib::MdMaterializedEventFamily::Metadata
&& output.payload_json.get("projectionKind").and_then(serde_json::Value::as_str)
== std::option::Option::Some("token_metadata");
});
let observed = output.and_then(|value| {
return value.payload_json.get("valueFields").and_then(|fields| {
return fields.get("updateAuthority");
});
});
let confirmed = match (expected_authority, observed) {
(std::option::Option::Some(expected), std::option::Option::Some(value)) => {
value.as_str() == std::option::Option::Some(expected.0.as_str())
},
(std::option::Option::None, std::option::Option::Some(value)) => value.is_null(),
_ => false,
};
return crate::Token2022ExecutionPostcondition {
role: "metadata".to_string(),
account: account.clone(),
status: if confirmed {
crate::Token2022ExecutionPostconditionStatus::Confirmed
} else {
crate::Token2022ExecutionPostconditionStatus::Contradicted
},
diagnostic: if confirmed {
"authoritative Token-2022 metadata snapshot matches the expected update authority"
.to_string()
} else {
"authoritative Token-2022 metadata snapshot contradicts or omits the expected update authority"
.to_string()
},
};
}
#[cfg(test)]
mod tests {
use base64::Engine; // rust-rules: trait-import
fn simulation(data: &[u8]) -> ks_lib::ExApiExecutionSimulationResult {
return ks_lib::ExApiExecutionSimulationResult {
simulated: true,
success: true,
cluster: ks_lib::ExApiExecutionCluster::Devnet,
blockhash_kind: ks_lib::ExApiExecutionBlockhashKind::Latest,
blockhash_age_slots: std::option::Option::Some(0),
replacement_blockhash: std::option::Option::None,
replacement_last_valid_block_height: std::option::Option::None,
nonce_account: std::option::Option::None,
nonce_authority: std::option::Option::None,
units_consumed: std::option::Option::Some(1),
estimated_fee_lamports: std::option::Option::Some(5_000),
logs: std::vec::Vec::new(),
return_data: std::option::Option::Some(serde_json::json!({
"programId": ks_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
"data": [base64::engine::general_purpose::STANDARD.encode(data), "base64"]
})),
error: std::option::Option::None,
};
}
fn metadata_bytes(authority: std::option::Option<[u8; 32]>) -> std::vec::Vec<u8> {
let mut value = std::vec::Vec::new();
value.extend_from_slice(authority.unwrap_or([0_u8; 32]).as_slice());
value.extend_from_slice([7_u8; 32].as_slice());
for field in ["Token", "TKN", "https://example.invalid/token.json"] {
value.extend_from_slice((field.len() as u32).to_le_bytes().as_slice());
value.extend_from_slice(field.as_bytes());
}
value.extend_from_slice(0_u32.to_le_bytes().as_slice());
return value;
}
#[test]
fn complete_emit_decodes_exact_metadata_and_ranged_emit_keeps_only_bytes() {
let complete = crate::inspect_token_2022_metadata_emit_simulation(
&simulation(metadata_bytes(std::option::Option::Some([9_u8; 32])).as_slice()),
std::option::Option::None,
std::option::Option::None,
)
.unwrap_or_else(|error| panic!("complete Emit evidence failed: {error}"));
assert_eq!(
complete
.decoded_metadata
.as_ref()
.and_then(|value| return value.get("name"))
.and_then(serde_json::Value::as_str),
std::option::Option::Some("Token")
);
let ranged = crate::inspect_token_2022_metadata_emit_simulation(
&simulation(&[1_u8, 2_u8, 3_u8]),
std::option::Option::Some(0),
std::option::Option::Some(3),
)
.unwrap_or_else(|error| panic!("ranged Emit evidence failed: {error}"));
assert!(ranged.decoded_metadata.is_none());
assert_eq!(ranged.data, vec![1_u8, 2_u8, 3_u8]);
}
#[test]
fn emit_range_and_provider_contract_fail_closed() {
assert!(
crate::validate_token_2022_metadata_emit_range(
std::option::Option::Some(2),
std::option::Option::Some(1),
)
.is_err()
);
assert!(
crate::validate_token_2022_metadata_emit_range(
std::option::Option::Some(0),
std::option::Option::Some(1_025),
)
.is_err()
);
assert!(
crate::validate_token_2022_metadata_emit_range(
std::option::Option::Some(1),
std::option::Option::None,
)
.is_err()
);
let mut invalid = simulation(&[1_u8]);
invalid.return_data = std::option::Option::Some(serde_json::json!({
"programId": ks_program_ids::SYSTEM_PROGRAM_ID,
"data": ["AQ==", "base64"]
}));
assert!(
crate::inspect_token_2022_metadata_emit_simulation(
&invalid,
std::option::Option::Some(0),
std::option::Option::Some(1)
)
.is_err()
);
}
#[test]
fn authority_postcondition_accepts_exact_value_and_null() {
let account = ks_lib::MdPubkey(bs58::encode([3_u8; 32]).into_string());
let authority = ks_lib::MdPubkey(bs58::encode([4_u8; 32]).into_string());
let mut snapshot = crate::Token2022StatefulSnapshotBundle {
account_key: account.0.clone(),
slot: 9,
state_kind: "mint".to_string(),
extension_names: vec!["token_metadata".to_string()],
outputs: vec![ks_lib::MtApiMaterializedOutput {
output_key: "metadata".to_string(),
family: ks_lib::MdMaterializedEventFamily::Metadata,
payload_json: serde_json::json!({
"projectionKind": "token_metadata",
"valueFields": {"updateAuthority": authority.0}
}),
}],
};
let confirmed = crate::inspect_token_2022_metadata_authority_postcondition(
&account,
std::option::Option::Some(&authority),
&snapshot,
);
assert_eq!(confirmed.status, crate::Token2022ExecutionPostconditionStatus::Confirmed);
snapshot.outputs[0].payload_json["valueFields"]["updateAuthority"] =
serde_json::Value::Null;
let cleared = crate::inspect_token_2022_metadata_authority_postcondition(
&account,
std::option::Option::None,
&snapshot,
);
assert_eq!(cleared.status, crate::Token2022ExecutionPostconditionStatus::Confirmed);
snapshot.account_key = bs58::encode([8_u8; 32]).into_string();
let wrong_account = crate::inspect_token_2022_metadata_authority_postcondition(
&account,
std::option::Option::None,
&snapshot,
);
assert_eq!(
wrong_account.status,
crate::Token2022ExecutionPostconditionStatus::Contradicted
);
}
}

View File

@@ -0,0 +1,431 @@
// file: ks-pipeline/src/spl_token_2022_preflight.rs
// version: 5
//! Bounded Token-2022 stateful preflight orchestration.
/// Maximum distinct Token-2022 accounts accepted by one preflight inspection.
pub const MAX_TOKEN_2022_PREFLIGHT_ACCOUNTS: usize = 16;
/// Maximum aggregate account-data budget accepted by one preflight inspection.
pub const MAX_TOKEN_2022_PREFLIGHT_TOTAL_BYTES: usize = 262_144;
/// One exact Token-2022 account requirement for a stateful preflight.
#[derive(Clone, Debug, PartialEq)]
pub struct Token2022PreflightRequirement {
/// Stable semantic role used in diagnostics.
pub role: std::string::String,
/// Canonical account address.
pub account: ks_lib::MdPubkey,
/// Expected Token-2022 state category.
pub kind: ks_lib::DcToken2022StateKind,
/// Maximum complete account bytes accepted for this requirement.
pub max_data_bytes: usize,
/// Optional expected mint for a Token Account.
pub expected_mint: std::option::Option<ks_lib::MdPubkey>,
/// Optional expected owner for a Token Account.
pub expected_owner: std::option::Option<ks_lib::MdPubkey>,
/// Optional exact decimals expected for a Mint.
pub expected_decimals: std::option::Option<u8>,
/// Published extension names required on the account.
pub required_extensions: std::vec::Vec<std::string::String>,
/// Optional external identities needed by cross-account extensions.
pub context: crate::Token2022StatefulContext,
}
/// One bounded Token-2022 preflight request.
#[derive(Clone, Debug, PartialEq)]
pub struct Token2022PreflightRequest {
/// Endpoint role used by all RPC reads.
pub query_role: std::string::String,
/// Optional minimum context slot shared by all reads.
pub min_context_slot: std::option::Option<u64>,
/// Maximum distinct accounts accepted after deduplication.
pub max_accounts: usize,
/// Maximum aggregate requested account bytes.
pub max_total_data_bytes: usize,
/// Ordered account requirements.
pub requirements: std::vec::Vec<Token2022PreflightRequirement>,
/// Optional ElGamal registry read required by the operation.
pub elgamal_registry: std::option::Option<crate::ElGamalRegistryStatefulReadRequest>,
}
/// One validated account result in a Token-2022 preflight report.
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct Token2022PreflightAccountReport {
/// Stable semantic role.
pub role: std::string::String,
/// Canonical account address.
pub account: ks_lib::MdPubkey,
/// State category observed after parsing.
pub state_kind: std::string::String,
/// Context slot returned by the endpoint.
pub context_slot: u64,
/// Ordered extension names observed on the account.
pub extension_names: std::vec::Vec<std::string::String>,
/// Ordered successful semantic checks.
pub checks: std::vec::Vec<std::string::String>,
}
/// Complete bounded Token-2022 stateful preflight report.
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct Token2022PreflightReport {
/// Commitment used for all account reads.
pub commitment: std::string::String,
/// Highest context slot observed across all reads.
pub context_slot: u64,
/// Aggregate requested account-data budget.
pub requested_data_bytes: usize,
/// Ordered distinct Token-2022 account reports.
pub accounts: std::vec::Vec<Token2022PreflightAccountReport>,
/// Whether an ElGamal registry was required and validated.
pub elgamal_registry_validated: bool,
}
/// Inspects all bounded Token-2022 state required before simulation.
pub async fn inspect_token_2022_preflight(
pool: &ks_onchain_transport::HttpEndpointPool,
request: &crate::Token2022PreflightRequest,
) -> ks_core::Result<crate::Token2022PreflightReport> {
let requirements = match validate_and_deduplicate_requirements(request) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let requested_data_bytes = requirements
.iter()
.fold(0usize, |total, requirement| {
return total.saturating_add(requirement.max_data_bytes);
})
.saturating_add(if request.elgamal_registry.is_some() {
crate::ELGAMAL_REGISTRY_STATEFUL_ACCOUNT_BYTES
} else {
0
});
let mut context_slot = request.min_context_slot.unwrap_or(0);
let mut accounts = std::vec::Vec::with_capacity(requirements.len());
for requirement in requirements {
let read_request = crate::Token2022StatefulReadRequest {
query_role: request.query_role.clone(),
account: requirement.account.clone(),
kind: requirement.kind,
min_context_slot: request.min_context_slot,
max_data_bytes: requirement.max_data_bytes,
context: requirement.context.clone(),
};
let read_result = match crate::read_token_2022_stateful_snapshot(pool, &read_request).await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let account_report = match validate_snapshot_requirement(&requirement, &read_result) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
context_slot = context_slot.max(account_report.context_slot);
accounts.push(account_report);
}
let mut elgamal_registry_validated = false;
if let std::option::Option::Some(registry_request) = request.elgamal_registry.as_ref() {
if registry_request.query_role != request.query_role {
return std::result::Result::Err(ks_core::Error::config(
"Token-2022 preflight ElGamal registry query role must match the shared query role",
));
}
let registry_result =
match crate::read_elgamal_registry_stateful_snapshot(pool, registry_request).await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
context_slot = context_slot.max(registry_result.context_slot);
elgamal_registry_validated = true;
}
return std::result::Result::Ok(crate::Token2022PreflightReport {
commitment: "confirmed".to_string(),
context_slot,
requested_data_bytes,
accounts,
elgamal_registry_validated,
});
}
fn validate_and_deduplicate_requirements(
request: &crate::Token2022PreflightRequest,
) -> ks_core::Result<std::vec::Vec<crate::Token2022PreflightRequirement>> {
if request.query_role.trim().is_empty() {
return std::result::Result::Err(ks_core::Error::config(
"Token-2022 preflight query_role must not be empty",
));
}
if request.max_accounts == 0 || request.max_accounts > crate::MAX_TOKEN_2022_PREFLIGHT_ACCOUNTS
{
return std::result::Result::Err(ks_core::Error::config(format!(
"Token-2022 preflight max_accounts must be between 1 and {}",
crate::MAX_TOKEN_2022_PREFLIGHT_ACCOUNTS
)));
}
if request.max_total_data_bytes == 0
|| request.max_total_data_bytes > crate::MAX_TOKEN_2022_PREFLIGHT_TOTAL_BYTES
{
return std::result::Result::Err(ks_core::Error::config(format!(
"Token-2022 preflight max_total_data_bytes must be between 1 and {}",
crate::MAX_TOKEN_2022_PREFLIGHT_TOTAL_BYTES
)));
}
if request.requirements.is_empty() {
return std::result::Result::Err(ks_core::Error::config(
"Token-2022 preflight requires at least one account",
));
}
let mut indexes = std::collections::BTreeMap::<std::string::String, usize>::new();
let mut unique = std::vec::Vec::<crate::Token2022PreflightRequirement>::new();
for requirement in &request.requirements {
if requirement.role.trim().is_empty() {
return std::result::Result::Err(ks_core::Error::config(
"Token-2022 preflight requirement role must not be empty",
));
}
if requirement.max_data_bytes == 0
|| requirement.max_data_bytes > crate::MAX_TOKEN_2022_STATEFUL_ACCOUNT_BYTES
{
return std::result::Result::Err(ks_core::Error::config(format!(
"Token-2022 preflight account {} has invalid max_data_bytes {}",
requirement.account.0, requirement.max_data_bytes
)));
}
if let std::option::Option::Some(index) = indexes.get(requirement.account.0.as_str()) {
if &unique[*index] != requirement {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_preflight_conflicting_duplicate",
format!(
"Token-2022 preflight account {} has conflicting requirements",
requirement.account.0
),
));
}
continue;
}
indexes.insert(requirement.account.0.clone(), unique.len());
unique.push(requirement.clone());
}
let total_accounts =
unique
.len()
.saturating_add(if request.elgamal_registry.is_some() { 1 } else { 0 });
if total_accounts > request.max_accounts {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_preflight_account_limit_exceeded",
format!(
"Token-2022 preflight requires {total_accounts} distinct accounts above limit {}",
request.max_accounts
),
));
}
let requested_data_bytes = unique
.iter()
.fold(0usize, |total, requirement| {
return total.saturating_add(requirement.max_data_bytes);
})
.saturating_add(if request.elgamal_registry.is_some() {
crate::ELGAMAL_REGISTRY_STATEFUL_ACCOUNT_BYTES
} else {
0
});
if requested_data_bytes > request.max_total_data_bytes {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_preflight_data_budget_exceeded",
format!(
"Token-2022 preflight requests {requested_data_bytes} bytes above aggregate limit {}",
request.max_total_data_bytes
),
));
}
return std::result::Result::Ok(unique);
}
fn validate_snapshot_requirement(
requirement: &crate::Token2022PreflightRequirement,
result: &crate::Token2022StatefulReadResult,
) -> ks_core::Result<crate::Token2022PreflightAccountReport> {
let token_output = result
.snapshot
.outputs
.iter()
.find(|output| return output.payload_json["domain"] == "spl_token_2022_account_state");
let token_output = match token_output {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(ks_core::Error::invalid_state(
"Token-2022 preflight snapshot is missing its token account owner projection",
));
},
};
let base_fields = &token_output.payload_json["baseFields"];
let mut checks = std::vec![
"owner_program_id".to_string(),
"complete_account_data".to_string(),
"state_kind".to_string()
];
if let std::option::Option::Some(expected_mint) = requirement.expected_mint.as_ref() {
if base_fields["mint"].as_str() != std::option::Option::Some(expected_mint.0.as_str()) {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_preflight_mint_mismatch",
format!(
"Token-2022 account {} mint does not match expected {}",
requirement.account.0, expected_mint.0
),
));
}
checks.push("mint_identity".to_string());
}
if let std::option::Option::Some(expected_owner) = requirement.expected_owner.as_ref() {
if base_fields["owner"].as_str() != std::option::Option::Some(expected_owner.0.as_str()) {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_preflight_owner_mismatch",
format!(
"Token-2022 account {} owner does not match expected {}",
requirement.account.0, expected_owner.0
),
));
}
checks.push("token_account_owner".to_string());
}
if let std::option::Option::Some(expected_decimals) = requirement.expected_decimals {
if base_fields["decimals"].as_u64() != std::option::Option::Some(expected_decimals as u64) {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_preflight_decimals_mismatch",
format!(
"Token-2022 mint {} decimals do not match expected {expected_decimals}",
requirement.account.0
),
));
}
checks.push("mint_decimals".to_string());
}
for required_extension in &requirement.required_extensions {
if !result
.snapshot
.extension_names
.iter()
.any(|value| return value == required_extension)
{
return std::result::Result::Err(ks_core::Error::new(
"token_2022_preflight_extension_missing",
format!(
"Token-2022 account {} is missing required extension {required_extension}",
requirement.account.0
),
));
}
}
if !requirement.required_extensions.is_empty() {
checks.push("required_extensions".to_string());
}
return std::result::Result::Ok(crate::Token2022PreflightAccountReport {
role: requirement.role.clone(),
account: requirement.account.clone(),
state_kind: result.snapshot.state_kind.clone(),
context_slot: result.context_slot,
extension_names: result.snapshot.extension_names.clone(),
checks,
});
}
#[cfg(test)]
mod tests {
fn pubkey(byte: u8) -> ks_lib::MdPubkey {
return ks_lib::MdPubkey(bs58::encode([byte; 32]).into_string());
}
fn requirement(account: ks_lib::MdPubkey) -> crate::Token2022PreflightRequirement {
return crate::Token2022PreflightRequirement {
role: "source".to_string(),
account,
kind: ks_lib::DcToken2022StateKind::Account,
max_data_bytes: 512,
expected_mint: std::option::Option::None,
expected_owner: std::option::Option::None,
expected_decimals: std::option::Option::None,
required_extensions: std::vec::Vec::new(),
context: crate::Token2022StatefulContext::default(),
};
}
#[test]
fn exact_duplicate_requirements_are_deduplicated_and_bounded() {
let item = requirement(pubkey(1));
let request = crate::Token2022PreflightRequest {
query_role: "query".to_string(),
min_context_slot: std::option::Option::Some(7),
max_accounts: 1,
max_total_data_bytes: 512,
requirements: std::vec![item.clone(), item],
elgamal_registry: std::option::Option::None,
};
let result = super::validate_and_deduplicate_requirements(&request);
assert_eq!(result.as_ref().map(std::vec::Vec::len), std::result::Result::Ok(1));
}
#[test]
fn conflicting_duplicates_and_aggregate_budget_fail_closed() {
let first = requirement(pubkey(2));
let mut conflicting = first.clone();
conflicting.role = "destination".to_string();
let conflict_request = crate::Token2022PreflightRequest {
query_role: "query".to_string(),
min_context_slot: std::option::Option::None,
max_accounts: 2,
max_total_data_bytes: 1024,
requirements: std::vec![first, conflicting],
elgamal_registry: std::option::Option::None,
};
assert!(super::validate_and_deduplicate_requirements(&conflict_request).is_err());
let budget_request = crate::Token2022PreflightRequest {
query_role: "query".to_string(),
min_context_slot: std::option::Option::None,
max_accounts: 2,
max_total_data_bytes: 700,
requirements: std::vec![requirement(pubkey(3)), requirement(pubkey(4))],
elgamal_registry: std::option::Option::None,
};
assert!(super::validate_and_deduplicate_requirements(&budget_request).is_err());
}
#[test]
fn requirement_validation_checks_mint_owner_decimals_and_extensions() {
let account = pubkey(5);
let mint = pubkey(6);
let owner = pubkey(7);
let requirement = crate::Token2022PreflightRequirement {
role: "source".to_string(),
account: account.clone(),
kind: ks_lib::DcToken2022StateKind::Account,
max_data_bytes: 512,
expected_mint: std::option::Option::Some(mint.clone()),
expected_owner: std::option::Option::Some(owner.clone()),
expected_decimals: std::option::Option::None,
required_extensions: std::vec!["memo_transfer".to_string()],
context: crate::Token2022StatefulContext::default(),
};
let result = crate::Token2022StatefulReadResult {
commitment: "confirmed".to_string(),
context_slot: 42,
snapshot: crate::Token2022StatefulSnapshotBundle {
account_key: account.0.clone(),
slot: 42,
state_kind: "account".to_string(),
extension_names: std::vec!["memo_transfer".to_string()],
outputs: std::vec![ks_lib::MtApiMaterializedOutput {
output_key: "state".to_string(),
family: ks_lib::MdMaterializedEventFamily::TokenAccount,
payload_json: serde_json::json!({
"domain":"spl_token_2022_account_state",
"baseFields":{"mint":mint.0,"owner":owner.0}
}),
}],
},
};
let report = super::validate_snapshot_requirement(&requirement, &result);
assert_eq!(
report.as_ref().map(|value| return value.checks.len()),
std::result::Result::Ok(6)
);
}
}

View File

@@ -0,0 +1,373 @@
// file: ks-pipeline/src/spl_token_2022_proof_orchestration.rs
// version: 6
//! Deterministic orchestration contract for mixed Token-2022 proof locations.
/// Maximum proof references accepted by one confidential Token-2022 operation.
pub const MAX_TOKEN_2022_OPERATION_PROOFS: usize = 5;
/// One bounded proof-orchestration request prepared before transaction assembly.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Token2022ProofOrchestrationRequest {
/// Stable executor operation code.
pub operation_code: std::string::String,
/// Exact ordered proof references required by the operation.
pub proofs: std::vec::Vec<ks_lib::ExSplTokenConfidentialProofReference>,
/// Optional authority expected on every context-state account.
pub expected_context_authority: std::option::Option<ks_lib::MdPubkey>,
/// Maximum compute-unit limit accepted for the future transaction.
pub compute_unit_limit: u32,
/// Maximum total fee accepted for the future transaction.
pub max_fee_lamports: u64,
/// Whether a future submission was explicitly requested.
pub submit: bool,
/// Whether the operator explicitly confirmed the future submission.
pub operator_confirmed: bool,
}
/// One deterministic mixed-proof orchestration result.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Token2022ProofOrchestrationReport {
/// Stable executor operation code.
pub operation_code: std::string::String,
/// Whether the instructions sysvar must be present.
pub instructions_sysvar_required: bool,
/// Ordered non-zero inline offsets.
pub inline_offsets: std::vec::Vec<i8>,
/// Ordered validated context-state requirements.
pub context_requirements: std::vec::Vec<crate::Token2022ProofContextRequirement>,
/// Simulation is always mandatory.
pub simulation_required: bool,
/// Whether the future send path is authorized by request-local policy.
pub send_authorized: bool,
/// Ordered successful checks.
pub checks: std::vec::Vec<std::string::String>,
}
/// Validates mixed inline/context-state proof orchestration before transaction assembly.
pub fn orchestrate_token_2022_proofs(
request: &crate::Token2022ProofOrchestrationRequest,
cryptographic_preflight: &crate::Token2022CryptographicPreflightReport,
) -> ks_core::Result<crate::Token2022ProofOrchestrationReport> {
if request.operation_code.trim().is_empty() {
return std::result::Result::Err(ks_core::Error::config(
"Token-2022 proof orchestration operation_code must not be empty",
));
}
if request.proofs.len() > crate::MAX_TOKEN_2022_OPERATION_PROOFS {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_operation_proof_limit_exceeded",
format!(
"Token-2022 proof orchestration accepts at most {} proofs",
crate::MAX_TOKEN_2022_OPERATION_PROOFS
),
));
}
if request.compute_unit_limit == 0 || request.max_fee_lamports == 0 {
return std::result::Result::Err(ks_core::Error::config(
"Token-2022 proof orchestration requires non-zero compute and fee ceilings",
));
}
if request.submit && !request.operator_confirmed {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_submission_confirmation_required",
"Token-2022 submission requires explicit operator confirmation",
));
}
let mut kinds = std::collections::BTreeSet::<std::string::String>::new();
let mut offsets = std::collections::BTreeSet::<i8>::new();
let mut context_accounts = std::collections::BTreeSet::<std::string::String>::new();
let mut inline_offsets = std::vec::Vec::<i8>::new();
let mut context_requirements = std::vec::Vec::<crate::Token2022ProofContextRequirement>::new();
for proof in &request.proofs {
let kind_code = proof_kind_code(proof.kind).to_string();
if !kinds.insert(kind_code.clone()) {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_duplicate_proof_kind",
format!(
"Token-2022 operation {} contains duplicate proof kind {kind_code}",
request.operation_code
),
));
}
if let std::result::Result::Err(error) = proof.location.validate() {
return std::result::Result::Err(ks_core::Error::invalid_state(error));
}
match &proof.location {
ks_lib::ExSplTokenConfidentialProofLocation::InstructionOffset { offset } => {
if !offsets.insert(*offset) {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_duplicate_inline_proof_offset",
format!(
"Token-2022 operation {} reuses inline proof offset {offset}",
request.operation_code
),
));
}
inline_offsets.push(*offset);
},
ks_lib::ExSplTokenConfidentialProofLocation::ContextStateAccount { account } => {
if !context_accounts.insert(account.0.clone()) {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_duplicate_context_state_account",
format!(
"Token-2022 operation {} reuses context-state account {}",
request.operation_code, account.0
),
));
}
context_requirements.push(crate::Token2022ProofContextRequirement {
role: kind_code,
account: account.clone(),
proof_type: proof_kind_to_zk_type(proof.kind),
expected_authority: request.expected_context_authority.clone(),
});
},
}
}
if let std::result::Result::Err(error) =
validate_context_reports(context_requirements.as_slice(), cryptographic_preflight)
{
return std::result::Result::Err(error);
}
return std::result::Result::Ok(crate::Token2022ProofOrchestrationReport {
operation_code: request.operation_code.clone(),
instructions_sysvar_required: !inline_offsets.is_empty(),
inline_offsets,
context_requirements,
simulation_required: true,
send_authorized: request.submit && request.operator_confirmed,
checks: vec![
"ordered_unique_proof_kinds".to_string(),
"non_zero_unique_inline_offsets".to_string(),
"unique_context_state_accounts".to_string(),
"context_preflight_matches_operation".to_string(),
"simulation_required".to_string(),
"bounded_compute_and_fee".to_string(),
],
});
}
fn validate_context_reports(
requirements: &[crate::Token2022ProofContextRequirement],
report: &crate::Token2022CryptographicPreflightReport,
) -> ks_core::Result<()> {
if requirements.len() != report.proof_contexts.len() {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_context_preflight_count_mismatch",
format!(
"Token-2022 operation requires {} context states, preflight contains {}",
requirements.len(),
report.proof_contexts.len()
),
));
}
for (requirement, observed) in requirements.iter().zip(report.proof_contexts.iter()) {
if requirement.account != observed.account
|| requirement.proof_type.discriminator() != observed.proof_discriminator
{
return std::result::Result::Err(ks_core::Error::new(
"token_2022_context_preflight_mismatch",
format!(
"Token-2022 proof context {} does not match the ordered operation requirement",
observed.account.0
),
));
}
}
return std::result::Result::Ok(());
}
fn proof_kind_code(kind: ks_lib::ExSplTokenConfidentialProofKind) -> &'static str {
return match kind {
ks_lib::ExSplTokenConfidentialProofKind::PubkeyValidity => "pubkey_validity",
ks_lib::ExSplTokenConfidentialProofKind::ZeroCiphertext => "zero_ciphertext",
ks_lib::ExSplTokenConfidentialProofKind::CiphertextCommitmentEquality => {
"ciphertext_commitment_equality"
},
ks_lib::ExSplTokenConfidentialProofKind::CiphertextCiphertextEquality => {
"ciphertext_ciphertext_equality"
},
ks_lib::ExSplTokenConfidentialProofKind::BatchedGroupedCiphertext3HandlesValidity => {
"batched_grouped_ciphertext_3_handles_validity"
},
ks_lib::ExSplTokenConfidentialProofKind::BatchedGroupedCiphertext2HandlesValidity => {
"batched_grouped_ciphertext_2_handles_validity"
},
ks_lib::ExSplTokenConfidentialProofKind::PercentageWithFee => "percentage_with_fee",
ks_lib::ExSplTokenConfidentialProofKind::BatchedRangeProofU64 => "batched_range_proof_u64",
ks_lib::ExSplTokenConfidentialProofKind::BatchedRangeProofU128 => {
"batched_range_proof_u128"
},
ks_lib::ExSplTokenConfidentialProofKind::BatchedRangeProofU256 => {
"batched_range_proof_u256"
},
};
}
fn proof_kind_to_zk_type(
kind: ks_lib::ExSplTokenConfidentialProofKind,
) -> ks_lib::ExSolanaCoreZkElGamalProofType {
return match kind {
ks_lib::ExSplTokenConfidentialProofKind::PubkeyValidity => {
ks_lib::ExSolanaCoreZkElGamalProofType::PubkeyValidity
},
ks_lib::ExSplTokenConfidentialProofKind::ZeroCiphertext => {
ks_lib::ExSolanaCoreZkElGamalProofType::ZeroCiphertext
},
ks_lib::ExSplTokenConfidentialProofKind::CiphertextCommitmentEquality => {
ks_lib::ExSolanaCoreZkElGamalProofType::CiphertextCommitmentEquality
},
ks_lib::ExSplTokenConfidentialProofKind::CiphertextCiphertextEquality => {
ks_lib::ExSolanaCoreZkElGamalProofType::CiphertextCiphertextEquality
},
ks_lib::ExSplTokenConfidentialProofKind::BatchedGroupedCiphertext3HandlesValidity => {
ks_lib::ExSolanaCoreZkElGamalProofType::BatchedGroupedCiphertext3HandlesValidity
},
ks_lib::ExSplTokenConfidentialProofKind::BatchedGroupedCiphertext2HandlesValidity => {
ks_lib::ExSolanaCoreZkElGamalProofType::BatchedGroupedCiphertext2HandlesValidity
},
ks_lib::ExSplTokenConfidentialProofKind::PercentageWithFee => {
ks_lib::ExSolanaCoreZkElGamalProofType::PercentageWithCap
},
ks_lib::ExSplTokenConfidentialProofKind::BatchedRangeProofU64 => {
ks_lib::ExSolanaCoreZkElGamalProofType::BatchedRangeProofU64
},
ks_lib::ExSplTokenConfidentialProofKind::BatchedRangeProofU128 => {
ks_lib::ExSolanaCoreZkElGamalProofType::BatchedRangeProofU128
},
ks_lib::ExSplTokenConfidentialProofKind::BatchedRangeProofU256 => {
ks_lib::ExSolanaCoreZkElGamalProofType::BatchedRangeProofU256
},
};
}
#[cfg(test)]
mod tests {
fn pubkey(byte: u8) -> ks_lib::MdPubkey {
return ks_lib::MdPubkey(bs58::encode([byte; 32]).into_string());
}
fn context_report(
account: ks_lib::MdPubkey,
proof_type: ks_lib::ExSolanaCoreZkElGamalProofType,
) -> crate::Token2022ProofContextReport {
return crate::Token2022ProofContextReport {
role: "proof".to_string(),
account,
proof_discriminator: proof_type.discriminator(),
context_state_bytes: proof_type.context_state_size(),
context_slot: 100,
checks: vec!["validated".to_string()],
};
}
#[test]
fn mixed_proofs_require_sysvar_and_preserve_ordered_contexts() {
let context = pubkey(1);
let request = crate::Token2022ProofOrchestrationRequest {
operation_code: "spl.token_2022.confidential_transfer".to_string(),
proofs: vec![
ks_lib::ExSplTokenConfidentialProofReference {
kind: ks_lib::ExSplTokenConfidentialProofKind::CiphertextCommitmentEquality,
location: ks_lib::ExSplTokenConfidentialProofLocation::InstructionOffset {
offset: 1,
},
},
ks_lib::ExSplTokenConfidentialProofReference {
kind: ks_lib::ExSplTokenConfidentialProofKind::BatchedRangeProofU128,
location: ks_lib::ExSplTokenConfidentialProofLocation::ContextStateAccount {
account: context.clone(),
},
},
],
expected_context_authority: std::option::Option::None,
compute_unit_limit: 400_000,
max_fee_lamports: 50_000,
submit: false,
operator_confirmed: false,
};
let preflight = crate::Token2022CryptographicPreflightReport {
commitment: "confirmed".to_string(),
context_slot: 100,
proof_contexts: vec![context_report(
context,
ks_lib::ExSolanaCoreZkElGamalProofType::BatchedRangeProofU128,
)],
};
let report = crate::orchestrate_token_2022_proofs(&request, &preflight)
.expect("mixed proof orchestration must succeed");
assert!(report.instructions_sysvar_required);
assert_eq!(report.inline_offsets, vec![1]);
assert_eq!(report.context_requirements.len(), 1);
assert!(report.simulation_required);
assert!(!report.send_authorized);
}
#[test]
fn context_only_proofs_do_not_require_instructions_sysvar() {
let context = pubkey(2);
let request = crate::Token2022ProofOrchestrationRequest {
operation_code: "spl.token_2022.empty_confidential_account".to_string(),
proofs: vec![ks_lib::ExSplTokenConfidentialProofReference {
kind: ks_lib::ExSplTokenConfidentialProofKind::ZeroCiphertext,
location: ks_lib::ExSplTokenConfidentialProofLocation::ContextStateAccount {
account: context.clone(),
},
}],
expected_context_authority: std::option::Option::None,
compute_unit_limit: 200_000,
max_fee_lamports: 20_000,
submit: true,
operator_confirmed: true,
};
let preflight = crate::Token2022CryptographicPreflightReport {
commitment: "confirmed".to_string(),
context_slot: 100,
proof_contexts: vec![context_report(
context,
ks_lib::ExSolanaCoreZkElGamalProofType::ZeroCiphertext,
)],
};
let report = crate::orchestrate_token_2022_proofs(&request, &preflight)
.expect("context-only orchestration must succeed");
assert!(!report.instructions_sysvar_required);
assert!(report.send_authorized);
}
#[test]
fn duplicate_offsets_contexts_and_unconfirmed_submission_fail_closed() {
let duplicate_offset = crate::Token2022ProofOrchestrationRequest {
operation_code: "operation".to_string(),
proofs: vec![
ks_lib::ExSplTokenConfidentialProofReference {
kind: ks_lib::ExSplTokenConfidentialProofKind::ZeroCiphertext,
location: ks_lib::ExSplTokenConfidentialProofLocation::InstructionOffset {
offset: 1,
},
},
ks_lib::ExSplTokenConfidentialProofReference {
kind: ks_lib::ExSplTokenConfidentialProofKind::BatchedRangeProofU64,
location: ks_lib::ExSplTokenConfidentialProofLocation::InstructionOffset {
offset: 1,
},
},
],
expected_context_authority: std::option::Option::None,
compute_unit_limit: 1,
max_fee_lamports: 1,
submit: false,
operator_confirmed: false,
};
let empty = crate::Token2022CryptographicPreflightReport {
commitment: "confirmed".to_string(),
context_slot: 0,
proof_contexts: vec![],
};
assert!(crate::orchestrate_token_2022_proofs(&duplicate_offset, &empty).is_err());
let mut unconfirmed = duplicate_offset;
unconfirmed.proofs.clear();
unconfirmed.submit = true;
assert!(crate::orchestrate_token_2022_proofs(&unconfirmed, &empty).is_err());
}
}

View File

@@ -0,0 +1,751 @@
// file: ks-pipeline/src/spl_token_2022_stateful.rs
// version: 9
//! Contextual Token-2022 account-state validation and materialization routing.
/// Optional external identities required to validate cross-account Token-2022 state.
#[derive(Clone, Debug, Default, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct Token2022StatefulContext {
/// Expected group account for one TokenGroupMember extension.
pub expected_group_address: std::option::Option<std::string::String>,
}
/// Maximum complete Token-2022 account data accepted by one bounded RPC read.
pub const MAX_TOKEN_2022_STATEFUL_ACCOUNT_BYTES: usize = 65_536;
/// One bounded Token-2022 account read request.
#[derive(Clone, Debug, PartialEq)]
pub struct Token2022StatefulReadRequest {
/// Endpoint role used for the HTTP RPC request.
pub query_role: std::string::String,
/// Canonical account address.
pub account: ks_lib::MdPubkey,
/// Expected Token-2022 base-state category.
pub kind: ks_lib::DcToken2022StateKind,
/// Optional minimum RPC context slot.
pub min_context_slot: std::option::Option<u64>,
/// Maximum decoded account bytes accepted from the endpoint.
pub max_data_bytes: usize,
/// Optional external identities needed by cross-account extensions.
pub context: crate::Token2022StatefulContext,
}
/// One bounded RPC read and its contextually validated projections.
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct Token2022StatefulReadResult {
/// Commitment used for the RPC read.
pub commitment: std::string::String,
/// Context slot returned by the endpoint.
pub context_slot: u64,
/// Complete validated snapshot bundle.
pub snapshot: crate::Token2022StatefulSnapshotBundle,
}
/// Reads, validates, parses, and routes one bounded Token-2022 account snapshot.
pub async fn read_token_2022_stateful_snapshot(
pool: &ks_onchain_transport::HttpEndpointPool,
request: &crate::Token2022StatefulReadRequest,
) -> ks_core::Result<crate::Token2022StatefulReadResult> {
if request.query_role.trim().is_empty() {
return std::result::Result::Err(ks_core::Error::config(
"Token-2022 stateful read query_role must not be empty",
));
}
if request.max_data_bytes == 0
|| request.max_data_bytes > crate::MAX_TOKEN_2022_STATEFUL_ACCOUNT_BYTES
{
return std::result::Result::Err(ks_core::Error::config(format!(
"Token-2022 stateful read max_data_bytes must be between 1 and {}",
crate::MAX_TOKEN_2022_STATEFUL_ACCOUNT_BYTES
)));
}
let config = match ks_onchain_transport::GetAccountInfoConfig::new_with_data(
ks_onchain_transport::RpcCommitmentLevel::Confirmed,
request.min_context_slot,
request.max_data_bytes,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let result = match pool
.get_account_info_for_role(request.query_role.as_str(), &request.account, &config)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return crate::materialize_token_2022_account_info_result(request, &result);
}
/// Validates one complete RPC account response before Token-2022 parsing and routing.
pub fn materialize_token_2022_account_info_result(
request: &crate::Token2022StatefulReadRequest,
result: &ks_onchain_transport::AccountInfoResult,
) -> ks_core::Result<crate::Token2022StatefulReadResult> {
if request.max_data_bytes == 0
|| request.max_data_bytes > crate::MAX_TOKEN_2022_STATEFUL_ACCOUNT_BYTES
{
return std::result::Result::Err(ks_core::Error::config(format!(
"Token-2022 stateful read max_data_bytes must be between 1 and {}",
crate::MAX_TOKEN_2022_STATEFUL_ACCOUNT_BYTES
)));
}
if let std::option::Option::Some(min_context_slot) = request.min_context_slot {
if result.context.slot < min_context_slot {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_stateful_context_slot_too_old",
format!(
"Token-2022 account context slot {} is below requested minimum {min_context_slot}",
result.context.slot
),
));
}
}
let account = match result.account.as_ref() {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_stateful_account_missing",
format!("Token-2022 account {} does not exist", request.account.0),
));
},
};
if account.executable {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_stateful_account_executable",
format!("Token-2022 state account {} must not be executable", request.account.0),
));
}
if account.owner.0 != ks_program_ids::SPL_TOKEN_2022_PROGRAM_ID {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_stateful_owner_mismatch",
format!(
"Token-2022 state account {} owner must be {}, got {}",
request.account.0,
ks_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
account.owner.0
),
));
}
if account.space > request.max_data_bytes as u64 || account.data.len() > request.max_data_bytes
{
return std::result::Result::Err(ks_core::Error::new(
"token_2022_stateful_account_too_large",
format!(
"Token-2022 account {} reports {} bytes and returned {} bytes above limit {}",
request.account.0,
account.space,
account.data.len(),
request.max_data_bytes
),
));
}
if account.space != account.data.len() as u64 {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_stateful_account_data_incomplete",
format!(
"Token-2022 account {} reports {} bytes but returned {} decoded bytes",
request.account.0,
account.space,
account.data.len()
),
));
}
let state = match ks_lib::decoder_spl_token_2022_parse_token_2022_state(
request.kind,
account.data.as_slice(),
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_stateful_parse_failed",
error,
));
},
};
let snapshot = match crate::materialize_parsed_token_2022_stateful_snapshot_with_context(
request.account.0.as_str(),
result.context.slot,
&state,
&request.context,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(ks_core::Error::new(
"token_2022_stateful_projection_failed",
error,
));
},
};
return std::result::Result::Ok(crate::Token2022StatefulReadResult {
commitment: "confirmed".to_string(),
context_slot: result.context.slot,
snapshot,
});
}
/// One contextually validated Token-2022 state snapshot and its owned projections.
#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct Token2022StatefulSnapshotBundle {
/// Canonical account identity used for contextual checks and stable output keys.
pub account_key: std::string::String,
/// Context slot associated with the account read.
pub slot: u64,
/// Parsed base-state category.
pub state_kind: std::string::String,
/// Ordered published or future extension names retained by the parser.
pub extension_names: std::vec::Vec<std::string::String>,
/// Processor-owned projections routed without duplicate ownership.
pub outputs: std::vec::Vec<ks_lib::MtApiMaterializedOutput>,
}
/// Parse, contextually validate, and materialize one bounded Token-2022 account snapshot.
pub fn materialize_token_2022_stateful_snapshot(
account_key: &str,
owner_program_id: &str,
slot: u64,
kind: ks_lib::DcToken2022StateKind,
data: &[u8],
) -> std::result::Result<crate::Token2022StatefulSnapshotBundle, String> {
if owner_program_id != ks_program_ids::SPL_TOKEN_2022_PROGRAM_ID {
return std::result::Result::Err(format!(
"Token-2022 state snapshot owner must be {}, got {owner_program_id}",
ks_program_ids::SPL_TOKEN_2022_PROGRAM_ID
));
}
let state = match ks_lib::decoder_spl_token_2022_parse_token_2022_state(kind, data) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
return crate::materialize_parsed_token_2022_stateful_snapshot_with_context(
account_key,
slot,
&state,
&crate::Token2022StatefulContext::default(),
);
}
/// Contextually validate and materialize one already parsed Token-2022 account snapshot.
pub fn materialize_parsed_token_2022_stateful_snapshot(
account_key: &str,
slot: u64,
state: &ks_lib::DcToken2022State,
) -> std::result::Result<crate::Token2022StatefulSnapshotBundle, String> {
return crate::materialize_parsed_token_2022_stateful_snapshot_with_context(
account_key,
slot,
state,
&crate::Token2022StatefulContext::default(),
);
}
/// Contextually validate one parsed Token-2022 snapshot with external cross-account identities.
pub fn materialize_parsed_token_2022_stateful_snapshot_with_context(
account_key: &str,
slot: u64,
state: &ks_lib::DcToken2022State,
context: &crate::Token2022StatefulContext,
) -> std::result::Result<crate::Token2022StatefulSnapshotBundle, String> {
if account_key.trim().is_empty() {
return std::result::Result::Err(
"Token-2022 stateful snapshot requires a non-empty account key".to_string(),
);
}
let decoded = match bs58::decode(account_key).into_vec() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => {
return std::result::Result::Err(
"Token-2022 stateful snapshot account key must be valid base58".to_string(),
);
},
};
if decoded.len() != 32 {
return std::result::Result::Err(format!(
"Token-2022 stateful snapshot account key must decode to 32 bytes, got {}",
decoded.len()
));
}
match validate_embedded_mint_identity(account_key, state) {
std::result::Result::Ok(()) => {},
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
match validate_group_member_identity(state, context) {
std::result::Result::Ok(()) => {},
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
let account_output = match ks_lib::materializer_token_materialize_token_2022_state_snapshot(
account_key,
slot,
state,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
let mut outputs = std::vec![account_output];
if state.kind == ks_lib::DcToken2022StateKind::Mint {
let metadata_outputs = match ks_lib::materializer_metadata_materialize_token_2022_snapshot(
account_key,
slot,
state,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
outputs.extend(metadata_outputs);
}
let fee_outputs = match ks_lib::materializer_fees_materialize_token_2022_state_snapshots(
account_key,
slot,
state,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
outputs.extend(fee_outputs);
let admin_outputs = match ks_lib::materializer_admin_materialize_token_2022_state_snapshots(
account_key,
slot,
state,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error),
};
outputs.extend(admin_outputs);
let state_kind = match state.kind {
ks_lib::DcToken2022StateKind::Mint => "mint",
ks_lib::DcToken2022StateKind::Account => "account",
ks_lib::DcToken2022StateKind::Multisig => "multisig",
};
return std::result::Result::Ok(crate::Token2022StatefulSnapshotBundle {
account_key: account_key.to_string(),
slot,
state_kind: state_kind.to_string(),
extension_names: state
.extensions
.iter()
.map(|entry| return entry.extension_name.to_string())
.collect(),
outputs,
});
}
fn validate_embedded_mint_identity(
account_key: &str,
state: &ks_lib::DcToken2022State,
) -> std::result::Result<(), String> {
for entry in &state.extensions {
if entry.extension_name != "token_metadata"
&& entry.extension_name != "token_group"
&& entry.extension_name != "token_group_member"
{
continue;
}
let embedded_mint = entry.value_fields.get("mint").and_then(serde_json::Value::as_str);
let embedded_mint = match embedded_mint {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(format!(
"Token-2022 {} extension requires a structured mint field",
entry.extension_name
));
},
};
if embedded_mint != account_key {
return std::result::Result::Err(format!(
"Token-2022 {} mint {} does not match account {account_key}",
entry.extension_name, embedded_mint
));
}
}
return std::result::Result::Ok(());
}
fn validate_group_member_identity(
state: &ks_lib::DcToken2022State,
context: &crate::Token2022StatefulContext,
) -> std::result::Result<(), String> {
for entry in &state.extensions {
if entry.extension_name != "token_group_member" {
continue;
}
let group = entry.value_fields.get("group").and_then(serde_json::Value::as_str);
let group = match group {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(
"Token-2022 token_group_member extension requires a structured group field"
.to_string(),
);
},
};
let expected = match context.expected_group_address.as_deref() {
std::option::Option::Some(value) => value,
std::option::Option::None => {
return std::result::Result::Err(
"Token-2022 token_group_member validation requires an expected group address"
.to_string(),
);
},
};
if group != expected {
return std::result::Result::Err(format!(
"Token-2022 token_group_member group {group} does not match expected group {expected}"
));
}
}
return std::result::Result::Ok(());
}
#[cfg(test)]
mod tests {
fn account_key(byte: u8) -> String {
return bs58::encode([byte; 32]).into_string();
}
fn mint_state(account_key: &str) -> ks_lib::DcToken2022State {
return ks_lib::DcToken2022State {
kind: ks_lib::DcToken2022StateKind::Mint,
base_fields: serde_json::json!({"supply":"1","decimals":0,"initialized":true}),
base_hex: "00".repeat(82),
account_type: std::option::Option::Some(1),
extensions: std::vec![
ks_lib::DcToken2022TlvEntry {
extension_type: 19,
extension_name: "token_metadata",
value_hex: "01".to_string(),
value_fields: serde_json::json!({
"mint": account_key,
"name": "Token",
"symbol": "TOK",
"uri": "https://example.invalid/token.json"
}),
},
ks_lib::DcToken2022TlvEntry {
extension_type: 20,
extension_name: "token_group",
value_hex: "02".to_string(),
value_fields: serde_json::json!({
"mint": account_key,
"size": "1",
"maxSize": "10"
}),
},
],
};
}
#[test]
fn mint_snapshot_routes_account_and_metadata_outputs_once() {
let account_key = account_key(7);
let state = mint_state(account_key.as_str());
let bundle = crate::materialize_parsed_token_2022_stateful_snapshot(
account_key.as_str(),
42,
&state,
);
assert_eq!(
bundle.as_ref().map(|value| return value.outputs.len()),
std::result::Result::Ok(3)
);
assert_eq!(
bundle.as_ref().map(|value| return value.state_kind.clone()),
std::result::Result::Ok("mint".to_string())
);
assert_eq!(
bundle.as_ref().map(|value| return value.extension_names.clone()),
std::result::Result::Ok(std::vec![
"token_metadata".to_string(),
"token_group".to_string()
])
);
}
#[test]
fn embedded_metadata_mint_must_match_the_account_identity() {
let account_address = account_key(8);
let mut state = mint_state(account_address.as_str());
state.extensions[0].value_fields["mint"] = serde_json::json!(account_key(9));
let result = crate::materialize_parsed_token_2022_stateful_snapshot(
account_address.as_str(),
42,
&state,
);
assert!(result.is_err());
}
#[test]
fn owner_and_account_identity_fail_closed_before_projection() {
let account_key = account_key(10);
let data = [0u8; 82];
let wrong_owner = crate::materialize_token_2022_stateful_snapshot(
account_key.as_str(),
ks_program_ids::SPL_TOKEN_PROGRAM_ID,
1,
ks_lib::DcToken2022StateKind::Mint,
&data,
);
assert!(wrong_owner.is_err());
let state = mint_state(account_key.as_str());
let malformed_key =
crate::materialize_parsed_token_2022_stateful_snapshot("not-base58-0", 1, &state);
assert!(malformed_key.is_err());
}
#[test]
fn account_snapshot_has_no_metadata_projection() {
let account_key = account_key(11);
let state = ks_lib::DcToken2022State {
kind: ks_lib::DcToken2022StateKind::Account,
base_fields: serde_json::json!({"amount":"0","state":"initialized"}),
base_hex: "00".repeat(165),
account_type: std::option::Option::None,
extensions: std::vec::Vec::new(),
};
let bundle =
crate::materialize_parsed_token_2022_stateful_snapshot(account_key.as_str(), 9, &state);
assert_eq!(
bundle.as_ref().map(|value| return value.outputs.len()),
std::result::Result::Ok(1)
);
}
#[test]
fn group_member_requires_and_matches_external_group_identity() {
let account_address = account_key(12);
let group_address = account_key(13);
let state = ks_lib::DcToken2022State {
kind: ks_lib::DcToken2022StateKind::Mint,
base_fields: serde_json::json!({"supply":"1","decimals":0,"initialized":true}),
base_hex: "00".repeat(82),
account_type: std::option::Option::Some(1),
extensions: std::vec![ks_lib::DcToken2022TlvEntry {
extension_type: 23,
extension_name: "token_group_member",
value_hex: "03".to_string(),
value_fields: serde_json::json!({
"mint": account_address,
"group": group_address,
"memberNumber": "1"
}),
}],
};
let missing = crate::materialize_parsed_token_2022_stateful_snapshot(
account_address.as_str(),
44,
&state,
);
assert!(missing.is_err());
let wrong_context = crate::Token2022StatefulContext {
expected_group_address: std::option::Option::Some(account_key(14)),
};
let wrong = crate::materialize_parsed_token_2022_stateful_snapshot_with_context(
account_address.as_str(),
44,
&state,
&wrong_context,
);
assert!(wrong.is_err());
let context = crate::Token2022StatefulContext {
expected_group_address: std::option::Option::Some(group_address),
};
let valid = crate::materialize_parsed_token_2022_stateful_snapshot_with_context(
account_address.as_str(),
44,
&state,
&context,
);
assert_eq!(
valid.as_ref().map(|bundle| return bundle.outputs.len()),
std::result::Result::Ok(2)
);
}
#[test]
fn mint_and_account_fee_extensions_route_to_the_fee_owner_once() {
let mint_address = account_key(15);
let mint = ks_lib::DcToken2022State {
kind: ks_lib::DcToken2022StateKind::Mint,
base_fields: serde_json::json!({"supply":"1","decimals":0,"initialized":true}),
base_hex: "00".repeat(82),
account_type: std::option::Option::Some(1),
extensions: std::vec![ks_lib::DcToken2022TlvEntry {
extension_type: 1,
extension_name: "transfer_fee_config",
value_hex: "01".to_string(),
value_fields: serde_json::json!({"withheldAmount":"7"}),
}],
};
let mint_bundle = crate::materialize_parsed_token_2022_stateful_snapshot(
mint_address.as_str(),
50,
&mint,
);
assert_eq!(
mint_bundle.as_ref().map(|bundle| return bundle.outputs.len()),
std::result::Result::Ok(2)
);
assert_eq!(
mint_bundle
.as_ref()
.map(|bundle| return bundle.outputs[1].payload_json["provenance"]["processorName"]
.clone()),
std::result::Result::Ok(serde_json::json!("materializer.fees"))
);
let account_address = account_key(16);
let account = ks_lib::DcToken2022State {
kind: ks_lib::DcToken2022StateKind::Account,
base_fields: serde_json::json!({"amount":"0","state":"initialized"}),
base_hex: "00".repeat(165),
account_type: std::option::Option::Some(2),
extensions: std::vec![ks_lib::DcToken2022TlvEntry {
extension_type: 17,
extension_name: "confidential_transfer_fee_amount",
value_hex: "02".to_string(),
value_fields: serde_json::json!({"withheldAmount":"ciphertext"}),
}],
};
let account_bundle = crate::materialize_parsed_token_2022_stateful_snapshot(
account_address.as_str(),
51,
&account,
);
assert_eq!(
account_bundle.as_ref().map(|bundle| return bundle.outputs.len()),
std::result::Result::Ok(2)
);
assert_eq!(
account_bundle
.as_ref()
.map(|bundle| return bundle.outputs[1].payload_json["confidentialValuesDecrypted"]
.clone()),
std::result::Result::Ok(serde_json::json!(false))
);
}
#[test]
fn mint_admin_extensions_route_to_the_admin_owner_once() {
let account_key = account_key(12);
let mut state = mint_state(account_key.as_str());
state.extensions.push(ks_lib::DcToken2022TlvEntry {
extension_type: 6,
extension_name: "default_account_state",
value_hex: "02".to_string(),
value_fields: serde_json::json!({"state": 2}),
});
let bundle = crate::materialize_parsed_token_2022_stateful_snapshot(
account_key.as_str(),
17,
&state,
);
assert_eq!(
bundle.as_ref().map(|value| return value.outputs.len()),
std::result::Result::Ok(4)
);
assert_eq!(
bundle.as_ref().map(|value| {
return value
.outputs
.iter()
.filter(|output| {
return output.payload_json["domain"]
== serde_json::json!("token_2022_extension_admin_state");
})
.count();
}),
std::result::Result::Ok(1)
);
}
#[test]
fn complete_rpc_account_routes_only_after_owner_size_and_context_validation() {
let account = account_key(21);
let request = crate::Token2022StatefulReadRequest {
query_role: "execution".to_string(),
account: ks_lib::MdPubkey(account.clone()),
kind: ks_lib::DcToken2022StateKind::Mint,
min_context_slot: std::option::Option::Some(40),
max_data_bytes: 82,
context: crate::Token2022StatefulContext::default(),
};
let result = ks_onchain_transport::AccountInfoResult {
context: ks_onchain_transport::RpcResponseContext {
slot: 41,
api_version: std::option::Option::None,
},
account: std::option::Option::Some(ks_onchain_transport::AccountInfoValue {
lamports: 1,
owner: ks_lib::MdProgramId(ks_program_ids::SPL_TOKEN_2022_PROGRAM_ID.to_string()),
executable: false,
rent_epoch: 0,
space: 82,
data: std::vec![0; 82],
}),
};
let materialized = crate::materialize_token_2022_account_info_result(&request, &result);
assert_eq!(
materialized.as_ref().map(|value| return value.context_slot),
std::result::Result::Ok(41)
);
assert_eq!(
materialized.as_ref().map(|value| return value.snapshot.outputs.len()),
std::result::Result::Ok(1)
);
}
#[test]
fn incomplete_foreign_executable_and_stale_rpc_accounts_fail_closed() {
let account = account_key(22);
let request = crate::Token2022StatefulReadRequest {
query_role: "execution".to_string(),
account: ks_lib::MdPubkey(account),
kind: ks_lib::DcToken2022StateKind::Mint,
min_context_slot: std::option::Option::Some(50),
max_data_bytes: 82,
context: crate::Token2022StatefulContext::default(),
};
let base = ks_onchain_transport::AccountInfoValue {
lamports: 1,
owner: ks_lib::MdProgramId(ks_program_ids::SPL_TOKEN_2022_PROGRAM_ID.to_string()),
executable: false,
rent_epoch: 0,
space: 82,
data: std::vec![0; 82],
};
let stale = ks_onchain_transport::AccountInfoResult {
context: ks_onchain_transport::RpcResponseContext {
slot: 49,
api_version: std::option::Option::None,
},
account: std::option::Option::Some(base.clone()),
};
assert!(crate::materialize_token_2022_account_info_result(&request, &stale).is_err());
let mut foreign = base.clone();
foreign.owner = ks_lib::MdProgramId(ks_program_ids::SPL_TOKEN_PROGRAM_ID.to_string());
let foreign = ks_onchain_transport::AccountInfoResult {
context: ks_onchain_transport::RpcResponseContext {
slot: 50,
api_version: std::option::Option::None,
},
account: std::option::Option::Some(foreign),
};
assert!(crate::materialize_token_2022_account_info_result(&request, &foreign).is_err());
let mut executable = base.clone();
executable.executable = true;
let executable = ks_onchain_transport::AccountInfoResult {
context: ks_onchain_transport::RpcResponseContext {
slot: 50,
api_version: std::option::Option::None,
},
account: std::option::Option::Some(executable),
};
assert!(crate::materialize_token_2022_account_info_result(&request, &executable).is_err());
let mut incomplete = base;
incomplete.data.pop();
let incomplete = ks_onchain_transport::AccountInfoResult {
context: ks_onchain_transport::RpcResponseContext {
slot: 50,
api_version: std::option::Option::None,
},
account: std::option::Option::Some(incomplete),
};
assert!(crate::materialize_token_2022_account_info_result(&request, &incomplete).is_err());
}
}

File diff suppressed because it is too large Load Diff