0.4.7-pre.008
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 14
|
# version: 15
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
@@ -18,7 +18,7 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.4.7-pre.7"
|
version = "0.4.7-pre.8"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-bot3"
|
repository = "https://git.sasedev.com/Sasedev/khadhroony-bot3"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/plans/V0_4_7_METAPLEX_TOKEN_METADATA_COMPLETION_PLAN.md -->
|
<!-- file: docs/plans/V0_4_7_METAPLEX_TOKEN_METADATA_COMPLETION_PLAN.md -->
|
||||||
<!-- version: 8 -->
|
<!-- version: 9 -->
|
||||||
|
|
||||||
# Plan `0.4.7` — achèvement de Metaplex Token Metadata
|
# Plan `0.4.7` — achèvement de Metaplex Token Metadata
|
||||||
|
|
||||||
@@ -583,3 +583,12 @@ Le développement fonctionnel peut commencer après acceptation des recommandati
|
|||||||
- orchestration simulation-first, résolution des signers et postconditions explicites ;
|
- orchestration simulation-first, résolution des signers et postconditions explicites ;
|
||||||
- aucune dépendance vers `kb-pipeline-demo-scenarios` ;
|
- aucune dépendance vers `kb-pipeline-demo-scenarios` ;
|
||||||
- les fixtures et campagnes réseau restent réservées à `pre.008`.
|
- les fixtures et campagnes réseau restent réservées à `pre.008`.
|
||||||
|
|
||||||
|
|
||||||
|
## État de `0.4.7-pre.008`
|
||||||
|
|
||||||
|
- inventaire synthétique fermé pour NFT, SFT, fungible, collection et pNFT ;
|
||||||
|
- matrice automatisée de validation Metaplex avec preuves bornées ;
|
||||||
|
- aucune soumission automatique ;
|
||||||
|
- CLI inchangé faute de besoin démontré pour une fixture Metaplex dédiée ;
|
||||||
|
- scénarios UI du desktop conservés pour `pre.009`.
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<!-- file: docs/validation/V0_4_7_PRE_008_METAPLEX_SYNTHETIC_AND_NETWORK_VALIDATION.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Validation `0.4.7-pre.008` — scénarios Metaplex
|
||||||
|
|
||||||
|
## Couverture
|
||||||
|
|
||||||
|
- NFT ;
|
||||||
|
- SFT ;
|
||||||
|
- token fongible ;
|
||||||
|
- collection ;
|
||||||
|
- programmable NFT.
|
||||||
|
|
||||||
|
## Contrats
|
||||||
|
|
||||||
|
Les scénarios synthétiques sont déterministes et simulation-safe. La matrice réseau distingue `not_run`, `synthetic_validated`, `simulated`, `submitted`, `confirmed`, `unavailable` et `failed`. Aucun statut réseau n’est déclaré sans preuve observée.
|
||||||
|
|
||||||
|
## CLI
|
||||||
|
|
||||||
|
Aucune nouvelle commande n’est ajoutée : le CLI reste destiné à la préparation Token-2022 tant qu’une fixture Metaplex dédiée n’est pas nécessaire.
|
||||||
|
|
||||||
|
## Validation locale à exécuter
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --all-targets
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py
|
||||||
|
cargo test -p kb-pipeline-demo-scenarios
|
||||||
|
```
|
||||||
@@ -1,8 +1,14 @@
|
|||||||
<!-- file: kb-pipeline-demo-scenarios/CHANGELOG.md -->
|
<!-- file: kb-pipeline-demo-scenarios/CHANGELOG.md -->
|
||||||
<!-- version: 8 -->
|
<!-- version: 9 -->
|
||||||
|
|
||||||
# CHANGELOG — kb-pipeline-demo-scenarios
|
# CHANGELOG — kb-pipeline-demo-scenarios
|
||||||
|
|
||||||
|
## 0.4.7-pre.008
|
||||||
|
|
||||||
|
- ajout de l’inventaire synthétique Metaplex NFT, SFT, fungible, collection et pNFT ;
|
||||||
|
- ajout de la matrice de validation automatisée Devnet/Testnet et de ses preuves bornées ;
|
||||||
|
- conservation du CLI sans nouvelle commande, aucune fixture Metaplex réseau dédiée n’étant encore nécessaire.
|
||||||
|
|
||||||
## 0.4.6
|
## 0.4.6
|
||||||
|
|
||||||
- alignement de la crate sur la version fonctionnelle bot3 `0.4.6` ;
|
- alignement de la crate sur la version fonctionnelle bot3 `0.4.6` ;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: kb-pipeline-demo-scenarios/README.md -->
|
<!-- file: kb-pipeline-demo-scenarios/README.md -->
|
||||||
<!-- version: 1 -->
|
<!-- version: 2 -->
|
||||||
|
|
||||||
# kb-pipeline-demo-scenarios
|
# kb-pipeline-demo-scenarios
|
||||||
|
|
||||||
@@ -17,6 +17,7 @@ La crate est mixte :
|
|||||||
- simulation ou soumission explicitement autorisée ;
|
- simulation ou soumission explicitement autorisée ;
|
||||||
- préparation de fixtures publiques ;
|
- préparation de fixtures publiques ;
|
||||||
- validation des matrices et rapports Token-2022 ;
|
- validation des matrices et rapports Token-2022 ;
|
||||||
|
- scénarios synthétiques et matrice de validation Metaplex Token Metadata ;
|
||||||
- progression observable et annulation coopérative.
|
- progression observable et annulation coopérative.
|
||||||
|
|
||||||
Elle ne remplace ni le pipeline générique ni l’application desktop.
|
Elle ne remplace ni le pipeline générique ni l’application desktop.
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<!-- file: kb-pipeline-demo-scenarios/TODO.md -->
|
<!-- file: kb-pipeline-demo-scenarios/TODO.md -->
|
||||||
<!-- version: 2 -->
|
<!-- version: 3 -->
|
||||||
|
|
||||||
# TODO — kb-pipeline-demo-scenarios
|
# TODO — kb-pipeline-demo-scenarios
|
||||||
|
|
||||||
## Entre `0.4.6` et `0.4.7`
|
## `0.4.7`
|
||||||
|
|
||||||
- [ ] Metaplex Token Metadata - ajouter les scénarios nécessaires à la clôture fonctionnelle de `0.4.7`.
|
- [ ] Tests réseau Metaplex - exécuter les validations Devnet/Testnet lorsque les fixtures, comptes et fonds nécessaires sont disponibles.
|
||||||
- [ ] Tests Devnet - ajouter les validations de bout en bout Metaplex sans dépendre du desktop.
|
- [ ] Preuves Metaplex - renseigner les signatures, simulations et postconditions observées sans déclarer de validation réseau non exécutée.
|
||||||
|
|
||||||
## Série `0.5.x`
|
## Série `0.5.x`
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: kb-pipeline-demo-scenarios/USAGE.md -->
|
<!-- file: kb-pipeline-demo-scenarios/USAGE.md -->
|
||||||
<!-- version: 2 -->
|
<!-- version: 3 -->
|
||||||
|
|
||||||
# Utilisation de kb-pipeline-demo-scenarios
|
# Utilisation de kb-pipeline-demo-scenarios
|
||||||
|
|
||||||
@@ -179,3 +179,33 @@ Les tests Devnet restent opt-in et peuvent produire des transactions réelles lo
|
|||||||
- cette crate est destinée aux démonstrations, validations et outils opérateur, pas au moteur de production autonome ;
|
- cette crate est destinée aux démonstrations, validations et outils opérateur, pas au moteur de production autonome ;
|
||||||
- les scénarios réseau exigent un endpoint, un wallet et des fonds compatibles ;
|
- les scénarios réseau exigent un endpoint, un wallet et des fonds compatibles ;
|
||||||
- la bibliothèque ne fournit pas d’interface graphique.
|
- la bibliothèque ne fournit pas d’interface graphique.
|
||||||
|
|
||||||
|
|
||||||
|
## Charger les scénarios synthétiques Metaplex
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn metaplex_scenario_ids() -> Vec<String> {
|
||||||
|
kb_pipeline_demo_scenarios::metaplex_token_metadata_synthetic_scenarios()
|
||||||
|
.iter()
|
||||||
|
.map(|scenario| scenario.id.clone())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
L’inventaire couvre NFT, SFT, token fongible, collection et pNFT. Ces scénarios sont déterministes et ne soumettent aucune transaction.
|
||||||
|
|
||||||
|
## Charger la matrice de validation Metaplex
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn load_metaplex_matrix(
|
||||||
|
) -> kb_core::Result<kb_pipeline_demo_scenarios::MetaplexTokenMetadataValidationMatrix> {
|
||||||
|
let result = kb_pipeline_demo_scenarios::load_metaplex_token_metadata_validation_matrix();
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(matrix) => Ok(matrix),
|
||||||
|
Err(error) => Err(error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Une validation réseau ne peut passer à `confirmed` que lorsque toutes les preuves déclarées sont présentes. Les statuts `not_run` et `unavailable` n’acceptent aucune preuve observée.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: kb-pipeline-demo-scenarios/src/lib.rs
|
// file: kb-pipeline-demo-scenarios/src/lib.rs
|
||||||
// version: 5
|
// version: 6
|
||||||
|
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
#![deny(unreachable_pub)]
|
#![deny(unreachable_pub)]
|
||||||
@@ -12,6 +12,8 @@ mod environment;
|
|||||||
mod solana_ata_execution;
|
mod solana_ata_execution;
|
||||||
mod solana_execution;
|
mod solana_execution;
|
||||||
mod solana_memo_execution;
|
mod solana_memo_execution;
|
||||||
|
mod solana_metaplex_token_metadata_scenarios;
|
||||||
|
mod solana_metaplex_token_metadata_validation;
|
||||||
mod solana_token_2022_devnet_execution;
|
mod solana_token_2022_devnet_execution;
|
||||||
mod solana_token_2022_devnet_scenarios;
|
mod solana_token_2022_devnet_scenarios;
|
||||||
mod solana_token_2022_validation;
|
mod solana_token_2022_validation;
|
||||||
@@ -65,6 +67,28 @@ pub use self::solana_memo_execution::DevnetMemoExecutionRequest;
|
|||||||
pub use self::solana_memo_execution::DevnetMemoExecutionSummary;
|
pub use self::solana_memo_execution::DevnetMemoExecutionSummary;
|
||||||
/// Executes one SPL Memo v4 Devnet simulation or explicitly authorized submission.
|
/// Executes one SPL Memo v4 Devnet simulation or explicitly authorized submission.
|
||||||
pub use self::solana_memo_execution::execute_devnet_memo;
|
pub use self::solana_memo_execution::execute_devnet_memo;
|
||||||
|
/// Stable asset family covered by one Metaplex scenario.
|
||||||
|
pub use self::solana_metaplex_token_metadata_scenarios::MetaplexTokenMetadataAssetFamily;
|
||||||
|
/// One stable Metaplex scenario reusable by automated tests and demo adapters.
|
||||||
|
pub use self::solana_metaplex_token_metadata_scenarios::MetaplexTokenMetadataScenario;
|
||||||
|
/// Execution mode accepted by one Metaplex scenario.
|
||||||
|
pub use self::solana_metaplex_token_metadata_scenarios::MetaplexTokenMetadataScenarioMode;
|
||||||
|
/// Returns the complete ordered synthetic Metaplex scenario inventory.
|
||||||
|
pub use self::solana_metaplex_token_metadata_scenarios::metaplex_token_metadata_synthetic_scenarios;
|
||||||
|
/// Maximum evidence entries accepted by one Metaplex validation scenario.
|
||||||
|
pub use self::solana_metaplex_token_metadata_validation::MAX_METAPLEX_TOKEN_METADATA_VALIDATION_EVIDENCE;
|
||||||
|
/// One bounded Metaplex validation evidence entry.
|
||||||
|
pub use self::solana_metaplex_token_metadata_validation::MetaplexTokenMetadataValidationEvidence;
|
||||||
|
/// Canonical Metaplex validation matrix.
|
||||||
|
pub use self::solana_metaplex_token_metadata_validation::MetaplexTokenMetadataValidationMatrix;
|
||||||
|
/// One scenario declared by the canonical Metaplex validation matrix.
|
||||||
|
pub use self::solana_metaplex_token_metadata_validation::MetaplexTokenMetadataValidationMatrixScenario;
|
||||||
|
/// Exact validation status for one Metaplex scenario.
|
||||||
|
pub use self::solana_metaplex_token_metadata_validation::MetaplexTokenMetadataValidationStatus;
|
||||||
|
/// Loads and validates the canonical Metaplex validation matrix.
|
||||||
|
pub use self::solana_metaplex_token_metadata_validation::load_metaplex_token_metadata_validation_matrix;
|
||||||
|
/// Validates the canonical Metaplex validation matrix.
|
||||||
|
pub use self::solana_metaplex_token_metadata_validation::validate_metaplex_token_metadata_validation_matrix;
|
||||||
/// Complete request for one Devnet Token-2022 execution.
|
/// Complete request for one Devnet Token-2022 execution.
|
||||||
pub use self::solana_token_2022_devnet_execution::DevnetSplToken2022ExecutionRequest;
|
pub use self::solana_token_2022_devnet_execution::DevnetSplToken2022ExecutionRequest;
|
||||||
/// Complete result of one Devnet Token-2022 execution.
|
/// Complete result of one Devnet Token-2022 execution.
|
||||||
|
|||||||
@@ -0,0 +1,170 @@
|
|||||||
|
// file: kb-pipeline-demo-scenarios/src/solana_metaplex_token_metadata_scenarios.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
//! Deterministic Metaplex Token Metadata scenario inventory for tests and Devnet campaigns.
|
||||||
|
|
||||||
|
/// Stable asset family covered by one Metaplex scenario.
|
||||||
|
#[derive(
|
||||||
|
Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize,
|
||||||
|
)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum MetaplexTokenMetadataAssetFamily {
|
||||||
|
/// Non-fungible token.
|
||||||
|
Nft,
|
||||||
|
/// Semi-fungible token.
|
||||||
|
Sft,
|
||||||
|
/// Fungible token with Metaplex metadata.
|
||||||
|
Fungible,
|
||||||
|
/// Collection metadata and verification flow.
|
||||||
|
Collection,
|
||||||
|
/// Programmable non-fungible token.
|
||||||
|
ProgrammableNft,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execution mode accepted by one scenario.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum MetaplexTokenMetadataScenarioMode {
|
||||||
|
/// Purely deterministic scenario using synthetic accounts and plans.
|
||||||
|
Synthetic,
|
||||||
|
/// Devnet or Testnet simulation without submission.
|
||||||
|
NetworkSimulation,
|
||||||
|
/// Explicitly authorized Devnet or Testnet submission.
|
||||||
|
NetworkSubmission,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One stable Metaplex scenario reusable by automated tests and demo adapters.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct MetaplexTokenMetadataScenario {
|
||||||
|
/// Stable scenario identifier.
|
||||||
|
pub id: std::string::String,
|
||||||
|
/// Asset family exercised by the scenario.
|
||||||
|
pub asset_family: crate::MetaplexTokenMetadataAssetFamily,
|
||||||
|
/// Execution mode.
|
||||||
|
pub mode: crate::MetaplexTokenMetadataScenarioMode,
|
||||||
|
/// Stable operation codes executed in order.
|
||||||
|
pub operation_codes: std::vec::Vec<std::string::String>,
|
||||||
|
/// Whether the scenario requires a collection account.
|
||||||
|
pub requires_collection: bool,
|
||||||
|
/// Whether the scenario requires programmable authorization rules.
|
||||||
|
pub requires_programmable_rules: bool,
|
||||||
|
/// Whether a confirmed postcondition is mandatory.
|
||||||
|
pub requires_postcondition: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the complete ordered synthetic Metaplex scenario inventory.
|
||||||
|
pub fn metaplex_token_metadata_synthetic_scenarios()
|
||||||
|
-> std::vec::Vec<crate::MetaplexTokenMetadataScenario> {
|
||||||
|
return vec![
|
||||||
|
crate::MetaplexTokenMetadataScenario {
|
||||||
|
id: "metaplex_nft_create_update".to_string(),
|
||||||
|
asset_family: crate::MetaplexTokenMetadataAssetFamily::Nft,
|
||||||
|
mode: crate::MetaplexTokenMetadataScenarioMode::Synthetic,
|
||||||
|
operation_codes: vec![
|
||||||
|
kb_lib::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION.to_string(),
|
||||||
|
kb_lib::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION.to_string(),
|
||||||
|
],
|
||||||
|
requires_collection: false,
|
||||||
|
requires_programmable_rules: false,
|
||||||
|
requires_postcondition: true,
|
||||||
|
},
|
||||||
|
crate::MetaplexTokenMetadataScenario {
|
||||||
|
id: "metaplex_sft_create_mint".to_string(),
|
||||||
|
asset_family: crate::MetaplexTokenMetadataAssetFamily::Sft,
|
||||||
|
mode: crate::MetaplexTokenMetadataScenarioMode::Synthetic,
|
||||||
|
operation_codes: vec![
|
||||||
|
kb_lib::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION.to_string(),
|
||||||
|
kb_lib::EX_METAPLEX_TOKEN_METADATA_MINT_OPERATION.to_string(),
|
||||||
|
],
|
||||||
|
requires_collection: false,
|
||||||
|
requires_programmable_rules: false,
|
||||||
|
requires_postcondition: true,
|
||||||
|
},
|
||||||
|
crate::MetaplexTokenMetadataScenario {
|
||||||
|
id: "metaplex_fungible_create_update".to_string(),
|
||||||
|
asset_family: crate::MetaplexTokenMetadataAssetFamily::Fungible,
|
||||||
|
mode: crate::MetaplexTokenMetadataScenarioMode::Synthetic,
|
||||||
|
operation_codes: vec![
|
||||||
|
kb_lib::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION.to_string(),
|
||||||
|
kb_lib::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION.to_string(),
|
||||||
|
],
|
||||||
|
requires_collection: false,
|
||||||
|
requires_programmable_rules: false,
|
||||||
|
requires_postcondition: true,
|
||||||
|
},
|
||||||
|
crate::MetaplexTokenMetadataScenario {
|
||||||
|
id: "metaplex_collection_verify_unverify".to_string(),
|
||||||
|
asset_family: crate::MetaplexTokenMetadataAssetFamily::Collection,
|
||||||
|
mode: crate::MetaplexTokenMetadataScenarioMode::Synthetic,
|
||||||
|
operation_codes: vec![
|
||||||
|
kb_lib::EX_METAPLEX_TOKEN_METADATA_VERIFY_OPERATION.to_string(),
|
||||||
|
kb_lib::EX_METAPLEX_TOKEN_METADATA_UNVERIFY_OPERATION.to_string(),
|
||||||
|
],
|
||||||
|
requires_collection: true,
|
||||||
|
requires_programmable_rules: false,
|
||||||
|
requires_postcondition: true,
|
||||||
|
},
|
||||||
|
crate::MetaplexTokenMetadataScenario {
|
||||||
|
id: "metaplex_pnft_delegate_transfer_revoke".to_string(),
|
||||||
|
asset_family: crate::MetaplexTokenMetadataAssetFamily::ProgrammableNft,
|
||||||
|
mode: crate::MetaplexTokenMetadataScenarioMode::Synthetic,
|
||||||
|
operation_codes: vec![
|
||||||
|
kb_lib::EX_METAPLEX_TOKEN_METADATA_DELEGATE_OPERATION.to_string(),
|
||||||
|
kb_lib::EX_METAPLEX_TOKEN_METADATA_TRANSFER_OPERATION.to_string(),
|
||||||
|
kb_lib::EX_METAPLEX_TOKEN_METADATA_REVOKE_OPERATION.to_string(),
|
||||||
|
],
|
||||||
|
requires_collection: false,
|
||||||
|
requires_programmable_rules: true,
|
||||||
|
requires_postcondition: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn synthetic_inventory_is_exact_unique_and_covers_every_required_asset_family() {
|
||||||
|
let scenarios = crate::metaplex_token_metadata_synthetic_scenarios();
|
||||||
|
assert_eq!(scenarios.len(), 5);
|
||||||
|
let ids = scenarios
|
||||||
|
.iter()
|
||||||
|
.map(|scenario| return scenario.id.as_str())
|
||||||
|
.collect::<std::collections::BTreeSet<&str>>();
|
||||||
|
assert_eq!(ids.len(), scenarios.len());
|
||||||
|
let families = scenarios
|
||||||
|
.iter()
|
||||||
|
.map(|scenario| return scenario.asset_family)
|
||||||
|
.collect::<std::collections::BTreeSet<crate::MetaplexTokenMetadataAssetFamily>>(
|
||||||
|
);
|
||||||
|
assert_eq!(families.len(), 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn synthetic_scenarios_are_simulation_safe_and_require_postconditions() {
|
||||||
|
for scenario in crate::metaplex_token_metadata_synthetic_scenarios() {
|
||||||
|
assert_eq!(scenario.mode, crate::MetaplexTokenMetadataScenarioMode::Synthetic);
|
||||||
|
assert!(!scenario.operation_codes.is_empty());
|
||||||
|
assert!(scenario.requires_postcondition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn programmable_and_collection_requirements_are_declared_only_where_needed() {
|
||||||
|
let scenarios = crate::metaplex_token_metadata_synthetic_scenarios();
|
||||||
|
let programmable = scenarios
|
||||||
|
.iter()
|
||||||
|
.filter(|scenario| return scenario.requires_programmable_rules)
|
||||||
|
.collect::<std::vec::Vec<&crate::MetaplexTokenMetadataScenario>>();
|
||||||
|
assert_eq!(programmable.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
programmable[0].asset_family,
|
||||||
|
crate::MetaplexTokenMetadataAssetFamily::ProgrammableNft
|
||||||
|
);
|
||||||
|
let collection = scenarios
|
||||||
|
.iter()
|
||||||
|
.filter(|scenario| return scenario.requires_collection)
|
||||||
|
.collect::<std::vec::Vec<&crate::MetaplexTokenMetadataScenario>>();
|
||||||
|
assert_eq!(collection.len(), 1);
|
||||||
|
assert_eq!(collection[0].asset_family, crate::MetaplexTokenMetadataAssetFamily::Collection);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
// file: kb-pipeline-demo-scenarios/src/solana_metaplex_token_metadata_validation.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
//! Machine-readable Metaplex Token Metadata validation contract.
|
||||||
|
|
||||||
|
/// Maximum evidence entries accepted by one Metaplex validation scenario.
|
||||||
|
pub const MAX_METAPLEX_TOKEN_METADATA_VALIDATION_EVIDENCE: usize = 64;
|
||||||
|
|
||||||
|
/// Exact validation status for one Metaplex scenario.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum MetaplexTokenMetadataValidationStatus {
|
||||||
|
/// Scenario has not been executed.
|
||||||
|
NotRun,
|
||||||
|
/// Scenario passed deterministic synthetic validation.
|
||||||
|
SyntheticValidated,
|
||||||
|
/// Scenario was simulated on Devnet or Testnet.
|
||||||
|
Simulated,
|
||||||
|
/// Scenario was submitted but confirmation evidence is incomplete.
|
||||||
|
Submitted,
|
||||||
|
/// Scenario was confirmed and all postconditions passed.
|
||||||
|
Confirmed,
|
||||||
|
/// Scenario is unavailable in the selected environment.
|
||||||
|
Unavailable,
|
||||||
|
/// Scenario ran and failed.
|
||||||
|
Failed,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One bounded validation evidence entry.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct MetaplexTokenMetadataValidationEvidence {
|
||||||
|
/// Stable evidence kind.
|
||||||
|
pub kind: std::string::String,
|
||||||
|
/// Bounded evidence value.
|
||||||
|
pub value: std::string::String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One scenario declared by the canonical Metaplex validation matrix.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct MetaplexTokenMetadataValidationMatrixScenario {
|
||||||
|
/// Stable scenario identifier.
|
||||||
|
pub id: std::string::String,
|
||||||
|
/// Required scenario mode.
|
||||||
|
pub mode: crate::MetaplexTokenMetadataScenarioMode,
|
||||||
|
/// Exact validation status.
|
||||||
|
pub status: crate::MetaplexTokenMetadataValidationStatus,
|
||||||
|
/// Evidence kinds required before confirmation.
|
||||||
|
pub required_evidence: std::vec::Vec<std::string::String>,
|
||||||
|
/// Observed evidence.
|
||||||
|
#[serde(default)]
|
||||||
|
pub evidence: std::vec::Vec<crate::MetaplexTokenMetadataValidationEvidence>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Canonical Metaplex validation matrix.
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct MetaplexTokenMetadataValidationMatrix {
|
||||||
|
/// Matrix schema version.
|
||||||
|
pub matrix_version: u32,
|
||||||
|
/// Owning milestone.
|
||||||
|
pub milestone: std::string::String,
|
||||||
|
/// Ordered validation scenarios.
|
||||||
|
pub scenarios: std::vec::Vec<crate::MetaplexTokenMetadataValidationMatrixScenario>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loads and validates the canonical Metaplex validation matrix.
|
||||||
|
pub fn load_metaplex_token_metadata_validation_matrix()
|
||||||
|
-> kb_core::Result<crate::MetaplexTokenMetadataValidationMatrix> {
|
||||||
|
let parsed =
|
||||||
|
match serde_json::from_str::<crate::MetaplexTokenMetadataValidationMatrix>(include_str!(
|
||||||
|
"../../test-fixtures/contract-matrices/METAPLEX_TOKEN_METADATA_VALIDATION_MATRIX.json"
|
||||||
|
)) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => {
|
||||||
|
return std::result::Result::Err(kb_core::Error::new(
|
||||||
|
"metaplex_token_metadata_validation_matrix_invalid_json",
|
||||||
|
format!("Metaplex validation matrix JSON is invalid: {error}"),
|
||||||
|
));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if let std::result::Result::Err(error) =
|
||||||
|
validate_metaplex_token_metadata_validation_matrix(&parsed)
|
||||||
|
{
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Validates the canonical Metaplex validation matrix.
|
||||||
|
pub fn validate_metaplex_token_metadata_validation_matrix(
|
||||||
|
matrix: &crate::MetaplexTokenMetadataValidationMatrix,
|
||||||
|
) -> kb_core::Result<()> {
|
||||||
|
if matrix.matrix_version != 1 || matrix.milestone != "0.4.7" {
|
||||||
|
return std::result::Result::Err(kb_core::Error::new(
|
||||||
|
"metaplex_token_metadata_validation_matrix_contract_mismatch",
|
||||||
|
"Metaplex validation matrix must use version 1 for milestone 0.4.7",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let expected = crate::metaplex_token_metadata_synthetic_scenarios()
|
||||||
|
.iter()
|
||||||
|
.map(|scenario| return scenario.id.clone())
|
||||||
|
.collect::<std::vec::Vec<std::string::String>>();
|
||||||
|
let actual = matrix
|
||||||
|
.scenarios
|
||||||
|
.iter()
|
||||||
|
.map(|scenario| return scenario.id.clone())
|
||||||
|
.collect::<std::vec::Vec<std::string::String>>();
|
||||||
|
if actual != expected {
|
||||||
|
return std::result::Result::Err(kb_core::Error::new(
|
||||||
|
"metaplex_token_metadata_validation_matrix_inventory_mismatch",
|
||||||
|
"Metaplex validation matrix inventory differs from the compiled scenario inventory",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let mut ids = std::collections::BTreeSet::<&str>::new();
|
||||||
|
for scenario in &matrix.scenarios {
|
||||||
|
if !ids.insert(scenario.id.as_str()) || scenario.required_evidence.is_empty() {
|
||||||
|
return std::result::Result::Err(kb_core::Error::new(
|
||||||
|
"metaplex_token_metadata_validation_matrix_scenario_invalid",
|
||||||
|
"Metaplex validation scenarios must be unique and declare required evidence",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if scenario.evidence.len() > crate::MAX_METAPLEX_TOKEN_METADATA_VALIDATION_EVIDENCE {
|
||||||
|
return std::result::Result::Err(kb_core::Error::new(
|
||||||
|
"metaplex_token_metadata_validation_evidence_limit_exceeded",
|
||||||
|
"Metaplex validation evidence exceeds the compiled bound",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if matches!(
|
||||||
|
scenario.status,
|
||||||
|
crate::MetaplexTokenMetadataValidationStatus::NotRun
|
||||||
|
| crate::MetaplexTokenMetadataValidationStatus::Unavailable
|
||||||
|
) && !scenario.evidence.is_empty()
|
||||||
|
{
|
||||||
|
return std::result::Result::Err(kb_core::Error::new(
|
||||||
|
"metaplex_token_metadata_validation_unobserved_with_evidence",
|
||||||
|
"Not-run or unavailable Metaplex scenarios must not contain evidence",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if scenario.status == crate::MetaplexTokenMetadataValidationStatus::Confirmed {
|
||||||
|
let observed = scenario
|
||||||
|
.evidence
|
||||||
|
.iter()
|
||||||
|
.map(|evidence| return evidence.kind.as_str())
|
||||||
|
.collect::<std::collections::BTreeSet<&str>>();
|
||||||
|
if !scenario
|
||||||
|
.required_evidence
|
||||||
|
.iter()
|
||||||
|
.all(|kind| return observed.contains(kind.as_str()))
|
||||||
|
{
|
||||||
|
return std::result::Result::Err(kb_core::Error::new(
|
||||||
|
"metaplex_token_metadata_validation_confirmed_without_required_evidence",
|
||||||
|
"Confirmed Metaplex scenario lacks required evidence",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn canonical_matrix_is_machine_readable_and_matches_synthetic_inventory() {
|
||||||
|
let result = crate::load_metaplex_token_metadata_validation_matrix();
|
||||||
|
assert!(result.is_ok());
|
||||||
|
if let std::result::Result::Ok(matrix) = result {
|
||||||
|
assert_eq!(matrix.scenarios.len(), 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn confirmed_scenario_requires_every_declared_evidence_kind() {
|
||||||
|
let result = crate::load_metaplex_token_metadata_validation_matrix();
|
||||||
|
assert!(result.is_ok());
|
||||||
|
if let std::result::Result::Ok(mut matrix) = result {
|
||||||
|
matrix.scenarios[0].status = crate::MetaplexTokenMetadataValidationStatus::Confirmed;
|
||||||
|
assert!(crate::validate_metaplex_token_metadata_validation_matrix(&matrix).is_err());
|
||||||
|
matrix.scenarios[0].evidence = matrix.scenarios[0]
|
||||||
|
.required_evidence
|
||||||
|
.iter()
|
||||||
|
.map(|kind| {
|
||||||
|
return crate::MetaplexTokenMetadataValidationEvidence {
|
||||||
|
kind: kind.clone(),
|
||||||
|
value: "synthetic-proof".to_string(),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
assert!(crate::validate_metaplex_token_metadata_validation_matrix(&matrix).is_ok());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"matrixVersion": 1,
|
||||||
|
"milestone": "0.4.7",
|
||||||
|
"scenarios": [
|
||||||
|
{
|
||||||
|
"id": "metaplex_nft_create_update",
|
||||||
|
"mode": "synthetic",
|
||||||
|
"status": "not_run",
|
||||||
|
"requiredEvidence": [
|
||||||
|
"test_suite",
|
||||||
|
"prepared_plan",
|
||||||
|
"postcondition"
|
||||||
|
],
|
||||||
|
"evidence": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "metaplex_sft_create_mint",
|
||||||
|
"mode": "synthetic",
|
||||||
|
"status": "not_run",
|
||||||
|
"requiredEvidence": [
|
||||||
|
"test_suite",
|
||||||
|
"prepared_plan",
|
||||||
|
"postcondition"
|
||||||
|
],
|
||||||
|
"evidence": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "metaplex_fungible_create_update",
|
||||||
|
"mode": "synthetic",
|
||||||
|
"status": "not_run",
|
||||||
|
"requiredEvidence": [
|
||||||
|
"test_suite",
|
||||||
|
"prepared_plan",
|
||||||
|
"postcondition"
|
||||||
|
],
|
||||||
|
"evidence": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "metaplex_collection_verify_unverify",
|
||||||
|
"mode": "synthetic",
|
||||||
|
"status": "not_run",
|
||||||
|
"requiredEvidence": [
|
||||||
|
"test_suite",
|
||||||
|
"prepared_plan",
|
||||||
|
"postcondition"
|
||||||
|
],
|
||||||
|
"evidence": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "metaplex_pnft_delegate_transfer_revoke",
|
||||||
|
"mode": "synthetic",
|
||||||
|
"status": "not_run",
|
||||||
|
"requiredEvidence": [
|
||||||
|
"test_suite",
|
||||||
|
"prepared_plan",
|
||||||
|
"postcondition"
|
||||||
|
],
|
||||||
|
"evidence": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user