v0.4.8-pre.008
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: kb-pipeline/src/lib.rs
|
||||
// version: 18
|
||||
// version: 20
|
||||
|
||||
#![forbid(unsafe_code)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -11,20 +11,23 @@ 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_ata_stateful;
|
||||
mod solana_elgamal_registry_stateful;
|
||||
mod solana_metaplex_token_metadata_execution_orchestration;
|
||||
mod solana_metaplex_token_metadata_preflight;
|
||||
mod solana_metaplex_token_metadata_stateful;
|
||||
mod solana_stateful;
|
||||
mod solana_token_2022_correlation;
|
||||
mod solana_token_2022_crypto_preflight;
|
||||
mod solana_token_2022_execution_orchestration;
|
||||
mod solana_token_2022_preflight;
|
||||
mod solana_token_2022_proof_orchestration;
|
||||
mod solana_token_2022_stateful;
|
||||
mod solana_token_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_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;
|
||||
@@ -90,74 +93,88 @@ pub use self::decode_replay::INSTRUCTION_DECODE_STAGE;
|
||||
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 `kb-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;
|
||||
/// Stateful invariants observed after one confirmed Associated Token Account execution.
|
||||
pub use self::solana_ata_stateful::SplAssociatedTokenAccountPostExecutionReport;
|
||||
/// One machine-readable Associated Token Account stateful check.
|
||||
pub use self::solana_ata_stateful::SplAssociatedTokenAccountStatefulCheck;
|
||||
/// One contextual Associated Token Account stateful fact.
|
||||
pub use self::solana_ata_stateful::SplAssociatedTokenAccountStatefulFact;
|
||||
/// Complete Associated Token Account stateful readiness report.
|
||||
pub use self::solana_ata_stateful::SplAssociatedTokenAccountStatefulReadinessReport;
|
||||
/// Complete request for one Associated Token Account stateful readiness inspection.
|
||||
pub use self::solana_ata_stateful::SplAssociatedTokenAccountStatefulReadinessRequest;
|
||||
/// Associated Token Account stateful readiness outcome.
|
||||
pub use self::solana_ata_stateful::SplAssociatedTokenAccountStatefulReadinessStatus;
|
||||
/// Verifies final Associated Token Account relationships after confirmed execution.
|
||||
pub use self::solana_ata_stateful::inspect_spl_associated_token_account_post_execution;
|
||||
/// Inspects state required before simulating one Associated Token Account operation.
|
||||
pub use self::solana_ata_stateful::inspect_spl_associated_token_account_stateful_readiness;
|
||||
/// Migrated ELGAMAL_REGISTRY_STATEFUL_ACCOUNT_BYTES contract.
|
||||
pub use self::solana_elgamal_registry_stateful::ELGAMAL_REGISTRY_STATEFUL_ACCOUNT_BYTES;
|
||||
/// Migrated ElGamalRegistryStatefulReadRequest contract.
|
||||
pub use self::solana_elgamal_registry_stateful::ElGamalRegistryStatefulReadRequest;
|
||||
/// Migrated ElGamalRegistryStatefulReadResult contract.
|
||||
pub use self::solana_elgamal_registry_stateful::ElGamalRegistryStatefulReadResult;
|
||||
/// Migrated ElGamalRegistryStatefulSnapshot contract.
|
||||
pub use self::solana_elgamal_registry_stateful::ElGamalRegistryStatefulSnapshot;
|
||||
/// Migrated materialize_elgamal_registry_account_info_result contract.
|
||||
pub use self::solana_elgamal_registry_stateful::materialize_elgamal_registry_account_info_result;
|
||||
/// Migrated materialize_elgamal_registry_stateful_snapshot contract.
|
||||
pub use self::solana_elgamal_registry_stateful::materialize_elgamal_registry_stateful_snapshot;
|
||||
/// Migrated read_elgamal_registry_stateful_snapshot contract.
|
||||
pub use self::solana_elgamal_registry_stateful::read_elgamal_registry_stateful_snapshot;
|
||||
/// Maximum number of resolved signers accepted by one Metaplex execution envelope.
|
||||
pub use self::solana_metaplex_token_metadata_execution_orchestration::MAX_METAPLEX_TOKEN_METADATA_EXECUTION_SIGNERS;
|
||||
/// Deterministic Metaplex execution-readiness report.
|
||||
pub use self::solana_metaplex_token_metadata_execution_orchestration::MetaplexTokenMetadataExecutionReadinessReport;
|
||||
/// Complete Metaplex execution-readiness request.
|
||||
pub use self::solana_metaplex_token_metadata_execution_orchestration::MetaplexTokenMetadataExecutionReadinessRequest;
|
||||
/// Result of one Metaplex stateful postcondition.
|
||||
pub use self::solana_metaplex_token_metadata_execution_orchestration::MetaplexTokenMetadataPostconditionStatus;
|
||||
/// Aggregates Metaplex postconditions without inventing success.
|
||||
pub use self::solana_metaplex_token_metadata_execution_orchestration::summarize_metaplex_token_metadata_postconditions;
|
||||
/// Validates the complete Metaplex execution envelope before signing.
|
||||
pub use self::solana_metaplex_token_metadata_execution_orchestration::validate_metaplex_token_metadata_execution_readiness;
|
||||
/// Maximum number of correlated Metaplex snapshots accepted by one preflight.
|
||||
pub use self::solana_metaplex_token_metadata_preflight::MAX_METAPLEX_TOKEN_METADATA_PREFLIGHT_ACCOUNTS;
|
||||
/// Deterministic Metaplex preflight report.
|
||||
pub use self::solana_metaplex_token_metadata_preflight::MetaplexTokenMetadataPreflightReport;
|
||||
/// Complete Metaplex preflight request.
|
||||
pub use self::solana_metaplex_token_metadata_preflight::MetaplexTokenMetadataPreflightRequest;
|
||||
/// Inspects one prepared Metaplex plan against confirmed state snapshots.
|
||||
pub use self::solana_metaplex_token_metadata_preflight::inspect_metaplex_token_metadata_preflight;
|
||||
/// Maximum complete Metaplex account data accepted by one RPC read.
|
||||
pub use self::solana_metaplex_token_metadata_stateful::MAX_METAPLEX_TOKEN_METADATA_ACCOUNT_BYTES;
|
||||
/// Supported Metaplex account category for one stateful read.
|
||||
pub use self::solana_metaplex_token_metadata_stateful::MetaplexTokenMetadataAccountKind;
|
||||
/// One bounded Metaplex stateful read request.
|
||||
pub use self::solana_metaplex_token_metadata_stateful::MetaplexTokenMetadataStatefulReadRequest;
|
||||
/// One bounded Metaplex stateful read result.
|
||||
pub use self::solana_metaplex_token_metadata_stateful::MetaplexTokenMetadataStatefulReadResult;
|
||||
/// Canonical bounded Metaplex state snapshot.
|
||||
pub use self::solana_metaplex_token_metadata_stateful::MetaplexTokenMetadataStatefulSnapshot;
|
||||
/// Validates one RPC account response and delegates parsing to `kb-lib`.
|
||||
pub use self::solana_metaplex_token_metadata_stateful::materialize_metaplex_token_metadata_account_info_result;
|
||||
/// Reads one bounded Metaplex Token Metadata account.
|
||||
pub use self::solana_metaplex_token_metadata_stateful::read_metaplex_token_metadata_stateful_snapshot;
|
||||
/// One machine-readable native Solana stateful readiness check.
|
||||
pub use self::solana_stateful::SolanaCoreStatefulCheck;
|
||||
/// One contextual fact measured during native Solana stateful readiness.
|
||||
@@ -170,94 +187,124 @@ pub use self::solana_stateful::SolanaCoreStatefulReadinessRequest;
|
||||
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::solana_token_2022_correlation::Token2022CorrelationStatus;
|
||||
pub use self::spl_token_2022_correlation::Token2022CorrelationStatus;
|
||||
/// Deterministic correlation report for one instruction output and one final snapshot.
|
||||
pub use self::solana_token_2022_correlation::Token2022StateCorrelationReport;
|
||||
pub use self::spl_token_2022_correlation::Token2022StateCorrelationReport;
|
||||
/// Correlates one materialized instruction fact with one authoritative Token-2022 snapshot.
|
||||
pub use self::solana_token_2022_correlation::correlate_token_2022_instruction_with_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::solana_token_2022_crypto_preflight::MAX_TOKEN_2022_PROOF_CONTEXTS;
|
||||
pub use self::spl_token_2022_crypto_preflight::MAX_TOKEN_2022_PROOF_CONTEXTS;
|
||||
/// Token-2022 cryptographic preflight report.
|
||||
pub use self::solana_token_2022_crypto_preflight::Token2022CryptographicPreflightReport;
|
||||
pub use self::spl_token_2022_crypto_preflight::Token2022CryptographicPreflightReport;
|
||||
/// Token-2022 cryptographic preflight request.
|
||||
pub use self::solana_token_2022_crypto_preflight::Token2022CryptographicPreflightRequest;
|
||||
pub use self::spl_token_2022_crypto_preflight::Token2022CryptographicPreflightRequest;
|
||||
/// One validated proof context report.
|
||||
pub use self::solana_token_2022_crypto_preflight::Token2022ProofContextReport;
|
||||
pub use self::spl_token_2022_crypto_preflight::Token2022ProofContextReport;
|
||||
/// One required proof context-state account.
|
||||
pub use self::solana_token_2022_crypto_preflight::Token2022ProofContextRequirement;
|
||||
pub use self::spl_token_2022_crypto_preflight::Token2022ProofContextRequirement;
|
||||
/// Encoded proof context metadata bytes.
|
||||
pub use self::solana_token_2022_crypto_preflight::ZK_PROOF_CONTEXT_META_BYTES;
|
||||
pub use self::spl_token_2022_crypto_preflight::ZK_PROOF_CONTEXT_META_BYTES;
|
||||
/// Inspects bounded Token-2022 cryptographic proof contexts.
|
||||
pub use self::solana_token_2022_crypto_preflight::inspect_token_2022_cryptographic_preflight;
|
||||
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::solana_token_2022_execution_orchestration::MAX_TOKEN_2022_EXECUTION_SIGNERS;
|
||||
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::solana_token_2022_execution_orchestration::Token2022ExecutionPostcondition;
|
||||
pub use self::spl_token_2022_execution_orchestration::Token2022ExecutionPostcondition;
|
||||
/// Result of one stateful postcondition after Token-2022 execution.
|
||||
pub use self::solana_token_2022_execution_orchestration::Token2022ExecutionPostconditionStatus;
|
||||
pub use self::spl_token_2022_execution_orchestration::Token2022ExecutionPostconditionStatus;
|
||||
/// Deterministic Token-2022 execution-readiness report.
|
||||
pub use self::solana_token_2022_execution_orchestration::Token2022ExecutionReadinessReport;
|
||||
pub use self::spl_token_2022_execution_orchestration::Token2022ExecutionReadinessReport;
|
||||
/// Complete deterministic Token-2022 execution-readiness request.
|
||||
pub use self::solana_token_2022_execution_orchestration::Token2022ExecutionReadinessRequest;
|
||||
pub use self::spl_token_2022_execution_orchestration::Token2022ExecutionReadinessRequest;
|
||||
/// Aggregates Token-2022 postconditions without inventing success.
|
||||
pub use self::solana_token_2022_execution_orchestration::summarize_token_2022_postconditions;
|
||||
pub use self::spl_token_2022_execution_orchestration::summarize_token_2022_postconditions;
|
||||
/// Validates the complete Token-2022 execution envelope before signing.
|
||||
pub use self::solana_token_2022_execution_orchestration::validate_token_2022_execution_readiness;
|
||||
pub use self::spl_token_2022_execution_orchestration::validate_token_2022_execution_readiness;
|
||||
/// Migrated MAX_TOKEN_2022_PREFLIGHT_ACCOUNTS contract.
|
||||
pub use self::solana_token_2022_preflight::MAX_TOKEN_2022_PREFLIGHT_ACCOUNTS;
|
||||
pub use self::spl_token_2022_preflight::MAX_TOKEN_2022_PREFLIGHT_ACCOUNTS;
|
||||
/// Migrated MAX_TOKEN_2022_PREFLIGHT_TOTAL_BYTES contract.
|
||||
pub use self::solana_token_2022_preflight::MAX_TOKEN_2022_PREFLIGHT_TOTAL_BYTES;
|
||||
pub use self::spl_token_2022_preflight::MAX_TOKEN_2022_PREFLIGHT_TOTAL_BYTES;
|
||||
/// Migrated Token2022PreflightAccountReport contract.
|
||||
pub use self::solana_token_2022_preflight::Token2022PreflightAccountReport;
|
||||
pub use self::spl_token_2022_preflight::Token2022PreflightAccountReport;
|
||||
/// Migrated Token2022PreflightReport contract.
|
||||
pub use self::solana_token_2022_preflight::Token2022PreflightReport;
|
||||
pub use self::spl_token_2022_preflight::Token2022PreflightReport;
|
||||
/// Migrated Token2022PreflightRequest contract.
|
||||
pub use self::solana_token_2022_preflight::Token2022PreflightRequest;
|
||||
pub use self::spl_token_2022_preflight::Token2022PreflightRequest;
|
||||
/// Migrated Token2022PreflightRequirement contract.
|
||||
pub use self::solana_token_2022_preflight::Token2022PreflightRequirement;
|
||||
pub use self::spl_token_2022_preflight::Token2022PreflightRequirement;
|
||||
/// Migrated inspect_token_2022_preflight contract.
|
||||
pub use self::solana_token_2022_preflight::inspect_token_2022_preflight;
|
||||
pub use self::spl_token_2022_preflight::inspect_token_2022_preflight;
|
||||
/// Maximum number of proofs accepted for one Token-2022 operation.
|
||||
pub use self::solana_token_2022_proof_orchestration::MAX_TOKEN_2022_OPERATION_PROOFS;
|
||||
pub use self::spl_token_2022_proof_orchestration::MAX_TOKEN_2022_OPERATION_PROOFS;
|
||||
/// Token-2022 proof orchestration report.
|
||||
pub use self::solana_token_2022_proof_orchestration::Token2022ProofOrchestrationReport;
|
||||
pub use self::spl_token_2022_proof_orchestration::Token2022ProofOrchestrationReport;
|
||||
/// Token-2022 proof orchestration request.
|
||||
pub use self::solana_token_2022_proof_orchestration::Token2022ProofOrchestrationRequest;
|
||||
pub use self::spl_token_2022_proof_orchestration::Token2022ProofOrchestrationRequest;
|
||||
/// Validates mixed inline and context-state proof orchestration.
|
||||
pub use self::solana_token_2022_proof_orchestration::orchestrate_token_2022_proofs;
|
||||
pub use self::spl_token_2022_proof_orchestration::orchestrate_token_2022_proofs;
|
||||
/// Migrated MAX_TOKEN_2022_STATEFUL_ACCOUNT_BYTES contract.
|
||||
pub use self::solana_token_2022_stateful::MAX_TOKEN_2022_STATEFUL_ACCOUNT_BYTES;
|
||||
pub use self::spl_token_2022_stateful::MAX_TOKEN_2022_STATEFUL_ACCOUNT_BYTES;
|
||||
/// Migrated Token2022StatefulContext contract.
|
||||
pub use self::solana_token_2022_stateful::Token2022StatefulContext;
|
||||
pub use self::spl_token_2022_stateful::Token2022StatefulContext;
|
||||
/// Migrated Token2022StatefulReadRequest contract.
|
||||
pub use self::solana_token_2022_stateful::Token2022StatefulReadRequest;
|
||||
pub use self::spl_token_2022_stateful::Token2022StatefulReadRequest;
|
||||
/// Migrated Token2022StatefulReadResult contract.
|
||||
pub use self::solana_token_2022_stateful::Token2022StatefulReadResult;
|
||||
pub use self::spl_token_2022_stateful::Token2022StatefulReadResult;
|
||||
/// Migrated Token2022StatefulSnapshotBundle contract.
|
||||
pub use self::solana_token_2022_stateful::Token2022StatefulSnapshotBundle;
|
||||
pub use self::spl_token_2022_stateful::Token2022StatefulSnapshotBundle;
|
||||
/// Migrated materialize_parsed_token_2022_stateful_snapshot contract.
|
||||
pub use self::solana_token_2022_stateful::materialize_parsed_token_2022_stateful_snapshot;
|
||||
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::solana_token_2022_stateful::materialize_parsed_token_2022_stateful_snapshot_with_context;
|
||||
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::solana_token_2022_stateful::materialize_token_2022_account_info_result;
|
||||
pub use self::spl_token_2022_stateful::materialize_token_2022_account_info_result;
|
||||
/// Migrated materialize_token_2022_stateful_snapshot contract.
|
||||
pub use self::solana_token_2022_stateful::materialize_token_2022_stateful_snapshot;
|
||||
pub use self::spl_token_2022_stateful::materialize_token_2022_stateful_snapshot;
|
||||
/// Migrated read_token_2022_stateful_snapshot contract.
|
||||
pub use self::solana_token_2022_stateful::read_token_2022_stateful_snapshot;
|
||||
pub use self::spl_token_2022_stateful::read_token_2022_stateful_snapshot;
|
||||
/// One machine-readable classic SPL Token stateful check.
|
||||
pub use self::solana_token_stateful::SplTokenStatefulCheck;
|
||||
pub use self::spl_token_stateful::SplTokenStatefulCheck;
|
||||
/// One contextual classic SPL Token stateful fact.
|
||||
pub use self::solana_token_stateful::SplTokenStatefulFact;
|
||||
pub use self::spl_token_stateful::SplTokenStatefulFact;
|
||||
/// Complete classic SPL Token stateful readiness report.
|
||||
pub use self::solana_token_stateful::SplTokenStatefulReadinessReport;
|
||||
pub use self::spl_token_stateful::SplTokenStatefulReadinessReport;
|
||||
/// Complete request for one classic SPL Token stateful readiness inspection.
|
||||
pub use self::solana_token_stateful::SplTokenStatefulReadinessRequest;
|
||||
pub use self::spl_token_stateful::SplTokenStatefulReadinessRequest;
|
||||
/// Classic SPL Token stateful readiness outcome.
|
||||
pub use self::solana_token_stateful::SplTokenStatefulReadinessStatus;
|
||||
pub use self::spl_token_stateful::SplTokenStatefulReadinessStatus;
|
||||
/// Inspects Localnet or Devnet classic SPL Token state required before simulation.
|
||||
pub use self::solana_token_stateful::inspect_spl_token_stateful_readiness;
|
||||
pub use self::spl_token_stateful::inspect_spl_token_stateful_readiness;
|
||||
|
||||
/// Canonical tracing target for pipeline orchestration.
|
||||
pub(crate) use self::constants::TRACING_TARGET;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-pipeline/src/solana_metaplex_token_metadata_execution_orchestration.rs
|
||||
// version: 1
|
||||
// file: kb-pipeline/src/metadata_metaplex_token_metadata_execution_orchestration.rs
|
||||
// version: 2
|
||||
|
||||
//! Simulation-first Metaplex Token Metadata execution orchestration.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-pipeline/src/solana_metaplex_token_metadata_preflight.rs
|
||||
// version: 2
|
||||
// file: kb-pipeline/src/metadata_metaplex_token_metadata_preflight.rs
|
||||
// version: 3
|
||||
|
||||
//! Stateful Metaplex Token Metadata preflight contracts.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-pipeline/src/solana_metaplex_token_metadata_stateful.rs
|
||||
// version: 3
|
||||
// file: kb-pipeline/src/metadata_metaplex_token_metadata_stateful.rs
|
||||
// version: 4
|
||||
|
||||
//! Bounded Metaplex Token Metadata account reads and canonical state projections.
|
||||
|
||||
@@ -0,0 +1,831 @@
|
||||
// file: kb-pipeline/src/metadata_solana_program_execution_orchestration.rs
|
||||
// version: 2
|
||||
|
||||
//! 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: kb_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<kb_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<kb_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: kb_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: kb_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,
|
||||
) -> kb_core::Result<crate::SolanaProgramMetadataExecutionReadinessReport> {
|
||||
if request.plan.operation_code != request.preflight.operation_code {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"solana_program_metadata_execution_operation_mismatch",
|
||||
"Program Metadata plan and preflight operation codes must match",
|
||||
));
|
||||
}
|
||||
if request.preflight.safety.decision == kb_lib::ExSafetyDecision::Deny {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"solana_program_metadata_execution_safety_denied",
|
||||
"Program Metadata preflight safety decision denied execution",
|
||||
));
|
||||
}
|
||||
if request.preflight.safety.decision == kb_lib::ExSafetyDecision::RequireConfirmation
|
||||
&& !request.operator_confirmed
|
||||
{
|
||||
return std::result::Result::Err(kb_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(kb_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(kb_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, kb_lib::MdPubkey>::new();
|
||||
for signer in &request.plan.required_signers {
|
||||
if !resolved.contains(signer.pubkey.0.as_str()) {
|
||||
return std::result::Result::Err(kb_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(kb_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(kb_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,
|
||||
) -> kb_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,
|
||||
) -> kb_core::Result<()> {
|
||||
if snapshots.len() > crate::MAX_SOLANA_PROGRAM_METADATA_PREFLIGHT_ACCOUNTS {
|
||||
return std::result::Result::Err(kb_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(kb_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(kb_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: &kb_lib::ExMetadataSpmOperation) -> &kb_lib::MdPubkey {
|
||||
return match operation {
|
||||
kb_lib::ExMetadataSpmOperation::Write { buffer, .. }
|
||||
| kb_lib::ExMetadataSpmOperation::Allocate { buffer, .. } => buffer,
|
||||
kb_lib::ExMetadataSpmOperation::Initialize { metadata, .. }
|
||||
| kb_lib::ExMetadataSpmOperation::SetData { metadata, .. }
|
||||
| kb_lib::ExMetadataSpmOperation::SetImmutable { metadata, .. } => metadata,
|
||||
kb_lib::ExMetadataSpmOperation::SetAuthority { account, .. }
|
||||
| kb_lib::ExMetadataSpmOperation::Trim { account, .. }
|
||||
| kb_lib::ExMetadataSpmOperation::Close { account, .. }
|
||||
| kb_lib::ExMetadataSpmOperation::Extend { account, .. } => account,
|
||||
};
|
||||
}
|
||||
|
||||
fn find_snapshot<'a>(
|
||||
snapshots: &'a [crate::SolanaProgramMetadataStatefulReadResult],
|
||||
account: &kb_lib::MdPubkey,
|
||||
) -> std::option::Option<&'a crate::SolanaProgramMetadataStatefulReadResult> {
|
||||
return snapshots.iter().find(|value| return value.account == *account);
|
||||
}
|
||||
|
||||
fn evaluate_operation_postcondition(
|
||||
operation: &kb_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 {
|
||||
kb_lib::ExMetadataSpmOperation::Write { offset, source, .. } => {
|
||||
write_postcondition(*offset, source, after, before_all)
|
||||
},
|
||||
kb_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,
|
||||
),
|
||||
kb_lib::ExMetadataSpmOperation::SetAuthority { new_authority, .. } => {
|
||||
authority_postcondition(new_authority.as_ref(), after)
|
||||
},
|
||||
kb_lib::ExMetadataSpmOperation::SetData {
|
||||
encoding, compression, format, source, ..
|
||||
} => set_data_postcondition(
|
||||
*encoding,
|
||||
*compression,
|
||||
*format,
|
||||
source,
|
||||
before,
|
||||
after,
|
||||
before_all,
|
||||
),
|
||||
kb_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,
|
||||
},
|
||||
kb_lib::ExMetadataSpmOperation::Trim { .. } => trim_postcondition(before, after),
|
||||
kb_lib::ExMetadataSpmOperation::Close { .. } => match after {
|
||||
std::option::Option::Some(value) => {
|
||||
matches!(&value.state, crate::SolanaProgramMetadataObservedAccountState::Missing)
|
||||
},
|
||||
std::option::Option::None => false,
|
||||
},
|
||||
kb_lib::ExMetadataSpmOperation::Allocate { authority, seed, canonical, .. } => {
|
||||
allocate_postcondition(authority, seed.as_deref(), *canonical, after)
|
||||
},
|
||||
kb_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: &kb_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 {
|
||||
kb_lib::ExMetadataSpmWriteSource::Inline { data } => data.as_slice(),
|
||||
kb_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: &kb_lib::MdPubkey,
|
||||
canonical: bool,
|
||||
seed: &[u8],
|
||||
encoding: kb_lib::ExMetadataSpmEncoding,
|
||||
compression: kb_lib::ExMetadataSpmCompression,
|
||||
format: kb_lib::ExMetadataSpmFormat,
|
||||
data_source: kb_lib::ExMetadataSpmDataSource,
|
||||
data: std::option::Option<&kb_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<&kb_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: kb_lib::ExMetadataSpmEncoding,
|
||||
compression: kb_lib::ExMetadataSpmCompression,
|
||||
format: kb_lib::ExMetadataSpmFormat,
|
||||
source: &kb_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 {
|
||||
kb_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,
|
||||
}
|
||||
},
|
||||
kb_lib::ExMetadataSpmSetDataSource::Inline { data } => data_matches(data, &metadata.data),
|
||||
kb_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 {
|
||||
kb_lib::DcMetadataSpmData::Direct(value) => value.as_slice() == bytes,
|
||||
kb_lib::DcMetadataSpmData::Url(value) => value.as_bytes() == bytes,
|
||||
kb_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: &kb_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: &kb_lib::ExMetadataSpmDataInput,
|
||||
observed: &kb_lib::DcMetadataSpmData,
|
||||
) -> bool {
|
||||
return match (input, observed) {
|
||||
(
|
||||
kb_lib::ExMetadataSpmDataInput::Direct { bytes },
|
||||
kb_lib::DcMetadataSpmData::Direct(value),
|
||||
) => bytes == value,
|
||||
(kb_lib::ExMetadataSpmDataInput::Url { url }, kb_lib::DcMetadataSpmData::Url(value)) => {
|
||||
url == value
|
||||
},
|
||||
(
|
||||
kb_lib::ExMetadataSpmDataInput::External { address, offset, length },
|
||||
kb_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) -> kb_lib::MdPubkey {
|
||||
return kb_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 = kb_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: kb_lib::EX_METADATA_SPM_WRITE_OPERATION.to_string(),
|
||||
fee_payer: signer.clone(),
|
||||
instructions: vec![kb_lib::ExApiPlannedInstruction {
|
||||
program_id: kb_lib::MdProgramId(
|
||||
kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID.to_string(),
|
||||
),
|
||||
operation_code: kb_lib::EX_METADATA_SPM_WRITE_OPERATION.to_string(),
|
||||
accounts: vec![],
|
||||
data: vec![0],
|
||||
}],
|
||||
required_signers: vec![kb_lib::ExApiRequiredSigner {
|
||||
pubkey: signer.clone(),
|
||||
role: "authority".to_string(),
|
||||
}],
|
||||
policy: kb_lib::ExApiExecutionPolicy {
|
||||
dry_run: false,
|
||||
..kb_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: kb_lib::EX_METADATA_SPM_WRITE_OPERATION.to_string(),
|
||||
target_account: pubkey(2),
|
||||
context_slot: 99,
|
||||
inspected_accounts: vec![],
|
||||
safety: kb_lib::ExSafetyEvaluation {
|
||||
decision: kb_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"],
|
||||
kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID
|
||||
);
|
||||
assert_eq!(
|
||||
matrix["operations"].as_array().map(std::vec::Vec::len),
|
||||
std::option::Option::Some(kb_lib::EX_METADATA_SPM_SUPPORTED_OPERATION_CODES.len())
|
||||
);
|
||||
assert_eq!(matrix["pipelineCrate"], "kb-pipeline");
|
||||
assert_eq!(matrix["demoScenarioCrate"], "kb-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![
|
||||
kb_lib::ExMetadataSpmOperation::Write {
|
||||
buffer: account.clone(),
|
||||
authority: authority.clone(),
|
||||
offset: 0,
|
||||
source: kb_lib::ExMetadataSpmWriteSource::Inline { data: vec![1] },
|
||||
},
|
||||
kb_lib::ExMetadataSpmOperation::Initialize {
|
||||
metadata: account.clone(),
|
||||
authority: authority.clone(),
|
||||
program: program.clone(),
|
||||
program_data: std::option::Option::None,
|
||||
canonical: false,
|
||||
seed: vec![0_u8; kb_lib::DC_METADATA_SPM_SEED_BYTES],
|
||||
encoding: kb_lib::ExMetadataSpmEncoding::Utf8,
|
||||
compression: kb_lib::ExMetadataSpmCompression::None,
|
||||
format: kb_lib::ExMetadataSpmFormat::Json,
|
||||
data_source: kb_lib::ExMetadataSpmDataSource::Direct,
|
||||
data: std::option::Option::Some(kb_lib::ExMetadataSpmDataInput::Direct {
|
||||
bytes: vec![1],
|
||||
}),
|
||||
allocate_account: true,
|
||||
},
|
||||
kb_lib::ExMetadataSpmOperation::SetAuthority {
|
||||
account: account.clone(),
|
||||
authority: authority.clone(),
|
||||
new_authority: std::option::Option::Some(pubkey(8)),
|
||||
program_context: std::option::Option::None,
|
||||
},
|
||||
kb_lib::ExMetadataSpmOperation::SetData {
|
||||
metadata: account.clone(),
|
||||
authority: authority.clone(),
|
||||
encoding: kb_lib::ExMetadataSpmEncoding::Utf8,
|
||||
compression: kb_lib::ExMetadataSpmCompression::None,
|
||||
format: kb_lib::ExMetadataSpmFormat::Json,
|
||||
source: kb_lib::ExMetadataSpmSetDataSource::PreserveExisting,
|
||||
program_context: std::option::Option::None,
|
||||
},
|
||||
kb_lib::ExMetadataSpmOperation::SetImmutable {
|
||||
metadata: account.clone(),
|
||||
authority: authority.clone(),
|
||||
program_context: std::option::Option::None,
|
||||
},
|
||||
kb_lib::ExMetadataSpmOperation::Trim {
|
||||
account: account.clone(),
|
||||
authority: authority.clone(),
|
||||
destination: destination.clone(),
|
||||
program_context: std::option::Option::None,
|
||||
},
|
||||
kb_lib::ExMetadataSpmOperation::Close {
|
||||
account: account.clone(),
|
||||
authority: authority.clone(),
|
||||
destination,
|
||||
program_context: std::option::Option::None,
|
||||
},
|
||||
kb_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,
|
||||
},
|
||||
kb_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 = kb_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
|
||||
);
|
||||
}
|
||||
}
|
||||
761
kb-pipeline/src/metadata_solana_program_preflight.rs
Normal file
761
kb-pipeline/src/metadata_solana_program_preflight.rs
Normal file
@@ -0,0 +1,761 @@
|
||||
// file: kb-pipeline/src/metadata_solana_program_preflight.rs
|
||||
// version: 2
|
||||
|
||||
//! 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: kb_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: kb_lib::ExMetadataSpmExecutionIntent,
|
||||
/// Exact prepared plan produced by `kb-lib`.
|
||||
pub plan: kb_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: kb_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<kb_lib::MdPubkey>,
|
||||
/// Safety decision applied before simulation.
|
||||
pub safety: kb_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,
|
||||
) -> kb_core::Result<crate::SolanaProgramMetadataPreflightReport> {
|
||||
if request.before.len() > crate::MAX_SOLANA_PROGRAM_METADATA_PREFLIGHT_ACCOUNTS {
|
||||
return std::result::Result::Err(kb_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(kb_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(kb_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(kb_core::Error::new(
|
||||
"metadata_solana_program_preflight_instruction_missing",
|
||||
"Program Metadata plan instruction is missing",
|
||||
));
|
||||
},
|
||||
};
|
||||
if instruction.program_id.0 != kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID
|
||||
|| instruction.operation_code != operation_code
|
||||
|| !kb_lib::EX_METADATA_SPM_SUPPORTED_OPERATION_CODES.contains(&operation_code)
|
||||
{
|
||||
return std::result::Result::Err(kb_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(kb_core::Error::new(
|
||||
"metadata_solana_program_preflight_destructive_approval_required",
|
||||
"destructive Program Metadata execution requires explicit approval",
|
||||
));
|
||||
}
|
||||
let safety = match kb_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 == kb_lib::ExSafetyDecision::Deny {
|
||||
return std::result::Result::Err(kb_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(kb_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(kb_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(kb_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: &kb_lib::ExMetadataSpmOperation) -> &kb_lib::MdPubkey {
|
||||
return match operation {
|
||||
kb_lib::ExMetadataSpmOperation::Write { buffer, .. }
|
||||
| kb_lib::ExMetadataSpmOperation::Allocate { buffer, .. } => buffer,
|
||||
kb_lib::ExMetadataSpmOperation::Initialize { metadata, .. }
|
||||
| kb_lib::ExMetadataSpmOperation::SetData { metadata, .. }
|
||||
| kb_lib::ExMetadataSpmOperation::SetImmutable { metadata, .. } => metadata,
|
||||
kb_lib::ExMetadataSpmOperation::SetAuthority { account, .. }
|
||||
| kb_lib::ExMetadataSpmOperation::Trim { account, .. }
|
||||
| kb_lib::ExMetadataSpmOperation::Close { account, .. }
|
||||
| kb_lib::ExMetadataSpmOperation::Extend { account, .. } => account,
|
||||
};
|
||||
}
|
||||
|
||||
fn validate_target_state(
|
||||
operation: &kb_lib::ExMetadataSpmOperation,
|
||||
target: &crate::SolanaProgramMetadataStatefulReadResult,
|
||||
) -> kb_core::Result<()> {
|
||||
let valid = match operation {
|
||||
kb_lib::ExMetadataSpmOperation::Write { .. } => {
|
||||
matches!(&target.state, crate::SolanaProgramMetadataObservedAccountState::Buffer(_))
|
||||
},
|
||||
kb_lib::ExMetadataSpmOperation::Initialize { data, .. } => {
|
||||
if data.is_some() {
|
||||
is_prefunded_uninitialized(target)
|
||||
} else {
|
||||
matches!(&target.state, crate::SolanaProgramMetadataObservedAccountState::Buffer(_))
|
||||
}
|
||||
},
|
||||
kb_lib::ExMetadataSpmOperation::SetData { .. }
|
||||
| kb_lib::ExMetadataSpmOperation::SetImmutable { .. } => {
|
||||
matches!(&target.state, crate::SolanaProgramMetadataObservedAccountState::Metadata(_))
|
||||
},
|
||||
kb_lib::ExMetadataSpmOperation::Allocate { .. } => is_prefunded_uninitialized(target),
|
||||
kb_lib::ExMetadataSpmOperation::SetAuthority { .. }
|
||||
| kb_lib::ExMetadataSpmOperation::Trim { .. }
|
||||
| kb_lib::ExMetadataSpmOperation::Close { .. }
|
||||
| kb_lib::ExMetadataSpmOperation::Extend { .. } => matches!(
|
||||
&target.state,
|
||||
crate::SolanaProgramMetadataObservedAccountState::Buffer(_)
|
||||
| crate::SolanaProgramMetadataObservedAccountState::Metadata(_)
|
||||
),
|
||||
};
|
||||
if !valid {
|
||||
return std::result::Result::Err(kb_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 == kb_program_ids::SYSTEM_PROGRAM_ID
|
||||
|| owner.0 == kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID)
|
||||
},
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
fn validate_source_state(
|
||||
operation: &kb_lib::ExMetadataSpmOperation,
|
||||
snapshots: &std::collections::BTreeMap<
|
||||
std::string::String,
|
||||
&crate::SolanaProgramMetadataStatefulReadResult,
|
||||
>,
|
||||
target: &crate::SolanaProgramMetadataStatefulReadResult,
|
||||
) -> kb_core::Result<()> {
|
||||
match operation {
|
||||
kb_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 {
|
||||
kb_lib::ExMetadataSpmWriteSource::Inline { data } => data.len(),
|
||||
kb_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(kb_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(kb_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(kb_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(kb_core::Error::new(
|
||||
"metadata_solana_program_preflight_write_out_of_bounds",
|
||||
"Write source exceeds the allocated Buffer capacity",
|
||||
));
|
||||
}
|
||||
},
|
||||
kb_lib::ExMetadataSpmOperation::SetData {
|
||||
source: kb_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(kb_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(kb_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: &kb_lib::ExMetadataSpmOperation,
|
||||
target: &crate::SolanaProgramMetadataStatefulReadResult,
|
||||
) -> kb_core::Result<bool> {
|
||||
let (authority, has_program_context) = match operation {
|
||||
kb_lib::ExMetadataSpmOperation::Write { authority: _, .. } => {
|
||||
return std::result::Result::Ok(false);
|
||||
},
|
||||
kb_lib::ExMetadataSpmOperation::Initialize { canonical, .. } => {
|
||||
return std::result::Result::Ok(*canonical);
|
||||
},
|
||||
kb_lib::ExMetadataSpmOperation::Allocate { seed, canonical, .. } => {
|
||||
return std::result::Result::Ok(seed.is_some() && *canonical);
|
||||
},
|
||||
kb_lib::ExMetadataSpmOperation::SetAuthority { authority, program_context, .. }
|
||||
| kb_lib::ExMetadataSpmOperation::SetData { authority, program_context, .. }
|
||||
| kb_lib::ExMetadataSpmOperation::SetImmutable { authority, program_context, .. }
|
||||
| kb_lib::ExMetadataSpmOperation::Trim { authority, program_context, .. }
|
||||
| kb_lib::ExMetadataSpmOperation::Close { authority, program_context, .. }
|
||||
| kb_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(kb_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: &kb_lib::ExMetadataSpmOperation,
|
||||
target: &crate::SolanaProgramMetadataStatefulReadResult,
|
||||
snapshots: &std::collections::BTreeMap<
|
||||
std::string::String,
|
||||
&crate::SolanaProgramMetadataStatefulReadResult,
|
||||
>,
|
||||
observations: &[crate::SolanaProgramMetadataRentObservation],
|
||||
) -> kb_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(kb_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(kb_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: &kb_lib::ExMetadataSpmOperation,
|
||||
target: &crate::SolanaProgramMetadataStatefulReadResult,
|
||||
snapshots: &std::collections::BTreeMap<
|
||||
std::string::String,
|
||||
&crate::SolanaProgramMetadataStatefulReadResult,
|
||||
>,
|
||||
) -> kb_core::Result<std::option::Option<u64>> {
|
||||
let header = match u64::try_from(kb_lib::DC_METADATA_SPM_METADATA_HEADER_BYTES) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"metadata_solana_program_preflight_header_size_overflow",
|
||||
error.to_string(),
|
||||
));
|
||||
},
|
||||
};
|
||||
return match operation {
|
||||
kb_lib::ExMetadataSpmOperation::Initialize {
|
||||
data: std::option::Option::Some(data),
|
||||
..
|
||||
} => add_metadata_header(header, metadata_input_wire_len(data)),
|
||||
kb_lib::ExMetadataSpmOperation::SetData {
|
||||
source: kb_lib::ExMetadataSpmSetDataSource::PreserveExisting,
|
||||
..
|
||||
} => std::result::Result::Ok(std::option::Option::None),
|
||||
kb_lib::ExMetadataSpmOperation::SetData {
|
||||
source: kb_lib::ExMetadataSpmSetDataSource::Inline { data },
|
||||
..
|
||||
} => add_metadata_header(header, metadata_input_wire_len(data)),
|
||||
kb_lib::ExMetadataSpmOperation::SetData {
|
||||
source: kb_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(kb_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(kb_core::Error::new(
|
||||
"metadata_solana_program_preflight_source_length_overflow",
|
||||
error.to_string(),
|
||||
));
|
||||
},
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
return std::result::Result::Err(kb_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));
|
||||
},
|
||||
kb_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)))
|
||||
},
|
||||
kb_lib::ExMetadataSpmOperation::Extend { length, .. } => {
|
||||
let current = match target.space {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err(kb_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(kb_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: &kb_lib::ExMetadataSpmDataInput) -> kb_core::Result<u64> {
|
||||
let length = match data {
|
||||
kb_lib::ExMetadataSpmDataInput::Direct { bytes } => bytes.len(),
|
||||
kb_lib::ExMetadataSpmDataInput::Url { url } => url.len(),
|
||||
kb_lib::ExMetadataSpmDataInput::External { .. } => {
|
||||
kb_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(kb_core::Error::new(
|
||||
"metadata_solana_program_preflight_data_length_overflow",
|
||||
error.to_string(),
|
||||
)),
|
||||
};
|
||||
}
|
||||
|
||||
fn add_metadata_header(
|
||||
header: u64,
|
||||
data_length: kb_core::Result<u64>,
|
||||
) -> kb_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(kb_core::Error::new(
|
||||
"metadata_solana_program_preflight_target_space_overflow",
|
||||
"Program Metadata target space overflowed u64",
|
||||
)),
|
||||
};
|
||||
}
|
||||
|
||||
fn safety_violation_codes(violations: &[kb_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) -> kb_lib::MdPubkey {
|
||||
return kb_lib::MdPubkey(solana_pubkey::Pubkey::new_from_array([value; 32]).to_string());
|
||||
}
|
||||
|
||||
fn policy(authority: &kb_lib::MdPubkey) -> kb_lib::ExApiExecutionPolicy {
|
||||
return kb_lib::ExApiExecutionPolicy {
|
||||
cost_limit: kb_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: kb_lib::ExApiPostExecutionValidationPolicy {
|
||||
canonical_insert_required: true,
|
||||
core_extraction_required: true,
|
||||
decode_replay_required: true,
|
||||
materialization_required: true,
|
||||
},
|
||||
..kb_lib::ExApiExecutionPolicy::default()
|
||||
};
|
||||
}
|
||||
|
||||
fn buffer_read(
|
||||
account: kb_lib::MdPubkey,
|
||||
authority: kb_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(kb_lib::MdProgramId(
|
||||
kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID.to_string(),
|
||||
)),
|
||||
space: std::option::Option::Some(100),
|
||||
state: crate::SolanaProgramMetadataObservedAccountState::Buffer(
|
||||
kb_lib::DcMetadataSpmBufferAccountSnapshot {
|
||||
account: account.0,
|
||||
program: std::option::Option::None,
|
||||
authority: std::option::Option::Some(authority.0),
|
||||
canonical: false,
|
||||
seed: kb_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 = kb_lib::ExMetadataSpmExecutionIntent {
|
||||
intent_id: "spm-preflight-write".to_string(),
|
||||
fee_payer: authority.clone(),
|
||||
policy: policy(&authority),
|
||||
allow_destructive_operation: true,
|
||||
operation: kb_lib::ExMetadataSpmOperation::Write {
|
||||
buffer: buffer.clone(),
|
||||
authority: authority.clone(),
|
||||
offset: 2,
|
||||
source: kb_lib::ExMetadataSpmWriteSource::Inline { data: vec![8, 9] },
|
||||
},
|
||||
};
|
||||
let plan = kb_lib::ExApiTypedInstructionExecutor::build_prepared_plan(
|
||||
&kb_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, kb_lib::EX_METADATA_SPM_WRITE_OPERATION);
|
||||
assert_eq!(value.safety.decision, kb_lib::ExSafetyDecision::Allow);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn allocate_requires_vacancy_and_explicit_rent_evidence() {
|
||||
let authority = pubkey(4);
|
||||
let buffer = authority.clone();
|
||||
let intent = kb_lib::ExMetadataSpmExecutionIntent {
|
||||
intent_id: "spm-preflight-allocate".to_string(),
|
||||
fee_payer: authority.clone(),
|
||||
policy: policy(&authority),
|
||||
allow_destructive_operation: false,
|
||||
operation: kb_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 = kb_lib::ExApiTypedInstructionExecutor::build_prepared_plan(
|
||||
&kb_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(kb_lib::MdProgramId(
|
||||
kb_program_ids::SYSTEM_PROGRAM_ID.to_string(),
|
||||
)),
|
||||
space: std::option::Option::Some(0),
|
||||
state: crate::SolanaProgramMetadataObservedAccountState::Uninitialized {
|
||||
owner: kb_lib::MdProgramId(kb_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(kb_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());
|
||||
}
|
||||
}
|
||||
425
kb-pipeline/src/metadata_solana_program_stateful.rs
Normal file
425
kb-pipeline/src/metadata_solana_program_stateful.rs
Normal file
@@ -0,0 +1,425 @@
|
||||
// file: kb-pipeline/src/metadata_solana_program_stateful.rs
|
||||
// version: 2
|
||||
|
||||
//! Bounded Solana Program Metadata account reads and authoritative state projections.
|
||||
|
||||
/// Maximum complete Solana Program Metadata account data accepted by one RPC read.
|
||||
pub const MAX_SOLANA_PROGRAM_METADATA_STATEFUL_ACCOUNT_BYTES: usize =
|
||||
kb_lib::DC_METADATA_SPM_MAX_ACCOUNT_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: kb_lib::MdProgramId,
|
||||
/// Current lamports retained by the account.
|
||||
lamports: u64,
|
||||
/// Current allocated account-data bytes.
|
||||
space: u64,
|
||||
},
|
||||
/// Decoded Program Metadata Buffer account.
|
||||
Buffer(kb_lib::DcMetadataSpmBufferAccountSnapshot),
|
||||
/// Decoded Program Metadata Metadata account.
|
||||
Metadata(kb_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: kb_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) -> kb_core::Result<()> {
|
||||
if self.query_role.trim().is_empty() {
|
||||
return std::result::Result::Err(kb_core::Error::config(
|
||||
"Solana Program Metadata stateful query_role must not be empty",
|
||||
));
|
||||
}
|
||||
let account_result = kb_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(kb_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: kb_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<kb_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<kb_lib::MtApiMaterializedOutput>,
|
||||
}
|
||||
|
||||
/// Reads one bounded Solana Program Metadata account.
|
||||
pub async fn read_solana_program_metadata_stateful_snapshot(
|
||||
pool: &kb_onchain_transport::HttpEndpointPool,
|
||||
request: &crate::SolanaProgramMetadataStatefulReadRequest,
|
||||
) -> kb_core::Result<crate::SolanaProgramMetadataStatefulReadResult> {
|
||||
if let std::result::Result::Err(error) = request.validate() {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let config = match kb_onchain_transport::GetAccountInfoConfig::new_with_data(
|
||||
kb_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: &kb_onchain_transport::AccountInfoResult,
|
||||
) -> kb_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(kb_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(kb_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(kb_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(kb_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 != kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"metadata_solana_program_stateful_owner_mismatch",
|
||||
"initialized Solana Program Metadata state must be owned by ProgM6",
|
||||
));
|
||||
}
|
||||
let snapshot = match kb_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(kb_core::Error::new(
|
||||
"metadata_solana_program_stateful_decode_failed",
|
||||
error.to_string(),
|
||||
));
|
||||
},
|
||||
};
|
||||
let materialized = match kb_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(kb_core::Error::new(
|
||||
"metadata_solana_program_stateful_materialization_failed",
|
||||
error,
|
||||
));
|
||||
},
|
||||
};
|
||||
let state = match snapshot {
|
||||
kb_lib::DcMetadataSpmAccountSnapshot::Buffer(value) => {
|
||||
crate::SolanaProgramMetadataObservedAccountState::Buffer(value)
|
||||
},
|
||||
kb_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(kb_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: kb_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 = kb_onchain_transport::AccountInfoResult {
|
||||
context: kb_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 = kb_onchain_transport::AccountInfoResult {
|
||||
context: kb_onchain_transport::RpcResponseContext {
|
||||
slot: 11,
|
||||
api_version: std::option::Option::None,
|
||||
},
|
||||
account: std::option::Option::Some(kb_onchain_transport::AccountInfoValue {
|
||||
lamports: 100,
|
||||
owner: kb_lib::MdProgramId(kb_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; kb_lib::DC_METADATA_SPM_BUFFER_HEADER_BYTES];
|
||||
data[0] = kb_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 = kb_onchain_transport::AccountInfoResult {
|
||||
context: kb_onchain_transport::RpcResponseContext {
|
||||
slot: 12,
|
||||
api_version: std::option::Option::None,
|
||||
},
|
||||
account: std::option::Option::Some(kb_onchain_transport::AccountInfoValue {
|
||||
lamports: 1_000,
|
||||
owner: kb_lib::MdProgramId(
|
||||
kb_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"))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-pipeline/src/solana_ata_stateful.rs
|
||||
// version: 3
|
||||
// file: kb-pipeline/src/spl_ata_stateful.rs
|
||||
// version: 4
|
||||
|
||||
//! Stateful Localnet and Devnet readiness checks for ATA operations.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-pipeline/src/solana_elgamal_registry_stateful.rs
|
||||
// version: 4
|
||||
// file: kb-pipeline/src/spl_elgamal_registry_stateful.rs
|
||||
// version: 5
|
||||
|
||||
//! Contextual SPL ElGamal registry account-state validation and materialization routing.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-pipeline/src/solana_token_2022_correlation.rs
|
||||
// version: 2
|
||||
// file: kb-pipeline/src/spl_token_2022_correlation.rs
|
||||
// version: 3
|
||||
|
||||
//! Deterministic Token-2022 instruction-to-state correlation.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-pipeline/src/solana_token_2022_crypto_preflight.rs
|
||||
// version: 3
|
||||
// file: kb-pipeline/src/spl_token_2022_crypto_preflight.rs
|
||||
// version: 4
|
||||
|
||||
//! Bounded cryptographic preflight for Token-2022 proof context-state accounts.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-pipeline/src/solana_token_2022_execution_orchestration.rs
|
||||
// version: 3
|
||||
// file: kb-pipeline/src/spl_token_2022_execution_orchestration.rs
|
||||
// version: 4
|
||||
|
||||
//! Final Token-2022 execution-readiness and stateful postcondition contracts.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-pipeline/src/solana_token_2022_preflight.rs
|
||||
// version: 3
|
||||
// file: kb-pipeline/src/spl_token_2022_preflight.rs
|
||||
// version: 4
|
||||
|
||||
//! Bounded Token-2022 stateful preflight orchestration.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-pipeline/src/solana_token_2022_proof_orchestration.rs
|
||||
// version: 4
|
||||
// file: kb-pipeline/src/spl_token_2022_proof_orchestration.rs
|
||||
// version: 5
|
||||
|
||||
//! Deterministic orchestration contract for mixed Token-2022 proof locations.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-pipeline/src/solana_token_2022_stateful.rs
|
||||
// version: 7
|
||||
// file: kb-pipeline/src/spl_token_2022_stateful.rs
|
||||
// version: 8
|
||||
|
||||
//! Contextual Token-2022 account-state validation and materialization routing.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-pipeline/src/solana_token_stateful.rs
|
||||
// version: 1
|
||||
// file: kb-pipeline/src/spl_token_stateful.rs
|
||||
// version: 2
|
||||
|
||||
//! Stateful Localnet and Devnet readiness checks for classic SPL Token operations.
|
||||
|
||||
Reference in New Issue
Block a user