v0.4.8-pre.008

This commit is contained in:
2026-08-06 06:12:54 +02:00
parent 44088c71c9
commit f576fbade2
52 changed files with 3592 additions and 420 deletions

View File

@@ -1,5 +1,5 @@
# file: Cargo.toml # file: Cargo.toml
# version: 29 # version: 30
[workspace] [workspace]
resolver = "3" resolver = "3"
@@ -18,7 +18,7 @@ members = [
] ]
[workspace.package] [workspace.package]
version = "0.4.8-pre.7" version = "0.4.8-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"

View File

@@ -1,5 +1,5 @@
<!-- file: docs/README.md --> <!-- file: docs/README.md -->
<!-- version: 18 --> <!-- version: 21 -->
# Documentation active de Khadhroony Bot3 # Documentation active de Khadhroony Bot3
@@ -42,6 +42,9 @@ Modèles documentaires non génératifs :
- [`audits/V0_4_8_PRE_002_METADATA_CONTRACT_AUDIT.md`](audits/V0_4_8_PRE_002_METADATA_CONTRACT_AUDIT.md) ; - [`audits/V0_4_8_PRE_002_METADATA_CONTRACT_AUDIT.md`](audits/V0_4_8_PRE_002_METADATA_CONTRACT_AUDIT.md) ;
- [`audits/V0_4_8_PRE_007_METAPLEX_MATERIALIZER_STRUCTURE_AUDIT.md`](audits/V0_4_8_PRE_007_METAPLEX_MATERIALIZER_STRUCTURE_AUDIT.md) ; - [`audits/V0_4_8_PRE_007_METAPLEX_MATERIALIZER_STRUCTURE_AUDIT.md`](audits/V0_4_8_PRE_007_METAPLEX_MATERIALIZER_STRUCTURE_AUDIT.md) ;
- [`audits/V0_4_8_PRE_007_MATERIALIZER_CONVENTION_AUDIT.md`](audits/V0_4_8_PRE_007_MATERIALIZER_CONVENTION_AUDIT.md) ; - [`audits/V0_4_8_PRE_007_MATERIALIZER_CONVENTION_AUDIT.md`](audits/V0_4_8_PRE_007_MATERIALIZER_CONVENTION_AUDIT.md) ;
- [`audits/V0_4_8_PRE_008_METADATA_SOLANA_PROGRAM_PIPELINE_AUDIT.md`](audits/V0_4_8_PRE_008_METADATA_SOLANA_PROGRAM_PIPELINE_AUDIT.md) ;
- [`audits/V0_4_8_PRE_008_METADATA_AND_PIPELINE_NOMENCLATURE_AUDIT.md`](audits/V0_4_8_PRE_008_METADATA_AND_PIPELINE_NOMENCLATURE_AUDIT.md) ;
- [`audits/V0_4_8_PRE_008_SOLANA_PROGRAM_METADATA_HISTORICAL_REPLAY_FIX.md`](audits/V0_4_8_PRE_008_SOLANA_PROGRAM_METADATA_HISTORICAL_REPLAY_FIX.md) ;
- [`decisions/DOCUMENT_ARCHIVE_SELECTION_POLICY.md`](decisions/DOCUMENT_ARCHIVE_SELECTION_POLICY.md) ; - [`decisions/DOCUMENT_ARCHIVE_SELECTION_POLICY.md`](decisions/DOCUMENT_ARCHIVE_SELECTION_POLICY.md) ;
- [`decisions/WINCODE_COMPATIBILITY_POLICY.md`](decisions/WINCODE_COMPATIBILITY_POLICY.md). - [`decisions/WINCODE_COMPATIBILITY_POLICY.md`](decisions/WINCODE_COMPATIBILITY_POLICY.md).
@@ -101,7 +104,9 @@ Les onze crates possèdent désormais `README.md`, `TODO.md`, `USAGE.md` et `CHA
- [`Audit contractuel 0.4.8-pre.002 — metadata`](audits/V0_4_8_PRE_002_METADATA_CONTRACT_AUDIT.md) ; - [`Audit contractuel 0.4.8-pre.002 — metadata`](audits/V0_4_8_PRE_002_METADATA_CONTRACT_AUDIT.md) ;
- [`Audit 0.4.8-pre.005 — instructions et historique Solana Program Metadata`](audits/V0_4_8_PRE_005_SOLANA_PROGRAM_METADATA_INSTRUCTION_HISTORY_AUDIT.md) ; - [`Audit 0.4.8-pre.005 — instructions et historique Solana Program Metadata`](audits/V0_4_8_PRE_005_SOLANA_PROGRAM_METADATA_INSTRUCTION_HISTORY_AUDIT.md) ;
- [`Audit 0.4.8-pre.007 — structure du matérialiseur Metaplex Token Metadata`](audits/V0_4_8_PRE_007_METAPLEX_MATERIALIZER_STRUCTURE_AUDIT.md) ; - [`Audit 0.4.8-pre.007 — structure du matérialiseur Metaplex Token Metadata`](audits/V0_4_8_PRE_007_METAPLEX_MATERIALIZER_STRUCTURE_AUDIT.md) ;
- [`Audit 0.4.8-pre.007 — conventions et structure de tous les matérialisateurs`](audits/V0_4_8_PRE_007_MATERIALIZER_CONVENTION_AUDIT.md). - [`Audit 0.4.8-pre.007 — conventions et structure de tous les matérialisateurs`](audits/V0_4_8_PRE_007_MATERIALIZER_CONVENTION_AUDIT.md) ;
- [`Audit 0.4.8-pre.008 — pipeline Solana Program Metadata`](audits/V0_4_8_PRE_008_METADATA_SOLANA_PROGRAM_PIPELINE_AUDIT.md).
- [`Correctif 0.4.8-pre.008 — replay historique Solana Program Metadata`](audits/V0_4_8_PRE_008_SOLANA_PROGRAM_METADATA_HISTORICAL_REPLAY_FIX.md).
## Plans de version actifs ## Plans de version actifs

View File

@@ -1,5 +1,5 @@
<!-- file: docs/audits/V0_4_8_PRE_007_MATERIALIZER_CONVENTION_AUDIT.md --> <!-- file: docs/audits/V0_4_8_PRE_007_MATERIALIZER_CONVENTION_AUDIT.md -->
<!-- version: 3 --> <!-- version: 4 -->
# Audit des conventions et de la structure des matérialisateurs — `0.4.8-pre.007-fix-004` # Audit des conventions et de la structure des matérialisateurs — `0.4.8-pre.007-fix-004`
@@ -46,7 +46,7 @@ Les identités runtime servent au code et au routage des logs. Les `processorNam
## 3. Inventaire audité ## 3. Inventaire audité
| Composant | Type principal ou API | Statut | Identité runtime | `processorName` | | Composant | Type principal ou API | Statut | Identité runtime | `processorName` |
|-------------------------|---------------------------------------|------------|--------------------------------------------------------|-------------------------------------------------| |-------------------------|-----------------------------------------------|---------|--------------------------------------------------------|-------------------------------------------------|
| Administration | `MtAdminMaterializer` | actif | `kb-lib.materializer.admin` | `materializer.admin` | | Administration | `MtAdminMaterializer` | actif | `kb-lib.materializer.admin` | `materializer.admin` |
| Bridge | `MtBridgeMaterializer` | réservé | `kb-lib.materializer.bridge` | `materializer.bridge` | | Bridge | `MtBridgeMaterializer` | réservé | `kb-lib.materializer.bridge` | `materializer.bridge` |
| Compliance audit | `MtComplianceAuditMaterializer` | actif | `kb-lib.materializer.compliance.audit` | `materializer.compliance.audit` | | Compliance audit | `MtComplianceAuditMaterializer` | actif | `kb-lib.materializer.compliance.audit` | `materializer.compliance.audit` |
@@ -55,9 +55,9 @@ Les identités runtime servent au code et au routage des logs. Les `processorNam
| Lending | `MtLendingMaterializer` | réservé | `kb-lib.materializer.lending` | `materializer.lending` | | Lending | `MtLendingMaterializer` | réservé | `kb-lib.materializer.lending` | `materializer.lending` |
| Lifecycle | `MtLifecycleMaterializer` | actif | `kb-lib.materializer.lifecycle` | `materializer.lifecycle` | | Lifecycle | `MtLifecycleMaterializer` | actif | `kb-lib.materializer.lifecycle` | `materializer.lifecycle` |
| Liquidity | `MtLiquidityMaterializer` | réservé | `kb-lib.materializer.liquidity` | `materializer.liquidity` | | Liquidity | `MtLiquidityMaterializer` | réservé | `kb-lib.materializer.liquidity` | `materializer.liquidity` |
| Metaplex Token Metadata | APIs de snapshots | state-only | `kb-lib.materializer.metadata.metaplex_token_metadata` | `materializer.metadata.metaplex_token_metadata` | | Metaplex Token Metadata | `MtMetadataMetaplexTokenMetadataMaterializer` | actif | `kb-lib.materializer.metadata.metaplex_token_metadata` | `materializer.metadata.metaplex_token_metadata` |
| Solana Program Metadata | `MtSolanaProgramMetadataMaterializer` | actif | `kb-lib.materializer.metadata.solana_program_metadata` | `materializer.metadata.solana_program_metadata` | | Solana Program Metadata | `MtMetadataSolanaProgramMaterializer` | actif | `kb-lib.materializer.metadata.solana_program_metadata` | `materializer.metadata.solana_program_metadata` |
| Token-2022 metadata | API de snapshots | state-only | `kb-lib.materializer.metadata.token_2022` | `materializer.metadata.token_2022` | | Token-2022 metadata | `MtMetadataToken2022Materializer` | actif | `kb-lib.materializer.metadata.token_2022` | `materializer.metadata.token_2022` |
| NFT | `MtNftMaterializer` | réservé | `kb-lib.materializer.nft` | `materializer.nft` | | NFT | `MtNftMaterializer` | réservé | `kb-lib.materializer.nft` | `materializer.nft` |
| Oracle | `MtOracleMaterializer` | réservé | `kb-lib.materializer.oracle` | `materializer.oracle` | | Oracle | `MtOracleMaterializer` | réservé | `kb-lib.materializer.oracle` | `materializer.oracle` |
| Orderbook | `MtOrderbookMaterializer` | réservé | `kb-lib.materializer.orderbook` | `materializer.orderbook` | | Orderbook | `MtOrderbookMaterializer` | réservé | `kb-lib.materializer.orderbook` | `materializer.orderbook` |
@@ -73,9 +73,7 @@ Les identités runtime servent au code et au routage des logs. Les `processorNam
| Transaction annotations | `MtTransactionAnnotationMaterializer` | actif | `kb-lib.materializer.transaction.annotations` | `materializer.transaction.annotations` | | Transaction annotations | `MtTransactionAnnotationMaterializer` | actif | `kb-lib.materializer.transaction.annotations` | `materializer.transaction.annotations` |
| Vault | `MtVaultMaterializer` | réservé | `kb-lib.materializer.vault` | `materializer.vault` | | Vault | `MtVaultMaterializer` | réservé | `kb-lib.materializer.vault` | `materializer.vault` |
Bilan : **25 composants nommés**, dont **9 matérialisateurs actifs**, **14 coquilles réservées** et **2 composants state-only**. Bilan après correction `pre.008` : **25 composants nommés**, dont **11 matérialisateurs actifs** et **14 coquilles réservées**. Les trois domaines metadata exposent des types spécialisés ; aucun wrapper metadata générique ne masque leur ownership.
`MtMetadataMaterializer` reste un wrapper public de compatibilité vers `MtSolanaProgramMetadataMaterializer`. Il ne possède pas une identité distincte et ne crée pas un second propriétaire de projection.
## 4. Structure normalisée ## 4. Structure normalisée
@@ -161,7 +159,7 @@ Les 22 chemins retirés sont fournis dans `delete-files.txt` à la racine du del
## 10. Conclusion ## 10. Conclusion
Les matérialisateurs utilisent désormais une convention unique pour les identités runtime, les processeurs persistés, les tracing targets, les constantes, les façades et les coquilles réservées. Les différences restantes sont fonctionnelles et explicites : un composant peut être actif, réservé ou state-only, mais il ne change pas de convention structurelle ou de provenance selon son statut. Les matérialisateurs utilisent désormais une convention unique pour les identités runtime, les processeurs persistés, les tracing targets, les constantes, les façades et les coquilles réservées. Un composant peut être actif ou réservé ; les APIs stateful restent des capacités de son matérialiseur spécialisé et ne constituent pas un type générique parallèle.
## 11. Correction des identités opérationnelles et du tracing ## 11. Correction des identités opérationnelles et du tracing
@@ -171,7 +169,7 @@ La compilation du workspace corrigé a révélé trois tracing targets déclaré
- `kb-lib.materializer.metadata.metaplex_token_metadata` ; - `kb-lib.materializer.metadata.metaplex_token_metadata` ;
- `kb-lib.materializer.metadata.token_2022`. - `kb-lib.materializer.metadata.token_2022`.
Le composant state-only Token-2022 réexportait également `MT_METADATA_TOKEN_2022_COMPONENT_NAME` sans le consommer dans son implémentation. Cette situation ne signifiait pas que les snapshots Token-2022 nétaient pas matérialisés : la fonction `materializer_metadata_materialize_token_2022_snapshot` produisait déjà les projections `TokenMetadata`, `TokenGroup` et `TokenGroupMember`. Elle révélait en revanche que lidentité runtime du composant nétait pas attachée à une opération observable. Le composant de snapshots Token-2022 réexportait également `MT_METADATA_TOKEN_2022_COMPONENT_NAME` sans le consommer dans son implémentation. Cette situation ne signifiait pas que les snapshots Token-2022 nétaient pas matérialisés : la fonction `materializer_metadata_materialize_token_2022_snapshot` produisait déjà les projections `TokenMetadata`, `TokenGroup` et `TokenGroupMember`. Elle révélait en revanche que lidentité runtime du composant nétait pas attachée à une opération observable.
Le correctif ajoute des événements structurés réels pour les chemins suivants : Le correctif ajoute des événements structurés réels pour les chemins suivants :
@@ -186,4 +184,4 @@ Laudit mécanique refuse désormais :
- un `TRACING_TARGET_*` de `kb-lib` déclaré sans macro `tracing` réelle ; - un `TRACING_TARGET_*` de `kb-lib` déclaré sans macro `tracing` réelle ;
- un `MT_*_COMPONENT_NAME` qui nest consommé par aucun fichier dimplémentation du composant. - un `MT_*_COMPONENT_NAME` qui nest consommé par aucun fichier dimplémentation du composant.
Les composants state-only restent distincts des matérialiseurs instructionnels : ils nimplémentent pas artificiellement `MtApiEventMaterializer`, mais leur identité runtime est désormais effectivement utilisée par leurs opérations de snapshot. La correction `pre.008` ajoute les matérialiseurs instructionnels spécialisés Metaplex et Token-2022 parce que leurs instructions sont effectivement décodées. Leurs APIs de snapshots restent distinctes et autoritatives, tandis que les sorties dinstructions restent des faits committés non autoritatifs.

View File

@@ -0,0 +1,52 @@
<!-- file: docs/audits/V0_4_8_PRE_008_METADATA_AND_PIPELINE_NOMENCLATURE_AUDIT.md -->
<!-- version: 1 -->
# Audit de nomenclature metadata et pipeline
## 1. Objet
Cet audit corrige deux dérives révélées après lintégration pipeline de `0.4.8-pre.008` : un matérialiseur Solana Program Metadata nommé hors de la hiérarchie `MtMetadata*`, et des modules `kb-pipeline` préfixés uniformément par `solana_` malgré des domaines Metaplex ou SPL distincts.
## 2. Matérialiseurs metadata
La surface publique canonique comprend désormais exactement trois types spécialisés :
| Domaine | Type | Processor |
|---------------------------|-----------------------------------------------|-------------------------------------------------|
| Metaplex Token Metadata | `MtMetadataMetaplexTokenMetadataMaterializer` | `materializer.metadata.metaplex_token_metadata` |
| Solana Program Metadata | `MtMetadataSolanaProgramMaterializer` | `materializer.metadata.solana_program_metadata` |
| Token-2022 metadata/group | `MtMetadataToken2022Materializer` | `materializer.metadata.token_2022` |
Le type `MtSolanaProgramMetadataMaterializer` et le wrapper `MtMetadataMaterializer` sont supprimés. Chaque type implémente les contrats legacy et contextuel, possède ses constantes, son tracing target et son ownership instructionnel. Les helpers `state.rs` et `account.rs` restent séparés : ils produisent les snapshots autoritatifs, alors que les matérialiseurs dinstructions produisent des faits committés non autoritatifs.
Les neuf instructions incorporées Token Metadata et Token Group sont classées sous `MdEventFamily::Metadata`. Cette classification évite de confier les changements dautorité propres aux interfaces metadata au matérialiseur dadministration générique.
## 3. Registre desktop
`demo_decode_replay` enregistre les trois types spécialisés. Un test construit une observation représentative de chaque domaine et vérifie quelle possède exactement un propriétaire dans le registre runtime.
## 4. Nomenclature `kb-pipeline`
Les modules privés suivent la hiérarchie suivante :
- `metadata_metaplex_token_metadata_*` ;
- `metadata_solana_program_*` ;
- `spl_ata_stateful` ;
- `spl_elgamal_registry_stateful` ;
- `spl_token_stateful` ;
- `spl_token_2022_*` ;
- `solana_stateful` uniquement pour les contrats Solana Core transversaux.
Les fonctions et structures publiques restent inchangées. Le renommage est donc structurel et documentaire, sans rupture volontaire de lAPI consommée par le desktop ou les futurs scénarios Devnet.
## 5. Fichiers remplacés
Les anciens fichiers `solana_*` de `kb-pipeline`, lancien test externe et lancienne matrice sont listés dans `delete-files.txt`. Leur suppression est obligatoire après extraction du delta.
## 6. Garde-fous
- les exports racine couvrent les trois matérialiseurs spécialisés ;
- le test externe nutilise que la façade `kb_lib::*` ;
- les familles Token-2022 metadata/group sont testées exhaustivement ;
- le test externe pipeline utilise des assertions de compilation `const` ;
- laudit des règles vérifie les façades, les exports, les constantes et la nomenclature des composants.

View File

@@ -0,0 +1,113 @@
<!-- file: docs/audits/V0_4_8_PRE_008_METADATA_SOLANA_PROGRAM_PIPELINE_AUDIT.md -->
<!-- version: 2 -->
# Audit du pipeline Solana Program Metadata — `0.4.8-pre.008`
## 1. Objet
Cet audit ferme la frontière réutilisable de Solana Program Metadata dans `kb-pipeline` sans introduire de fixture, de wallet de démonstration ni de dépendance envers `kb-pipeline-demo-scenarios`.
La surface couverte est le programme :
```text
ProgM6JCCvbYkfKqJYHePx4xxSUSqJp7rh8Lyv7nk7S
```
## 2. Séparation des responsabilités
| Crate ou composant | Responsabilité en `pre.008` |
|-----------------------------------------------|----------------------------------------------------------------------------------------|
| `kb-lib` | modèles, décodage, matérialisation, intents, builders, exécuteur et safety commun |
| `kb-pipeline` | lectures stateful, préflight, readiness simulation-first et postconditions |
| `kb-pipeline-demo-scenarios` | aucune modification ; fixture et parcours Devnet réservés à `pre.009` |
| `kb-app-demo-desktop/demo_decode_replay` | enregistrement générique du décodeur et du matérialiseur pour la validation historique |
| `kb-app-demo-desktop/demo_execution_metadata` | aucune modification ; intégration `ProgM6…` réservée à `pre.009` |
## 3. Lectures stateful
Le pipeline distingue explicitement :
- compte absent ;
- compte présent mais vide ou intégralement nul ;
- compte `Buffer` initialisé ;
- compte `Metadata` initialisé.
Les réponses RPC doivent être confirmées, non exécutables, complètes et sous la borne maximale. Un état initialisé doit être détenu par `ProgM6…`. Le décodage et la projection autoritative sont délégués aux APIs publiques de `kb-lib`.
## 4. Préflight
Le préflight lie lintent typé au plan préparé et contrôle :
- le Program ID et le code dopération exacts ;
- lunique instruction du plan ;
- lapprobation explicite des mutations sensibles ;
- la décision de `ExSafetyChecker` ;
- les snapshots confirmés et non dupliqués ;
- létat du compte cible ;
- les sources `Buffer` ;
- lautorité explicite ou le contexte canonical ;
- le préfinancement rent des opérations dallocation ou dagrandissement.
Une décision `Deny` bloque le traitement. Une décision `RequireConfirmation` reste exécutable mais doit être satisfaite dans lenveloppe de readiness.
## 5. Readiness et postconditions
La readiness refuse :
- un plan non lié au préflight ;
- un message non simulé ou simulé sous un autre hash ;
- une simulation en échec ;
- un signer requis non résolu ;
- une soumission sans confirmation opérateur ;
- une soumission depuis un plan `dry_run`.
Les neuf opérations possèdent une postcondition stateful :
| Opération | Postcondition principale |
|----------------|-----------------------------------------------------------------|
| `Write` | octets attendus au bon offset du `Buffer` |
| `Initialize` | état `Metadata`, PDA, seed, descripteurs et contenu compatibles |
| `SetAuthority` | autorité finale exacte ou supprimée |
| `SetData` | descripteurs et contenu final compatibles |
| `SetImmutable` | `mutable = false` |
| `Trim` | même type détat et taille non croissante |
| `Close` | compte absent |
| `Allocate` | état `Buffer`, autorité, seed et canonicité compatibles |
| `Extend` | même type détat et taille augmentée exactement |
Une lecture finale absente du rapport produit `NotApplicable`, jamais `Confirmed`.
## 6. Validation historique prévue
La validation historique opérateur suit :
1. `demo_backfill` sur un échantillon borné de signatures ;
2. extraction Core ;
3. decode replay ciblé sur `ProgM6…` et `metadata.solana_program_metadata` ;
4. matérialisation via `materializer.metadata.solana_program_metadata` ;
5. lecture bornée des événements matérialisés.
Cette validation précède les scénarios Devnet de `pre.009` et ne dépend pas de leur fixture.
## 7. Matrice
La matrice machine-readable est :
```text
test-fixtures/contract-matrices/METADATA_SOLANA_PROGRAM_PIPELINE_MATRIX.json
```
Elle distingue explicitement la crate généraliste du package de scénarios et ferme linventaire des neuf opérations.
## 8. Correction de nomenclature `pre.008`
Les modules privés de `kb-pipeline` sont renommés selon leur domaine :
- `metadata_metaplex_token_metadata_*` pour Metaplex Token Metadata ;
- `metadata_solana_program_*` pour Solana Program Metadata ;
- `spl_ata_stateful` et `spl_elgamal_registry_stateful` pour les programmes SPL correspondants ;
- `spl_token_stateful` et `spl_token_2022_*` pour SPL Token et Token-2022 ;
- `solana_stateful` reste réservé aux primitives Solana Core transversales.
Les fonctions et structures publiques restent stables. La matrice et le test externe suivent la même nomenclature de fichier.
Le registre de replay desktop utilise désormais trois matérialiseurs metadata spécialisés : `MtMetadataMetaplexTokenMetadataMaterializer`, `MtMetadataSolanaProgramMaterializer` et `MtMetadataToken2022Materializer`. Aucun wrapper générique `MtMetadataMaterializer` nest conservé.

View File

@@ -0,0 +1,54 @@
<!-- file: docs/audits/V0_4_8_PRE_008_SOLANA_PROGRAM_METADATA_HISTORICAL_REPLAY_FIX.md -->
<!-- version: 1 -->
# Correctif du replay historique Solana Program Metadata
## 1. Validation ayant révélé le défaut
La validation Mainnet a importé deux pages de 100 transactions du programme `ProgM6JCCvbYkfKqJYHePx4xxSUSqJp7rh8Lyv7nk7S`, puis extrait 200 transactions et rejoué 582 instructions. Le processeur `metadata.solana_program_metadata` a reconnu et dispatché 132 instructions, mais les 132 ont terminé en échec de décodage.
Les surfaces observées étaient :
| Instruction | Observations |
|-----------------|-------------:|
| `write` | 88 |
| `allocate` | 15 |
| `initialize` | 11 |
| `set_authority` | 7 |
| `close` | 5 |
| `set_data` | 5 |
| `set_immutable` | 1 |
| `trim` | 0 |
| `extend` | 0 |
Le fait que toutes les instructions observées échouent malgré des discriminants reconnus et des transactions réussies indique un défaut transversal après reconnaissance, et non sept défauts wire indépendants.
## 2. Cause
`MdCoreInstructionReplayInput.account_keys_json` est produit depuis len-tête du message Solana. Ses champs `signer` et `writable` représentent donc les privilèges globaux de chaque clé pour lensemble de la transaction.
Le décodeur comparait ces champs par égalité avec les privilèges minimaux documentés pour chaque position de linstruction. Cette égalité est invalide : une autorité peut être readonly dans le contrat de linstruction tout en étant writable au niveau du message parce quelle est aussi fee payer ou intervient comme compte writable dans une autre instruction.
Le programme on-chain vérifie les privilèges nécessaires, notamment `authority.is_signer()`, mais ne refuse pas un sur-ensemble de privilèges fourni par le message.
## 3. Correction
La validation applique désormais les règles suivantes :
- un compte déclaré signer doit être signer dans le message ;
- un compte déclaré writable doit être writable dans le message ;
- un compte déclaré readonly peut être globalement writable ;
- un compte déclaré non-signer peut être globalement signer ;
- les positions, rôles, placeholders optionnels et contrats de source restent vérifiés séparément ;
- labsence dun privilège requis reste un échec fermé.
Une régression couvre les neuf instructions stables avec une autorité globalement signer et writable.
## 4. Validation à rejouer
Après application du correctif, relancer les 132 entrées en échec, de préférence avec le replay forcé limité au programme `ProgM6…`. Le résultat attendu est :
- `decoded = 132` ;
- `failed = 0` ;
- une matérialisation pour chaque instruction committée ;
- `trim` et `extend` restent non observés dans cet échantillon et devront être couverts par les scénarios Devnet.

View File

@@ -1,11 +1,11 @@
<!-- file: docs/plans/V0_4_8_SOLANA_PROGRAM_METADATA_AND_TOKEN_2022_COMPLETENESS_PLAN.md --> <!-- file: docs/plans/V0_4_8_SOLANA_PROGRAM_METADATA_AND_TOKEN_2022_COMPLETENESS_PLAN.md -->
<!-- version: 10 --> <!-- version: 13 -->
# Plan `0.4.8` — Solana Program Metadata et complétude Token-2022 # Plan `0.4.8` — Solana Program Metadata et complétude Token-2022
## 1. Statut et rôle ## 1. Statut et rôle
Ce document est le livrable principal de `0.4.8-pre.001`, réconcilié après laudit contractuel de `0.4.8-pre.002`, les modèles de `pre.003`, les décodeurs de comptes de `pre.004`, le décodeur dinstructions de `pre.005`, la matérialisation de `pre.006` et lexécuteur de `pre.007`. Il constitue le plan vivant de la version et reste modifiable lorsque laudit du code, des interfaces officielles, de lIDL ou des validations Devnet impose un ajustement. Ce document est le livrable principal de `0.4.8-pre.001`, réconcilié après laudit contractuel de `0.4.8-pre.002`, les modèles de `pre.003`, les décodeurs de comptes de `pre.004`, le décodeur dinstructions de `pre.005`, la matérialisation de `pre.006`, lexécuteur de `pre.007` et lorchestration généraliste de `pre.008`. Il constitue le plan vivant de la version et reste modifiable lorsque laudit du code, des interfaces officielles, de lIDL ou des validations Devnet impose un ajustement.
Il doit être maintenu pendant chaque prerelease, puis archivé pendant la dernière prerelease après transfert des décisions durables vers le ROADMAP, les matrices, les guides, les rapports, les TODO et les changelogs concernés. Il doit être maintenu pendant chaque prerelease, puis archivé pendant la dernière prerelease après transfert des décisions durables vers le ROADMAP, les matrices, les guides, les rapports, les TODO et les changelogs concernés.
@@ -274,23 +274,37 @@ Résultat durable : les neuf instructions stables possèdent une opération typ
- aucune opération stable classée deprecated ou replaced faute de preuve officielle ; - aucune opération stable classée deprecated ou replaced faute de preuve officielle ;
- financement de rent volontairement laissé à la fixture et à lorchestration RPC : le programme exige que les comptes à allouer ou étendre soient préfinancés. - financement de rent volontairement laissé à la fixture et à lorchestration RPC : le programme exige que les comptes à allouer ou étendre soient préfinancés.
Travaux parallèles intégrés : les fonctions de matérialisation Metaplex existaient déjà dans `materializer/metadata/core.rs`; il ne sagissait donc pas dun oubli fonctionnel de `0.4.6/0.4.7`. Elles sont déplacées dans un sous-module dédié sans changement de clé métier. Token-2022 reçoit également son composant state-only dédié. Laudit est ensuite étendu à lensemble de `src/materializer` : 25 identités sont harmonisées, les façades deviennent pures, les implémentations utilisent `materializer.rs`, les constantes sont centralisées et les coquilles réservées ne produisent plus de faux événements legacy. Les constats sont consignés dans `docs/audits/V0_4_8_PRE_007_METAPLEX_MATERIALIZER_STRUCTURE_AUDIT.md` et `docs/audits/V0_4_8_PRE_007_MATERIALIZER_CONVENTION_AUDIT.md`. Un audit transversal de toutes les surfaces déjà livrées est planifié en `0.5.x` pour vérifier les règles de décodage exhaustif, propriété de matérialisation, exécution des opérations dangereuses via `ExSafetyChecker`, dépréciation officielle et versions remplacées decode-only. Travaux parallèles intégrés : les fonctions de matérialisation Metaplex existaient déjà dans `materializer/metadata/core.rs`; il ne sagissait donc pas dun oubli fonctionnel de `0.4.6/0.4.7`. Elles sont déplacées dans un sous-module dédié sans changement de clé métier. Token-2022 reçoit également son composant dédié de snapshots, puis `pre.008` ajoute son matérialiseur instructionnel spécialisé ainsi que celui de Metaplex. Laudit est ensuite étendu à lensemble de `src/materializer` : 25 identités sont harmonisées, les façades deviennent pures, les implémentations utilisent `materializer.rs`, les constantes sont centralisées et les coquilles réservées ne produisent plus de faux événements legacy. Les constats sont consignés dans `docs/audits/V0_4_8_PRE_007_METAPLEX_MATERIALIZER_STRUCTURE_AUDIT.md` et `docs/audits/V0_4_8_PRE_007_MATERIALIZER_CONVENTION_AUDIT.md`. Un audit transversal de toutes les surfaces déjà livrées est planifié en `0.5.x` pour vérifier les règles de décodage exhaustif, propriété de matérialisation, exécution des opérations dangereuses via `ExSafetyChecker`, dépréciation officielle et versions remplacées decode-only.
### `0.4.8-pre.008` — pipeline stateful Solana Program Metadata ### `0.4.8-pre.008` — pipeline stateful Solana Program Metadata — code terminé, replay historique à revalider
- simulation, soumission, confirmation, lectures avant/après, postconditions, replay et matérialisation. - lectures RPC bornées des comptes absents, non initialisés, `Buffer` et `Metadata` ;
- décodage et matérialisation autoritative des états via `kb-lib` ;
- préflight des neuf opérations, vérification des sources Buffer, autorités, canonicité et preuves de rent ;
- passage par `ExSafetyChecker`, readiness simulation-first, résolution des signers et autorisation de soumission ;
- postconditions avant/après des neuf opérations, sans succès inventé lorsquune lecture manque ;
- matrice pipeline machine-readable ;
- enregistrement du décodeur et des trois matérialiseurs metadata spécialisés dans le replay générique desktop pour le futur test `demo_backfill` ;
- nomenclature interne `metadata_*`, `spl_*` et `solana_*` alignée sur les domaines de `kb-lib` ;
- aucune modification de `kb-pipeline-demo-scenarios` ni de `demo_execution_metadata`.
- audit consigné dans `docs/audits/V0_4_8_PRE_008_METADATA_SOLANA_PROGRAM_PIPELINE_AUDIT.md`.
- première campagne historique : 200 transactions importées, 582 instructions rejouées et 132 instructions `ProgM6…` reconnues ; toutes ont été rejetées à tort parce que le décodeur comparait les privilèges globaux du message par égalité avec les minima de linstruction ;
- `pre.008-fix-002` remplace cette égalité par une validation de minima et ajoute une régression sur les neuf instructions ; la campagne des 132 entrées doit être rejouée avant douvrir `pre.009`.
### `0.4.8-pre.009` — campagne Devnet Solana Program Metadata ### `0.4.8-pre.009` — campagne Devnet et desktop Solana Program Metadata
- fixture Rust native ; - fixture Rust native et comptes préfinancés ;
- scénarios réutilisables ; - scénarios réutilisables dans `kb-pipeline-demo-scenarios` ;
- appels RPC de simulation, soumission, confirmation, lectures avant/après et replay ;
- adaptation du sous-panneau `ProgM6…` de `demo_execution_metadata` pour consommer les runners réutilisables ;
- validation historique préalable via `demo_backfill`, Core extraction et decode replay sur un échantillon de signatures ;
- preuves RPC et rapport intermédiaire. - preuves RPC et rapport intermédiaire.
### `0.4.8-pre.010` — complétude Token-2022 Token Metadata ### `0.4.8-pre.010` — complétude Token-2022 Token Metadata
- implémentation des écarts confirmés de `Initialize`, `UpdateField`, `RemoveKey`, `UpdateAuthority` et `Emit` ; - fermeture des écarts dintents/builders/exécution confirmés pour `UpdateAuthority` et `Emit`, après réaudit des cinq instructions `Initialize`, `UpdateField`, `RemoveKey`, `UpdateAuthority` et `Emit` ;
- conservation du matérialiseur détat dédié `metadata/token_2022/` pour les extensions `TokenMetadata`, `TokenGroup` et `TokenGroupMember` ; - conservation des snapshots dédiés `metadata/token_2022/` pour `TokenMetadata`, `TokenGroup` et `TokenGroupMember` ;
- ajout dune matérialisation explicite des cinq observations dinstructions `spl-token-metadata-interface`, actuellement absente ; - validation de la matérialisation instructionnelle spécialisée déjà ajoutée en `pre.008` pour les interfaces Token Metadata et Token Group ;
- maintien de `InitializeMetadataPointer` et `UpdateMetadataPointer` dans le matérialiseur dadministration qui en possède déjà les faits ; - maintien de `InitializeMetadataPointer` et `UpdateMetadataPointer` dans le matérialiseur dadministration qui en possède déjà les faits ;
- tests synthétiques et stateful. - tests synthétiques et stateful.
@@ -299,11 +313,12 @@ Travaux parallèles intégrés : les fonctions de matérialisation Metaplex exis
- fixture mint ; - fixture mint ;
- simulations, soumissions sûres, confirmations, postconditions, replay et matérialisation. - simulations, soumissions sûres, confirmations, postconditions, replay et matérialisation.
### `0.4.8-pre.012` — desktop metadata ### `0.4.8-pre.012` — finalisation desktop metadata
- accordéons ou sous-panneaux séparés pour `ProgM6…`, Token-2022 et Metaplex ; - compléter le sous-panneau Token-2022 après les travaux de `pre.010` et `pre.011` ;
- réutilisation du JsonViewer commun pour les résultats intégralement JSON ; - conserver des accordéons ou sous-panneaux séparés pour `ProgM6…`, Token-2022 et Metaplex ;
- pparation, simulation, soumission, lectures et preuves ; -utiliser le JsonViewer commun pour les résultats intégralement JSON ;
- réconcilier préparation, simulation, soumission, lectures et preuves des trois domaines ;
- aucun bouton de fetch URI. - aucun bouton de fetch URI.
### `0.4.8-pre.013` — stockage, matrices et réconciliation transversale ### `0.4.8-pre.013` — stockage, matrices et réconciliation transversale

View File

@@ -1,8 +1,15 @@
<!-- file: kb-app-demo-desktop/CHANGELOG.md --> <!-- file: kb-app-demo-desktop/CHANGELOG.md -->
<!-- version: 25 --> <!-- version: 27 -->
# CHANGELOG — kb-app-demo-desktop # CHANGELOG — kb-app-demo-desktop
## 0.4.8-pre.008
- enregistre `metadata.solana_program_metadata` dans le replay générique afin que les signatures hydratées par `demo_backfill` puissent être décodées ;
- enregistre les trois matérialiseurs spécialisés `MtMetadataMetaplexTokenMetadataMaterializer`, `MtMetadataSolanaProgramMaterializer` et `MtMetadataToken2022Materializer` dans le replay générique ;
- vérifie par test quune observation représentative de chaque domaine metadata possède exactement un matérialiseur runtime ;
- ne modifie ni `demo_execution_metadata`, ni les scénarios Devnet, réservés à `0.4.8-pre.009`.
## 0.4.8-pre.001 ## 0.4.8-pre.001
- aligne les versions Tauri et frontend sur `0.4.8` pour ouvrir la nouvelle version fonctionnelle ; - aligne les versions Tauri et frontend sur `0.4.8` pour ouvrir la nouvelle version fonctionnelle ;

View File

@@ -1,5 +1,5 @@
<!-- file: kb-app-demo-desktop/README.md --> <!-- file: kb-app-demo-desktop/README.md -->
<!-- version: 7 --> <!-- version: 8 -->
# kb-app-demo-desktop # kb-app-demo-desktop
@@ -10,7 +10,7 @@
- configuration et diagnostics de logging ; - configuration et diagnostics de logging ;
- transport HTTP JSON-RPC et WebSocket ; - transport HTTP JSON-RPC et WebSocket ;
- diagnostics PostgreSQL ; - diagnostics PostgreSQL ;
- backfill, extraction Core et replay ; - backfill, extraction Core et replay, avec enregistrement générique de Solana Program Metadata ;
- démonstrations Solana Core, SPL Memo, SPL Token, ATA et Token-2022 ; - démonstrations Solana Core, SPL Memo, SPL Token, ATA et Token-2022 ;
- panneau Metadata avec scénarios synthétiques, préparation du profil et de la base, intents typés, simulation RPC Devnet réelle et soumission explicitement confirmée ; - panneau Metadata avec scénarios synthétiques, préparation du profil et de la base, intents typés, simulation RPC Devnet réelle et soumission explicitement confirmée ;
- progression, annulation et restauration de létat des fenêtres. - progression, annulation et restauration de létat des fenêtres.

View File

@@ -1,5 +1,5 @@
<!-- file: kb-app-demo-desktop/USAGE.md --> <!-- file: kb-app-demo-desktop/USAGE.md -->
<!-- version: 5 --> <!-- version: 6 -->
# Utilisation de kb-app-demo-desktop # Utilisation de kb-app-demo-desktop
@@ -151,6 +151,17 @@ console.log(options, summary);
Une campagne longue peut être annulée avec `demo_core_extraction_cancel`. Une campagne longue peut être annulée avec `demo_core_extraction_cancel`.
### Validation historique Solana Program Metadata
Avant les scénarios Devnet, utiliser les fenêtres généralistes dans cet ordre :
1. `demo_backfill` avec un échantillon borné de signatures confirmées contenant `ProgM6JCCvbYkfKqJYHePx4xxSUSqJp7rh8Lyv7nk7S` ;
2. extraction Core sur les mêmes signatures ;
3. decode replay avec le Program ID `ProgM6…`, le décodeur `metadata.solana_program_metadata` et la matérialisation activée ;
4. lecture des événements du processor `materializer.metadata.solana_program_metadata`.
Ce parcours ne dépend pas de `kb-pipeline-demo-scenarios`. La fenêtre `demo_execution_metadata` et les scénarios Devnet `ProgM6…` seront ajoutés séparément dans `0.4.8-pre.009`.
### Decode replay ### Decode replay
```typescript ```typescript

View File

@@ -1,5 +1,5 @@
// file: kb-app-demo-desktop/src/demo_decode_replay.rs // file: kb-app-demo-desktop/src/demo_decode_replay.rs
// version: 37 // version: 39
//! Tauri commands and UI payloads for contextual instruction decode replay. //! Tauri commands and UI payloads for contextual instruction decode replay.
@@ -662,10 +662,13 @@ fn available_materializers() -> std::vec::Vec<std::sync::Arc<dyn kb_lib::MtApiEv
std::sync::Arc::new(kb_lib::MtComplianceAuditMaterializer), std::sync::Arc::new(kb_lib::MtComplianceAuditMaterializer),
std::sync::Arc::new(kb_lib::MtFeesMaterializer), std::sync::Arc::new(kb_lib::MtFeesMaterializer),
std::sync::Arc::new(kb_lib::MtLifecycleMaterializer), std::sync::Arc::new(kb_lib::MtLifecycleMaterializer),
std::sync::Arc::new(kb_lib::MtMetadataMetaplexTokenMetadataMaterializer),
std::sync::Arc::new(kb_lib::MtMetadataSolanaProgramMaterializer),
std::sync::Arc::new(kb_lib::MtMetadataToken2022Materializer),
std::sync::Arc::new(kb_lib::MtStakingMaterializer), std::sync::Arc::new(kb_lib::MtStakingMaterializer),
std::sync::Arc::new(kb_lib::MtTokenAccountsMaterializer), std::sync::Arc::new(kb_lib::MtTokenAccountsMaterializer),
std::sync::Arc::new(kb_lib::MtRiskMaterializer), std::sync::Arc::new(kb_lib::MtRiskMaterializer),
std::sync::Arc::new(kb_lib::MtTransactionAnnotationMaterializer,), std::sync::Arc::new(kb_lib::MtTransactionAnnotationMaterializer),
]; ];
} }
@@ -673,7 +676,8 @@ fn available_decoders() -> std::vec::Vec<std::sync::Arc<dyn kb_lib::DcApiInstruc
return std::vec![ return std::vec![
std::sync::Arc::new(kb_lib::DcSolanaCoreDecoder), std::sync::Arc::new(kb_lib::DcSolanaCoreDecoder),
std::sync::Arc::new(kb_lib::DcMetadataMetaplexTokenMetadataDecoder), std::sync::Arc::new(kb_lib::DcMetadataMetaplexTokenMetadataDecoder),
std::sync::Arc::new(kb_lib::DcSplAssociatedTokenAccountDecoder,), std::sync::Arc::new(kb_lib::DcMetadataSolanaProgramMetadataDecoder),
std::sync::Arc::new(kb_lib::DcSplAssociatedTokenAccountDecoder),
std::sync::Arc::new(kb_lib::DcSplElgamalRegistryDecoder), std::sync::Arc::new(kb_lib::DcSplElgamalRegistryDecoder),
std::sync::Arc::new(kb_lib::DcSplMemoDecoder), std::sync::Arc::new(kb_lib::DcSplMemoDecoder),
std::sync::Arc::new(kb_lib::DcSplTokenDecoder), std::sync::Arc::new(kb_lib::DcSplTokenDecoder),
@@ -1020,10 +1024,47 @@ mod tests {
}; };
} }
fn metadata_observation(
program_id: &str,
surface: &str,
entry: &str,
) -> kb_lib::DcApiDecodedObservation {
return kb_lib::DcApiDecodedObservation {
event_key: format!("{surface}:{entry}:0"),
event: kb_lib::MdDecodedProtocolEvent {
signature: kb_lib::MdSignature("signature".to_string()),
slot: kb_lib::MdSlot(1),
instruction_path: kb_lib::MdInstructionPath("0".to_string()),
program_id: kb_lib::MdProgramId(program_id.to_string()),
protocol_code: kb_lib::MdProtocolCode(surface.to_string()),
surface_code: kb_lib::MdSurfaceCode(surface.to_string()),
event_code: kb_lib::MdEventCode(format!("{surface}.{entry}")),
event_name: kb_lib::MdEventName(entry.to_string()),
event_family: kb_lib::MdEventFamily::Metadata,
source_kind: kb_lib::MdEventSourceKind::Instruction,
confidence: kb_lib::MdDecoderConfidence::ManualExact,
},
payload_json: serde_json::json!({
"programId": program_id,
"instruction": entry,
"parameters": {},
"accounts": []
}),
transaction_failed: false,
transaction_error: std::option::Option::None,
observation_committed: true,
proof: kb_lib::DcApiDecoderProof {
kind: kb_lib::DcApiDecoderProofKind::Manual,
confidence: kb_lib::MdDecoderConfidence::ManualExact,
evidence: std::vec!["fixture".to_string()],
},
};
}
#[test] #[test]
fn native_memo_token_2022_elgamal_classic_token_and_ata_decoders_are_registered() { fn current_core_spl_and_metadata_decoders_are_registered() {
let decoders = super::available_decoders(); let decoders = super::available_decoders();
assert_eq!(decoders.len(), 7); assert_eq!(decoders.len(), 8);
let mut names = decoders let mut names = decoders
.iter() .iter()
.map(|decoder| return decoder.identity().name) .map(|decoder| return decoder.identity().name)
@@ -1031,6 +1072,7 @@ mod tests {
names.sort(); names.sort();
let mut expected_names = std::vec![ let mut expected_names = std::vec![
"metadata.metaplex_token_metadata".to_string(), "metadata.metaplex_token_metadata".to_string(),
"metadata.solana_program_metadata".to_string(),
"solana.core".to_string(), "solana.core".to_string(),
"spl.associated_token_account".to_string(), "spl.associated_token_account".to_string(),
"spl.elgamal_registry".to_string(), "spl.elgamal_registry".to_string(),
@@ -1045,7 +1087,7 @@ mod tests {
#[test] #[test]
fn runtime_materializer_registry_is_complete_for_current_instructional_surfaces() { fn runtime_materializer_registry_is_complete_for_current_instructional_surfaces() {
let materializers = super::available_materializers(); let materializers = super::available_materializers();
assert_eq!(materializers.len(), 8); assert_eq!(materializers.len(), 11);
let names = materializers let names = materializers
.iter() .iter()
.map(|materializer| return materializer.identity().name) .map(|materializer| return materializer.identity().name)
@@ -1057,6 +1099,9 @@ mod tests {
"materializer.compliance.audit".to_string(), "materializer.compliance.audit".to_string(),
"materializer.fees".to_string(), "materializer.fees".to_string(),
"materializer.lifecycle".to_string(), "materializer.lifecycle".to_string(),
"materializer.metadata.metaplex_token_metadata".to_string(),
"materializer.metadata.solana_program_metadata".to_string(),
"materializer.metadata.token_2022".to_string(),
"materializer.staking".to_string(), "materializer.staking".to_string(),
"materializer.token.accounts".to_string(), "materializer.token.accounts".to_string(),
"materializer.risk".to_string(), "materializer.risk".to_string(),
@@ -1065,6 +1110,38 @@ mod tests {
); );
} }
#[test]
fn metadata_materializer_ownership_is_exact_in_the_runtime_registry() {
for (program_id, surface, entry, expected) in [
(
kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID,
"metadata.metaplex_token_metadata",
"create_metadata_account_v3",
"materializer.metadata.metaplex_token_metadata",
),
(
kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID,
"metadata.solana_program_metadata",
"initialize",
"materializer.metadata.solana_program_metadata",
),
(
kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
"spl.token_2022",
"initialize_token_metadata",
"materializer.metadata.token_2022",
),
] {
let observation = metadata_observation(program_id, surface, entry);
let owners = super::available_materializers()
.iter()
.filter(|materializer| return materializer.accepts_observation(&observation))
.map(|materializer| return materializer.identity().name)
.collect::<std::vec::Vec<_>>();
assert_eq!(owners, std::vec![expected.to_string()]);
}
}
#[test] #[test]
fn ata_materializer_ownership_is_exact_in_the_runtime_registry() { fn ata_materializer_ownership_is_exact_in_the_runtime_registry() {
for (entry, expected) in [ for (entry, expected) in [

View File

@@ -1,8 +1,19 @@
<!-- file: kb-lib/CHANGELOG.md --> <!-- file: kb-lib/CHANGELOG.md -->
<!-- version: 28 --> <!-- version: 30 -->
# CHANGELOG — kb-lib # CHANGELOG — kb-lib
## 0.4.8-pre.008 — matérialiseurs metadata spécialisés
- corrige le décodeur Solana Program Metadata pour interpréter les flags `signer` et `writable` du replay comme des privilèges globaux de message : les privilèges requis sont désormais des minima, et les sur-ensembles légitimes dus au fee payer ou à dautres instructions ne sont plus rejetés ;
- ajoute une régression couvrant les neuf instructions avec une autorité signer et writable au niveau du message, tout en conservant le refus des privilèges requis manquants ;
- corrige la déclaration Rust de `MT_METADATA_METAPLEX_TOKEN_METADATA_ACCEPTED_FAMILIES` livrée dans `pre.008-fix-001`.
- remplace le type ambigu `MtSolanaProgramMetadataMaterializer` par `MtMetadataSolanaProgramMaterializer` et retire le wrapper générique `MtMetadataMaterializer` ;
- ajoute `MtMetadataMetaplexTokenMetadataMaterializer` et `MtMetadataToken2022Materializer`, chacun avec ses deux contrats, son identité, son tracing target et un ownership instructionnel exact ;
- conserve séparément les APIs de snapshots Metaplex, Solana Program Metadata et Token-2022, sans confondre fait dinstruction et état autoritatif ;
- classe les neuf instructions incorporées Token Metadata et Token Group sous la famille `Metadata`, afin quelles soient possédées par le matérialiseur metadata spécialisé plutôt que par un composant dadministration générique ;
- étend les tests de façade publique et les contrats dinventaire aux trois matérialiseurs metadata spécialisés.
## 0.4.8-pre.007 — exécuteur Solana Program Metadata et structure Metaplex ## 0.4.8-pre.007 — exécuteur Solana Program Metadata et structure Metaplex
- ajoute `ExMetadataSolanaProgramMetadataExecutor`, les intents typés, les helpers de PDA et les builders wire exacts des neuf instructions stables `0..8` ; - ajoute `ExMetadataSolanaProgramMetadataExecutor`, les intents typés, les helpers de PDA et les builders wire exacts des neuf instructions stables `0..8` ;
@@ -24,9 +35,9 @@
- remplace les implémentations génériques `core.rs` par des `materializer.rs`, transforme les fichiers parents en façades pures et déplace toutes les familles, surfaces, tables et identités vers les `constants.rs` réexportés jusquà la racine de crate ; - remplace les implémentations génériques `core.rs` par des `materializer.rs`, transforme les fichiers parents en façades pures et déplace toutes les familles, surfaces, tables et identités vers les `constants.rs` réexportés jusquà la racine de crate ;
- rend les 14 matérialisateurs réservés réellement neutres sur les deux traits : famille vide constante, refus legacy, liste dévénements vide et résultat contextuel `Ignored` ; - rend les 14 matérialisateurs réservés réellement neutres sur les deux traits : famille vide constante, refus legacy, liste dévénements vide et résultat contextuel `Ignored` ;
- aligne les routes de logging dexemple sur `compliance.audit`, `transaction.annotations` et `token.accounts`, et ajoute des audits mécaniques empêchant la réintroduction de noms, constantes ou façades non conformes ; - aligne les routes de logging dexemple sur `compliance.audit`, `transaction.annotations` et `token.accounts`, et ajoute des audits mécaniques empêchant la réintroduction de noms, constantes ou façades non conformes ;
- conserve `MtMetadataMaterializer` comme wrapper de compatibilité du composant canonique `MtSolanaProgramMetadataMaterializer`, sans second `processorName` ni second propriétaire de faits. - prépare la spécialisation complète des matérialiseurs metadata, finalisée en `0.4.8-pre.008` sans wrapper générique ambigu.
- corrige les warnings de contrats opérationnels en ajoutant des événements `tracing` réels aux matérialisations `fees`, Metaplex Token Metadata et metadata Token-2022, avec `component_name`, `processor_name`, statut, identité de compte ou dinstruction et nombre de sorties ; - corrige les warnings de contrats opérationnels en ajoutant des événements `tracing` réels aux matérialisations `fees`, Metaplex Token Metadata et metadata Token-2022, avec `component_name`, `processor_name`, statut, identité de compte ou dinstruction et nombre de sorties ;
- consomme désormais lidentité runtime `MT_METADATA_TOKEN_2022_COMPONENT_NAME` dans limplémentation state-only au lieu de la limiter aux réexports, et étend les audits pour refuser tout tracing target opérationnel déclaré mais inutilisé ainsi que toute identité de composant absente de son implémentation. - consomme désormais lidentité runtime `MT_METADATA_TOKEN_2022_COMPONENT_NAME` dans les opérations de snapshot, puis étend les audits pour refuser tout tracing target opérationnel déclaré mais inutilisé ainsi que toute identité de composant absente de son implémentation.
## 0.4.8-pre.006 — matérialisation Solana Program Metadata ## 0.4.8-pre.006 — matérialisation Solana Program Metadata

View File

@@ -1,5 +1,5 @@
<!-- file: kb-lib/README.md --> <!-- file: kb-lib/README.md -->
<!-- version: 25 --> <!-- version: 26 -->
# kb-lib # kb-lib
@@ -48,7 +48,7 @@ Larbre `src/materializer` suit un contrat homogène :
- une identité runtime `kb-lib.materializer.*` distincte du `processorName` persisté `materializer.*` ; - une identité runtime `kb-lib.materializer.*` distincte du `processorName` persisté `materializer.*` ;
- des coquilles réservées réellement inactives sur les deux contrats legacy et contextuel. - des coquilles réservées réellement inactives sur les deux contrats legacy et contextuel.
Linventaire audité comprend 25 composants nommés : neuf matérialisateurs actifs, quatorze coquilles réservées et deux composants metadata state-only. Le détail est consigné dans [laudit de convention des matérialisateurs](../docs/audits/V0_4_8_PRE_007_MATERIALIZER_CONVENTION_AUDIT.md). Linventaire audité comprend 25 composants nommés : onze matérialisateurs actifs et quatorze coquilles réservées. Les composants metadata Metaplex Token Metadata, Solana Program Metadata et Token-2022 possèdent chacun un type spécialisé, une identité propre et leurs APIs de snapshots lorsque leur état on-chain lexige. Le détail est consigné dans [laudit de convention des matérialisateurs](../docs/audits/V0_4_8_PRE_007_MATERIALIZER_CONVENTION_AUDIT.md).
## Responsabilités ## Responsabilités

View File

@@ -1,5 +1,5 @@
<!-- file: kb-lib/USAGE.md --> <!-- file: kb-lib/USAGE.md -->
<!-- version: 13 --> <!-- version: 14 -->
# Utilisation de kb-lib # Utilisation de kb-lib
@@ -153,10 +153,16 @@ println!("status={:?}", result.status);
Le tag `5` est toujours publié comme `trim`. Son ancien nom pré-stable `withdraw_excess_lamports` est conservé uniquement comme provenance, car aucune release stable ne lexpose comme instruction distincte. Les URL et références externes restent des données on-chain ; le décodeur ne réalise aucun fetch. Le tag `5` est toujours publié comme `trim`. Son ancien nom pré-stable `withdraw_excess_lamports` est conservé uniquement comme provenance, car aucune release stable ne lexpose comme instruction distincte. Les URL et références externes restent des données on-chain ; le décodeur ne réalise aucun fetch.
Le matérialiseur `MtMetadataMaterializer` accepte les neuf observations dinstructions de la surface exacte `metadata.solana_program_metadata`. Il refuse les transactions échouées et produit un fait de mutation par instruction. Ces faits ne remplacent pas un snapshot de compte : létat final doit être matérialisé depuis la lecture post-exécution. Les trois domaines metadata disposent de matérialiseurs instructionnels spécialisés :
- `MtMetadataMetaplexTokenMetadataMaterializer` pour les observations Metaplex de famille metadata ;
- `MtMetadataSolanaProgramMaterializer` pour les neuf instructions stables de `ProgM6…` ;
- `MtMetadataToken2022Materializer` pour les instructions incorporées Token Metadata et Token Group de Token-2022.
Chaque type possède son propre `componentName`, son propre `processorName`, ses familles acceptées et un ownership exact. Les transactions échouées sont refusées. Les faits dinstructions ne remplacent jamais les snapshots autoritatifs lus après exécution.
```rust ```rust
let materializer = kb_lib::MtMetadataMaterializer; let materializer = kb_lib::MtMetadataSolanaProgramMaterializer;
if kb_lib::MtApiEventMaterializer::accepts_observation(&materializer, &observation) { if kb_lib::MtApiEventMaterializer::accepts_observation(&materializer, &observation) {
let result = kb_lib::MtApiEventMaterializer::materialize(&materializer, &observation); let result = kb_lib::MtApiEventMaterializer::materialize(&materializer, &observation);
println!("materialization status={:?}", result.status); println!("materialization status={:?}", result.status);

View File

@@ -1,5 +1,5 @@
// file: kb-lib/src/decoder/metadata/solana_program_metadata/instruction.rs // file: kb-lib/src/decoder/metadata/solana_program_metadata/instruction.rs
// version: 4 // version: 5
//! Exact contextual instruction decoding for Solana Program Metadata. //! Exact contextual instruction decoding for Solana Program Metadata.
@@ -627,9 +627,15 @@ fn validate_accounts(
)); ));
}, },
}; };
if account.signer != contract.signer || account.writable != contract.writable { if contract.signer && !account.signer {
return std::result::Result::Err(format!( return std::result::Result::Err(format!(
"{} account {position} ({}) has invalid signer/writable flags", "{} account {position} ({}) is missing the required signer privilege",
parsed.code, contract.role
));
}
if contract.writable && !account.writable {
return std::result::Result::Err(format!(
"{} account {position} ({}) is missing the required writable privilege",
parsed.code, contract.role parsed.code, contract.role
)); ));
} }
@@ -1160,6 +1166,115 @@ mod tests {
} }
} }
#[test]
fn transaction_wide_privilege_supersets_are_accepted() {
let cases = [
(
vec![0, 4, 0, 0, 0, 1, 2],
vec![
("buffer", false, true),
("authority", true, true),
(placeholder(), false, false),
],
"write",
),
(
[vec![1], vec![0; 16], vec![1, 0, 1, 0], vec![1]].concat(),
vec![
("metadata", false, true),
("authority", true, true),
("program", false, false),
(placeholder(), false, false),
(placeholder(), false, false),
],
"initialize",
),
(
vec![2, 0],
vec![
("account", false, true),
("authority", true, true),
(placeholder(), false, false),
(placeholder(), false, false),
],
"set_authority",
),
(
vec![3, 1, 0, 1],
vec![
("metadata", false, true),
("authority", true, true),
(placeholder(), false, true),
(placeholder(), false, false),
(placeholder(), false, false),
],
"set_data",
),
(
vec![4],
vec![
("metadata", false, true),
("authority", true, true),
(placeholder(), false, false),
(placeholder(), false, false),
],
"set_immutable",
),
(
vec![5],
vec![
("account", false, true),
("authority", true, true),
(placeholder(), false, false),
(placeholder(), false, false),
("destination", false, true),
("SysvarRent111111111111111111111111111111111", false, false),
],
"trim",
),
(
vec![6],
vec![
("account", false, true),
("authority", true, true),
(placeholder(), false, false),
(placeholder(), false, false),
("destination", false, true),
],
"close",
),
(
[vec![7], vec![8; 16]].concat(),
vec![
("buffer", false, true),
("authority", true, true),
("program", false, false),
(placeholder(), false, false),
(placeholder(), false, false),
],
"allocate",
),
(
vec![8, 32, 0],
vec![
("account", false, true),
("authority", true, true),
(placeholder(), false, false),
(placeholder(), false, false),
],
"extend",
),
];
for (payload, accounts, expected) in cases {
let result = decode(payload.as_slice(), accounts.as_slice());
assert_eq!(result.status, crate::DcApiDecoderOutcomeStatus::Decoded, "{expected}");
assert_eq!(
result.recognized_entry_code.as_deref(),
std::option::Option::Some(expected)
);
}
}
#[test] #[test]
fn source_selection_and_account_flags_fail_closed() { fn source_selection_and_account_flags_fail_closed() {
let invalid_write = decode( let invalid_write = decode(
@@ -1199,6 +1314,16 @@ mod tests {
], ],
); );
assert_eq!(invalid_flags.status, crate::DcApiDecoderOutcomeStatus::Failed); assert_eq!(invalid_flags.status, crate::DcApiDecoderOutcomeStatus::Failed);
let missing_signer = decode(
&[8, 1, 0],
&[
("account", false, true),
("authority", false, true),
(placeholder(), false, false),
(placeholder(), false, false),
],
);
assert_eq!(missing_signer.status, crate::DcApiDecoderOutcomeStatus::Failed);
} }
#[test] #[test]

View File

@@ -1,5 +1,5 @@
// file: kb-lib/src/decoder/spl/token_2022/wire.rs // file: kb-lib/src/decoder/spl/token_2022/wire.rs
// version: 2 // version: 3
//! Bounded Token-2022 wire, account and authority decoding. //! Bounded Token-2022 wire, account and authority decoding.
@@ -2532,10 +2532,7 @@ fn event_family(code: &str) -> crate::MdEventFamily {
| "initialize_multisig2" | "initialize_multisig2"
| "initialize_permanent_delegate" | "initialize_permanent_delegate"
| "initialize_interest_bearing_mint" | "initialize_interest_bearing_mint"
| "update_interest_bearing_rate" | "update_interest_bearing_rate" => crate::MdEventFamily::Admin,
| "update_token_metadata_authority"
| "update_token_group_authority"
| "update_token_group_max_size" => crate::MdEventFamily::Admin,
"initialize_mint" "initialize_mint"
| "initialize_mint2" | "initialize_mint2"
| "mint_to" | "mint_to"
@@ -2564,8 +2561,11 @@ fn event_family(code: &str) -> crate::MdEventFamily {
"initialize_token_metadata" "initialize_token_metadata"
| "update_token_metadata_field" | "update_token_metadata_field"
| "remove_token_metadata_key" | "remove_token_metadata_key"
| "update_token_metadata_authority"
| "emit_token_metadata" | "emit_token_metadata"
| "initialize_token_group" | "initialize_token_group"
| "update_token_group_max_size"
| "update_token_group_authority"
| "initialize_token_group_member" => crate::MdEventFamily::Metadata, | "initialize_token_group_member" => crate::MdEventFamily::Metadata,
"batch" | "get_account_data_size" | "amount_to_ui_amount" | "ui_amount_to_amount" => { "batch" | "get_account_data_size" | "amount_to_ui_amount" | "ui_amount_to_amount" => {
crate::MdEventFamily::Audit crate::MdEventFamily::Audit
@@ -2608,3 +2608,23 @@ fn failed(
}], }],
}; };
} }
#[cfg(test)]
mod metadata_family_tests {
#[test]
fn every_embedded_metadata_and_group_instruction_uses_metadata_family() {
for operation in [
"initialize_token_metadata",
"update_token_metadata_field",
"remove_token_metadata_key",
"update_token_metadata_authority",
"emit_token_metadata",
"initialize_token_group",
"update_token_group_max_size",
"update_token_group_authority",
"initialize_token_group_member",
] {
assert_eq!(super::event_family(operation), crate::MdEventFamily::Metadata);
}
}
}

View File

@@ -1,5 +1,5 @@
// file: kb-lib/src/lib.rs // file: kb-lib/src/lib.rs
// version: 36 // version: 37
//! Consolidated decoder, executor, materializer and shared model library. //! Consolidated decoder, executor, materializer and shared model library.
#![warn(missing_docs)] #![warn(missing_docs)]
@@ -1439,8 +1439,12 @@ pub use self::materializer::MtLifecycleMaterializer;
pub use self::materializer::MtLiquidityMaterializer; pub use self::materializer::MtLiquidityMaterializer;
/// Exposes the legacy common business materializer trait. /// Exposes the legacy common business materializer trait.
pub use self::materializer::MtMaterializer; pub use self::materializer::MtMaterializer;
/// Compatibility wrapper for the Solana Program Metadata materializer. /// Metaplex Token Metadata instruction materializer.
pub use self::materializer::MtMetadataMaterializer; pub use self::materializer::MtMetadataMetaplexTokenMetadataMaterializer;
/// Solana Program Metadata instruction materializer.
pub use self::materializer::MtMetadataSolanaProgramMaterializer;
/// SPL Token-2022 embedded metadata and group instruction materializer.
pub use self::materializer::MtMetadataToken2022Materializer;
/// Reserved NFT materializer. /// Reserved NFT materializer.
pub use self::materializer::MtNftMaterializer; pub use self::materializer::MtNftMaterializer;
/// Reserved oracle materializer. /// Reserved oracle materializer.
@@ -1457,8 +1461,6 @@ pub use self::materializer::MtRewardsMaterializer;
pub use self::materializer::MtRiskMaterializer; pub use self::materializer::MtRiskMaterializer;
/// Reserved routing materializer. /// Reserved routing materializer.
pub use self::materializer::MtRoutingMaterializer; pub use self::materializer::MtRoutingMaterializer;
/// Stable Solana Program Metadata instruction materializer.
pub use self::materializer::MtSolanaProgramMetadataMaterializer;
/// Stable native stake and vote materializer. /// Stable native stake and vote materializer.
pub use self::materializer::MtStakingMaterializer; pub use self::materializer::MtStakingMaterializer;
/// Stable Token, ATA and Token-2022 account materializer. /// Stable Token, ATA and Token-2022 account materializer.
@@ -2250,22 +2252,28 @@ pub(crate) use self::materializer::MT_LIQUIDITY_ACCEPTED_FAMILIES;
pub(crate) use self::materializer::MT_LIQUIDITY_COMPONENT_NAME; pub(crate) use self::materializer::MT_LIQUIDITY_COMPONENT_NAME;
/// Crate-root access to `MT_LIQUIDITY_PROCESSOR_NAME`. /// Crate-root access to `MT_LIQUIDITY_PROCESSOR_NAME`.
pub(crate) use self::materializer::MT_LIQUIDITY_PROCESSOR_NAME; pub(crate) use self::materializer::MT_LIQUIDITY_PROCESSOR_NAME;
/// Crate-root access to `MT_METADATA_METAPLEX_TOKEN_METADATA_ACCEPTED_FAMILIES`.
pub(crate) use self::materializer::MT_METADATA_METAPLEX_TOKEN_METADATA_ACCEPTED_FAMILIES;
/// Crate-root access to `MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME`. /// Crate-root access to `MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME`.
pub(crate) use self::materializer::MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME; pub(crate) use self::materializer::MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME;
/// Crate-root access to `MT_METADATA_METAPLEX_TOKEN_METADATA_PROCESSOR_NAME`. /// Crate-root access to `MT_METADATA_METAPLEX_TOKEN_METADATA_PROCESSOR_NAME`.
pub(crate) use self::materializer::MT_METADATA_METAPLEX_TOKEN_METADATA_PROCESSOR_NAME; pub(crate) use self::materializer::MT_METADATA_METAPLEX_TOKEN_METADATA_PROCESSOR_NAME;
/// Crate-root access to `MT_METADATA_METAPLEX_TOKEN_METADATA_PROJECTION_VERSION`. /// Crate-root access to `MT_METADATA_METAPLEX_TOKEN_METADATA_PROJECTION_VERSION`.
pub(crate) use self::materializer::MT_METADATA_METAPLEX_TOKEN_METADATA_PROJECTION_VERSION; pub(crate) use self::materializer::MT_METADATA_METAPLEX_TOKEN_METADATA_PROJECTION_VERSION;
/// Crate-root access to `MT_METADATA_SOLANA_PROGRAM_METADATA_ACCEPTED_FAMILIES`. /// Crate-root access to `MT_METADATA_SOLANA_PROGRAM_ACCEPTED_FAMILIES`.
pub(crate) use self::materializer::MT_METADATA_SOLANA_PROGRAM_METADATA_ACCEPTED_FAMILIES; pub(crate) use self::materializer::MT_METADATA_SOLANA_PROGRAM_ACCEPTED_FAMILIES;
/// Crate-root access to `MT_METADATA_SOLANA_PROGRAM_METADATA_COMPONENT_NAME`. /// Crate-root access to `MT_METADATA_SOLANA_PROGRAM_COMPONENT_NAME`.
pub(crate) use self::materializer::MT_METADATA_SOLANA_PROGRAM_METADATA_COMPONENT_NAME; pub(crate) use self::materializer::MT_METADATA_SOLANA_PROGRAM_COMPONENT_NAME;
/// Crate-root access to `MT_METADATA_SOLANA_PROGRAM_METADATA_PROCESSOR_NAME`. /// Crate-root access to `MT_METADATA_SOLANA_PROGRAM_PROCESSOR_NAME`.
pub(crate) use self::materializer::MT_METADATA_SOLANA_PROGRAM_METADATA_PROCESSOR_NAME; pub(crate) use self::materializer::MT_METADATA_SOLANA_PROGRAM_PROCESSOR_NAME;
/// Crate-root access to `MT_METADATA_SOLANA_PROGRAM_METADATA_PROJECTION_VERSION`. /// Crate-root access to `MT_METADATA_SOLANA_PROGRAM_PROJECTION_VERSION`.
pub(crate) use self::materializer::MT_METADATA_SOLANA_PROGRAM_METADATA_PROJECTION_VERSION; pub(crate) use self::materializer::MT_METADATA_SOLANA_PROGRAM_PROJECTION_VERSION;
/// Crate-root access to `MT_METADATA_TOKEN_2022_ACCEPTED_FAMILIES`.
pub(crate) use self::materializer::MT_METADATA_TOKEN_2022_ACCEPTED_FAMILIES;
/// Crate-root access to `MT_METADATA_TOKEN_2022_COMPONENT_NAME`. /// Crate-root access to `MT_METADATA_TOKEN_2022_COMPONENT_NAME`.
pub(crate) use self::materializer::MT_METADATA_TOKEN_2022_COMPONENT_NAME; pub(crate) use self::materializer::MT_METADATA_TOKEN_2022_COMPONENT_NAME;
/// Crate-root access to `MT_METADATA_TOKEN_2022_INSTRUCTION_ENTRIES`.
pub(crate) use self::materializer::MT_METADATA_TOKEN_2022_INSTRUCTION_ENTRIES;
/// Crate-root access to `MT_METADATA_TOKEN_2022_PROCESSOR_NAME`. /// Crate-root access to `MT_METADATA_TOKEN_2022_PROCESSOR_NAME`.
pub(crate) use self::materializer::MT_METADATA_TOKEN_2022_PROCESSOR_NAME; pub(crate) use self::materializer::MT_METADATA_TOKEN_2022_PROCESSOR_NAME;
/// Crate-root access to `MT_METADATA_TOKEN_2022_PROJECTION_VERSION`. /// Crate-root access to `MT_METADATA_TOKEN_2022_PROJECTION_VERSION`.
@@ -2406,8 +2414,8 @@ pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_FEES;
pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_LIFECYCLE; pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_LIFECYCLE;
/// Crate-root access to `TRACING_TARGET_MATERIALIZER_METADATA_METAPLEX_TOKEN_METADATA`. /// Crate-root access to `TRACING_TARGET_MATERIALIZER_METADATA_METAPLEX_TOKEN_METADATA`.
pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_METADATA_METAPLEX_TOKEN_METADATA; pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_METADATA_METAPLEX_TOKEN_METADATA;
/// Crate-root access to `TRACING_TARGET_MATERIALIZER_METADATA_SOLANA_PROGRAM_METADATA`. /// Crate-root access to `TRACING_TARGET_MATERIALIZER_METADATA_SOLANA_PROGRAM`.
pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_METADATA_SOLANA_PROGRAM_METADATA; pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_METADATA_SOLANA_PROGRAM;
/// Crate-root access to `TRACING_TARGET_MATERIALIZER_METADATA_TOKEN_2022`. /// Crate-root access to `TRACING_TARGET_MATERIALIZER_METADATA_TOKEN_2022`.
pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_METADATA_TOKEN_2022; pub(crate) use self::materializer::TRACING_TARGET_MATERIALIZER_METADATA_TOKEN_2022;
/// Crate-root access to `TRACING_TARGET_MATERIALIZER_RISK`. /// Crate-root access to `TRACING_TARGET_MATERIALIZER_RISK`.

View File

@@ -1,5 +1,5 @@
// file: kb-lib/src/materializer.rs // file: kb-lib/src/materializer.rs
// version: 14 // version: 15
//! Consolidated materializer modules. //! Consolidated materializer modules.
@@ -73,10 +73,12 @@ pub use self::lending::MtLendingMaterializer;
pub use self::lifecycle::MtLifecycleMaterializer; pub use self::lifecycle::MtLifecycleMaterializer;
/// Reserved liquidity materializer. /// Reserved liquidity materializer.
pub use self::liquidity::MtLiquidityMaterializer; pub use self::liquidity::MtLiquidityMaterializer;
/// Compatibility wrapper for the Solana Program Metadata materializer. /// Metaplex Token Metadata instruction materializer.
pub use self::metadata::MtMetadataMaterializer; pub use self::metadata::MtMetadataMetaplexTokenMetadataMaterializer;
/// Stable Solana Program Metadata instruction materializer. /// Solana Program Metadata instruction materializer.
pub use self::metadata::MtSolanaProgramMetadataMaterializer; pub use self::metadata::MtMetadataSolanaProgramMaterializer;
/// SPL Token-2022 embedded metadata and group instruction materializer.
pub use self::metadata::MtMetadataToken2022Materializer;
/// Materializes one canonical non-Metadata Metaplex account snapshot. /// Materializes one canonical non-Metadata Metaplex account snapshot.
pub use self::metadata::materializer_metadata_materialize_metaplex_account_snapshot; pub use self::metadata::materializer_metadata_materialize_metaplex_account_snapshot;
/// Materializes one canonical Metaplex Metadata account snapshot. /// Materializes one canonical Metaplex Metadata account snapshot.
@@ -272,30 +274,36 @@ pub(crate) use self::liquidity::MT_LIQUIDITY_ACCEPTED_FAMILIES;
pub(crate) use self::liquidity::MT_LIQUIDITY_COMPONENT_NAME; pub(crate) use self::liquidity::MT_LIQUIDITY_COMPONENT_NAME;
/// Crate-visible `MT_LIQUIDITY_PROCESSOR_NAME` component constant. /// Crate-visible `MT_LIQUIDITY_PROCESSOR_NAME` component constant.
pub(crate) use self::liquidity::MT_LIQUIDITY_PROCESSOR_NAME; pub(crate) use self::liquidity::MT_LIQUIDITY_PROCESSOR_NAME;
/// Crate-visible `MT_METADATA_METAPLEX_TOKEN_METADATA_ACCEPTED_FAMILIES` component constant.
pub(crate) use self::metadata::MT_METADATA_METAPLEX_TOKEN_METADATA_ACCEPTED_FAMILIES;
/// Crate-visible `MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME` component constant. /// Crate-visible `MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME` component constant.
pub(crate) use self::metadata::MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME; pub(crate) use self::metadata::MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME;
/// Crate-visible `MT_METADATA_METAPLEX_TOKEN_METADATA_PROCESSOR_NAME` component constant. /// Crate-visible `MT_METADATA_METAPLEX_TOKEN_METADATA_PROCESSOR_NAME` component constant.
pub(crate) use self::metadata::MT_METADATA_METAPLEX_TOKEN_METADATA_PROCESSOR_NAME; pub(crate) use self::metadata::MT_METADATA_METAPLEX_TOKEN_METADATA_PROCESSOR_NAME;
/// Crate-visible `MT_METADATA_METAPLEX_TOKEN_METADATA_PROJECTION_VERSION` component constant. /// Crate-visible `MT_METADATA_METAPLEX_TOKEN_METADATA_PROJECTION_VERSION` component constant.
pub(crate) use self::metadata::MT_METADATA_METAPLEX_TOKEN_METADATA_PROJECTION_VERSION; pub(crate) use self::metadata::MT_METADATA_METAPLEX_TOKEN_METADATA_PROJECTION_VERSION;
/// Crate-visible `MT_METADATA_SOLANA_PROGRAM_METADATA_ACCEPTED_FAMILIES` component constant. /// Crate-visible `MT_METADATA_SOLANA_PROGRAM_ACCEPTED_FAMILIES` component constant.
pub(crate) use self::metadata::MT_METADATA_SOLANA_PROGRAM_METADATA_ACCEPTED_FAMILIES; pub(crate) use self::metadata::MT_METADATA_SOLANA_PROGRAM_ACCEPTED_FAMILIES;
/// Crate-visible `MT_METADATA_SOLANA_PROGRAM_METADATA_COMPONENT_NAME` component constant. /// Crate-visible `MT_METADATA_SOLANA_PROGRAM_COMPONENT_NAME` component constant.
pub(crate) use self::metadata::MT_METADATA_SOLANA_PROGRAM_METADATA_COMPONENT_NAME; pub(crate) use self::metadata::MT_METADATA_SOLANA_PROGRAM_COMPONENT_NAME;
/// Crate-visible `MT_METADATA_SOLANA_PROGRAM_METADATA_PROCESSOR_NAME` component constant. /// Crate-visible `MT_METADATA_SOLANA_PROGRAM_PROCESSOR_NAME` component constant.
pub(crate) use self::metadata::MT_METADATA_SOLANA_PROGRAM_METADATA_PROCESSOR_NAME; pub(crate) use self::metadata::MT_METADATA_SOLANA_PROGRAM_PROCESSOR_NAME;
/// Crate-visible `MT_METADATA_SOLANA_PROGRAM_METADATA_PROJECTION_VERSION` component constant. /// Crate-visible `MT_METADATA_SOLANA_PROGRAM_PROJECTION_VERSION` component constant.
pub(crate) use self::metadata::MT_METADATA_SOLANA_PROGRAM_METADATA_PROJECTION_VERSION; pub(crate) use self::metadata::MT_METADATA_SOLANA_PROGRAM_PROJECTION_VERSION;
/// Crate-visible `MT_METADATA_TOKEN_2022_ACCEPTED_FAMILIES` component constant.
pub(crate) use self::metadata::MT_METADATA_TOKEN_2022_ACCEPTED_FAMILIES;
/// Crate-visible `MT_METADATA_TOKEN_2022_COMPONENT_NAME` component constant. /// Crate-visible `MT_METADATA_TOKEN_2022_COMPONENT_NAME` component constant.
pub(crate) use self::metadata::MT_METADATA_TOKEN_2022_COMPONENT_NAME; pub(crate) use self::metadata::MT_METADATA_TOKEN_2022_COMPONENT_NAME;
/// Crate-visible `MT_METADATA_TOKEN_2022_INSTRUCTION_ENTRIES` component constant.
pub(crate) use self::metadata::MT_METADATA_TOKEN_2022_INSTRUCTION_ENTRIES;
/// Crate-visible `MT_METADATA_TOKEN_2022_PROCESSOR_NAME` component constant. /// Crate-visible `MT_METADATA_TOKEN_2022_PROCESSOR_NAME` component constant.
pub(crate) use self::metadata::MT_METADATA_TOKEN_2022_PROCESSOR_NAME; pub(crate) use self::metadata::MT_METADATA_TOKEN_2022_PROCESSOR_NAME;
/// Crate-visible `MT_METADATA_TOKEN_2022_PROJECTION_VERSION` component constant. /// Crate-visible `MT_METADATA_TOKEN_2022_PROJECTION_VERSION` component constant.
pub(crate) use self::metadata::MT_METADATA_TOKEN_2022_PROJECTION_VERSION; pub(crate) use self::metadata::MT_METADATA_TOKEN_2022_PROJECTION_VERSION;
/// Crate-visible `TRACING_TARGET_MATERIALIZER_METADATA_METAPLEX_TOKEN_METADATA` component constant. /// Crate-visible `TRACING_TARGET_MATERIALIZER_METADATA_METAPLEX_TOKEN_METADATA` component constant.
pub(crate) use self::metadata::TRACING_TARGET_MATERIALIZER_METADATA_METAPLEX_TOKEN_METADATA; pub(crate) use self::metadata::TRACING_TARGET_MATERIALIZER_METADATA_METAPLEX_TOKEN_METADATA;
/// Crate-visible `TRACING_TARGET_MATERIALIZER_METADATA_SOLANA_PROGRAM_METADATA` component constant. /// Crate-visible `TRACING_TARGET_MATERIALIZER_METADATA_SOLANA_PROGRAM` component constant.
pub(crate) use self::metadata::TRACING_TARGET_MATERIALIZER_METADATA_SOLANA_PROGRAM_METADATA; pub(crate) use self::metadata::TRACING_TARGET_MATERIALIZER_METADATA_SOLANA_PROGRAM;
/// Crate-visible `TRACING_TARGET_MATERIALIZER_METADATA_TOKEN_2022` component constant. /// Crate-visible `TRACING_TARGET_MATERIALIZER_METADATA_TOKEN_2022` component constant.
pub(crate) use self::metadata::TRACING_TARGET_MATERIALIZER_METADATA_TOKEN_2022; pub(crate) use self::metadata::TRACING_TARGET_MATERIALIZER_METADATA_TOKEN_2022;
/// Crate-visible `MT_NFT_ACCEPTED_FAMILIES` component constant. /// Crate-visible `MT_NFT_ACCEPTED_FAMILIES` component constant.
@@ -491,10 +499,22 @@ mod tests {
crate::MT_LIQUIDITY_PROCESSOR_NAME, crate::MT_LIQUIDITY_PROCESSOR_NAME,
), ),
( (
&crate::MtSolanaProgramMetadataMaterializer, &crate::MtMetadataMetaplexTokenMetadataMaterializer,
&crate::MtSolanaProgramMetadataMaterializer, &crate::MtMetadataMetaplexTokenMetadataMaterializer,
crate::MT_METADATA_SOLANA_PROGRAM_METADATA_COMPONENT_NAME, crate::MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME,
crate::MT_METADATA_SOLANA_PROGRAM_METADATA_PROCESSOR_NAME, crate::MT_METADATA_METAPLEX_TOKEN_METADATA_PROCESSOR_NAME,
),
(
&crate::MtMetadataSolanaProgramMaterializer,
&crate::MtMetadataSolanaProgramMaterializer,
crate::MT_METADATA_SOLANA_PROGRAM_COMPONENT_NAME,
crate::MT_METADATA_SOLANA_PROGRAM_PROCESSOR_NAME,
),
(
&crate::MtMetadataToken2022Materializer,
&crate::MtMetadataToken2022Materializer,
crate::MT_METADATA_TOKEN_2022_COMPONENT_NAME,
crate::MT_METADATA_TOKEN_2022_PROCESSOR_NAME,
), ),
( (
&crate::MtNftMaterializer, &crate::MtNftMaterializer,
@@ -583,22 +603,6 @@ mod tests {
]; ];
} }
fn state_only_materializer_contracts() -> &'static [(&'static str, &'static str, &'static str)]
{
return &[
(
crate::MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME,
crate::MT_METADATA_METAPLEX_TOKEN_METADATA_PROCESSOR_NAME,
crate::TRACING_TARGET_MATERIALIZER_METADATA_METAPLEX_TOKEN_METADATA,
),
(
crate::MT_METADATA_TOKEN_2022_COMPONENT_NAME,
crate::MT_METADATA_TOKEN_2022_PROCESSOR_NAME,
crate::TRACING_TARGET_MATERIALIZER_METADATA_TOKEN_2022,
),
];
}
#[test] #[test]
fn every_materializer_uses_the_component_and_processor_name_convention() { fn every_materializer_uses_the_component_and_processor_name_convention() {
for (legacy, contextual, component_name, processor_name) in materializer_contracts() { for (legacy, contextual, component_name, processor_name) in materializer_contracts() {
@@ -615,18 +619,6 @@ mod tests {
} }
} }
#[test]
fn state_only_materializers_keep_runtime_processor_and_tracing_identity_aligned() {
for (component_name, processor_name, tracing_target) in state_only_materializer_contracts()
{
assert_eq!(*tracing_target, *component_name);
assert_eq!(
component_name.strip_prefix("kb-lib."),
std::option::Option::Some(*processor_name)
);
}
}
#[test] #[test]
fn reserved_materializers_are_empty_shells() { fn reserved_materializers_are_empty_shells() {
let materializers: &[&dyn crate::MtApiEventMaterializer] = &[ let materializers: &[&dyn crate::MtApiEventMaterializer] = &[

View File

@@ -1,5 +1,5 @@
// file: kb-lib/src/materializer/metadata.rs // file: kb-lib/src/materializer/metadata.rs
// version: 8 // version: 9
//! `metadata` materializer family. //! `metadata` materializer family.
@@ -7,23 +7,27 @@ mod metaplex_token_metadata;
mod solana_program_metadata; mod solana_program_metadata;
mod token_2022; mod token_2022;
/// Metaplex Token Metadata instruction materializer.
pub use self::metaplex_token_metadata::MtMetadataMetaplexTokenMetadataMaterializer;
/// Materializes one canonical non-Metadata Metaplex account snapshot. /// Materializes one canonical non-Metadata Metaplex account snapshot.
pub use self::metaplex_token_metadata::materializer_metadata_materialize_metaplex_account_snapshot; pub use self::metaplex_token_metadata::materializer_metadata_materialize_metaplex_account_snapshot;
/// Materializes one canonical Metaplex Metadata account snapshot. /// Materializes one canonical Metaplex Metadata account snapshot.
pub use self::metaplex_token_metadata::materializer_metadata_materialize_metaplex_metadata_snapshot; pub use self::metaplex_token_metadata::materializer_metadata_materialize_metaplex_metadata_snapshot;
/// Compatibility materializer retaining the historical generic metadata type name. /// Solana Program Metadata instruction materializer.
pub use self::solana_program_metadata::MtMetadataMaterializer; pub use self::solana_program_metadata::MtMetadataSolanaProgramMaterializer;
/// Stable Solana Program Metadata instruction materializer.
pub use self::solana_program_metadata::MtSolanaProgramMetadataMaterializer;
/// Materializes one decoded Solana Program Metadata account snapshot. /// Materializes one decoded Solana Program Metadata account snapshot.
pub use self::solana_program_metadata::materializer_metadata_materialize_solana_program_metadata_account_snapshot; pub use self::solana_program_metadata::materializer_metadata_materialize_solana_program_metadata_account_snapshot;
/// Materializes one bounded Solana Program Metadata Buffer account snapshot. /// Materializes one bounded Solana Program Metadata Buffer account snapshot.
pub use self::solana_program_metadata::materializer_metadata_materialize_solana_program_metadata_buffer_snapshot; pub use self::solana_program_metadata::materializer_metadata_materialize_solana_program_metadata_buffer_snapshot;
/// Materializes one bounded Solana Program Metadata Metadata account snapshot. /// Materializes one bounded Solana Program Metadata Metadata account snapshot.
pub use self::solana_program_metadata::materializer_metadata_materialize_solana_program_metadata_metadata_snapshot; pub use self::solana_program_metadata::materializer_metadata_materialize_solana_program_metadata_metadata_snapshot;
/// SPL Token-2022 embedded metadata and group instruction materializer.
pub use self::token_2022::MtMetadataToken2022Materializer;
/// Materializes Token-2022 embedded metadata and group snapshots. /// Materializes Token-2022 embedded metadata and group snapshots.
pub use self::token_2022::materializer_metadata_materialize_token_2022_snapshot; pub use self::token_2022::materializer_metadata_materialize_token_2022_snapshot;
/// Event families accepted by the Metaplex Token Metadata instruction materializer.
pub(crate) use self::metaplex_token_metadata::MT_METADATA_METAPLEX_TOKEN_METADATA_ACCEPTED_FAMILIES;
/// Stable runtime component name for Metaplex Token Metadata projections. /// Stable runtime component name for Metaplex Token Metadata projections.
pub(crate) use self::metaplex_token_metadata::MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME; pub(crate) use self::metaplex_token_metadata::MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME;
/// Stable processor name for Metaplex Token Metadata projections. /// Stable processor name for Metaplex Token Metadata projections.
@@ -33,17 +37,21 @@ pub(crate) use self::metaplex_token_metadata::MT_METADATA_METAPLEX_TOKEN_METADAT
/// Canonical tracing target for Metaplex Token Metadata materialization. /// Canonical tracing target for Metaplex Token Metadata materialization.
pub(crate) use self::metaplex_token_metadata::TRACING_TARGET_MATERIALIZER_METADATA_METAPLEX_TOKEN_METADATA; pub(crate) use self::metaplex_token_metadata::TRACING_TARGET_MATERIALIZER_METADATA_METAPLEX_TOKEN_METADATA;
/// Event families accepted by the Solana Program Metadata instruction materializer. /// Event families accepted by the Solana Program Metadata instruction materializer.
pub(crate) use self::solana_program_metadata::MT_METADATA_SOLANA_PROGRAM_METADATA_ACCEPTED_FAMILIES; pub(crate) use self::solana_program_metadata::MT_METADATA_SOLANA_PROGRAM_ACCEPTED_FAMILIES;
/// Stable runtime component name for Solana Program Metadata projections. /// Stable runtime component name for Solana Program Metadata projections.
pub(crate) use self::solana_program_metadata::MT_METADATA_SOLANA_PROGRAM_METADATA_COMPONENT_NAME; pub(crate) use self::solana_program_metadata::MT_METADATA_SOLANA_PROGRAM_COMPONENT_NAME;
/// Stable processor name for Solana Program Metadata projections. /// Stable processor name for Solana Program Metadata projections.
pub(crate) use self::solana_program_metadata::MT_METADATA_SOLANA_PROGRAM_METADATA_PROCESSOR_NAME; pub(crate) use self::solana_program_metadata::MT_METADATA_SOLANA_PROGRAM_PROCESSOR_NAME;
/// Stable projection schema version for Solana Program Metadata outputs. /// Stable projection schema version for Solana Program Metadata outputs.
pub(crate) use self::solana_program_metadata::MT_METADATA_SOLANA_PROGRAM_METADATA_PROJECTION_VERSION; pub(crate) use self::solana_program_metadata::MT_METADATA_SOLANA_PROGRAM_PROJECTION_VERSION;
/// Canonical tracing target for Solana Program Metadata materialization. /// Canonical tracing target for Solana Program Metadata materialization.
pub(crate) use self::solana_program_metadata::TRACING_TARGET_MATERIALIZER_METADATA_SOLANA_PROGRAM_METADATA; pub(crate) use self::solana_program_metadata::TRACING_TARGET_MATERIALIZER_METADATA_SOLANA_PROGRAM;
/// Event families accepted by the Token-2022 metadata instruction materializer.
pub(crate) use self::token_2022::MT_METADATA_TOKEN_2022_ACCEPTED_FAMILIES;
/// Stable runtime component name for Token-2022 embedded metadata projections. /// Stable runtime component name for Token-2022 embedded metadata projections.
pub(crate) use self::token_2022::MT_METADATA_TOKEN_2022_COMPONENT_NAME; pub(crate) use self::token_2022::MT_METADATA_TOKEN_2022_COMPONENT_NAME;
/// Exact embedded Token Metadata and Token Group instruction ownership.
pub(crate) use self::token_2022::MT_METADATA_TOKEN_2022_INSTRUCTION_ENTRIES;
/// Stable processor name for Token-2022 embedded metadata projections. /// Stable processor name for Token-2022 embedded metadata projections.
pub(crate) use self::token_2022::MT_METADATA_TOKEN_2022_PROCESSOR_NAME; pub(crate) use self::token_2022::MT_METADATA_TOKEN_2022_PROCESSOR_NAME;
/// Stable Token-2022 embedded metadata projection version. /// Stable Token-2022 embedded metadata projection version.

View File

@@ -1,17 +1,22 @@
// file: kb-lib/src/materializer/metadata/metaplex_token_metadata.rs // file: kb-lib/src/materializer/metadata/metaplex_token_metadata.rs
// version: 2 // version: 3
//! Metaplex Token Metadata account-state materializer. //! Metaplex Token Metadata account-state materializer.
mod account; mod account;
mod constants; mod constants;
mod materializer;
mod state; mod state;
/// Materializes one canonical non-Metadata Metaplex account snapshot. /// Materializes one canonical non-Metadata Metaplex account snapshot.
pub use self::account::materializer_metadata_materialize_metaplex_account_snapshot; pub use self::account::materializer_metadata_materialize_metaplex_account_snapshot;
/// Metaplex Token Metadata instruction materializer.
pub use self::materializer::MtMetadataMetaplexTokenMetadataMaterializer;
/// Materializes one canonical Metaplex Metadata account snapshot. /// Materializes one canonical Metaplex Metadata account snapshot.
pub use self::state::materializer_metadata_materialize_metaplex_metadata_snapshot; pub use self::state::materializer_metadata_materialize_metaplex_metadata_snapshot;
/// Event families accepted by the Metaplex Token Metadata instruction materializer.
pub(crate) use self::constants::MT_METADATA_METAPLEX_TOKEN_METADATA_ACCEPTED_FAMILIES;
/// Stable runtime component name for Metaplex Token Metadata projections. /// Stable runtime component name for Metaplex Token Metadata projections.
pub(crate) use self::constants::MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME; pub(crate) use self::constants::MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME;
/// Stable processor name for Metaplex Token Metadata projections. /// Stable processor name for Metaplex Token Metadata projections.

View File

@@ -1,5 +1,5 @@
// file: kb-lib/src/materializer/metadata/metaplex_token_metadata/constants.rs // file: kb-lib/src/materializer/metadata/metaplex_token_metadata/constants.rs
// version: 4 // version: 6
//! Local constants for the metadata metaplex_token_metadata materializer. //! Local constants for the metadata metaplex_token_metadata materializer.
@@ -9,6 +9,9 @@ pub(crate) const MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME: &str =
/// Stable processor name persisted by metadata projections. /// Stable processor name persisted by metadata projections.
pub(crate) const MT_METADATA_METAPLEX_TOKEN_METADATA_PROCESSOR_NAME: &str = pub(crate) const MT_METADATA_METAPLEX_TOKEN_METADATA_PROCESSOR_NAME: &str =
"materializer.metadata.metaplex_token_metadata"; "materializer.metadata.metaplex_token_metadata";
/// Event families accepted by the Metaplex Token Metadata instruction materializer.
pub(crate) const MT_METADATA_METAPLEX_TOKEN_METADATA_ACCEPTED_FAMILIES: &[crate::MdEventFamily] =
&[crate::MdEventFamily::Metadata];
/// Stable projection contract version. /// Stable projection contract version.
pub(crate) const MT_METADATA_METAPLEX_TOKEN_METADATA_PROJECTION_VERSION: u32 = 2; pub(crate) const MT_METADATA_METAPLEX_TOKEN_METADATA_PROJECTION_VERSION: u32 = 2;
/// Canonical tracing target for the metadata metaplex_token_metadata materializer. /// Canonical tracing target for the metadata metaplex_token_metadata materializer.

View File

@@ -0,0 +1,234 @@
// file: kb-lib/src/materializer/metadata/metaplex_token_metadata/materializer.rs
// version: 1
//! Metaplex Token Metadata instruction materialization.
/// Metaplex Token Metadata instruction materializer.
#[derive(Clone, Debug, Default)]
pub struct MtMetadataMetaplexTokenMetadataMaterializer;
impl crate::MtMaterializer for crate::MtMetadataMetaplexTokenMetadataMaterializer {
fn materializer_name(&self) -> &'static str {
return crate::MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME;
}
fn materializer_version(&self) -> &'static str {
return env!("CARGO_PKG_VERSION");
}
fn accepts_event(&self, event: &crate::MdDecodedProtocolEvent) -> bool {
return event.event_family == crate::MdEventFamily::Metadata
&& event.program_id.0 == kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID
&& event.surface_code.0 == crate::DC_METADATA_MTM_SURFACE_CODE
&& instruction_contract(event.event_name.0.as_str()).is_some();
}
fn materialize_event(
&self,
event: &crate::MdDecodedProtocolEvent,
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
if !crate::MtMaterializer::accepts_event(self, event) {
return std::result::Result::Ok(std::vec::Vec::new());
}
return std::result::Result::Ok(std::vec![crate::MdMaterializedEvent {
signature: event.signature.clone(),
slot: event.slot,
decoded_family: event.event_family,
materialized_family: crate::MdMaterializedEventFamily::Metadata,
}]);
}
}
impl crate::MtApiEventMaterializer for crate::MtMetadataMetaplexTokenMetadataMaterializer {
fn identity(&self) -> crate::MtApiMaterializerIdentity {
return crate::MtApiMaterializerIdentity {
name: crate::MT_METADATA_METAPLEX_TOKEN_METADATA_PROCESSOR_NAME.to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
};
}
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
return crate::MT_METADATA_METAPLEX_TOKEN_METADATA_ACCEPTED_FAMILIES;
}
fn accepts_observation(&self, observation: &crate::DcApiDecodedObservation) -> bool {
return observation.event.event_family == crate::MdEventFamily::Metadata
&& observation.event.program_id.0
== kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID
&& observation.event.surface_code.0 == crate::DC_METADATA_MTM_SURFACE_CODE
&& instruction_contract(observation.event.event_name.0.as_str()).is_some()
&& payload_contract_matches(observation);
}
fn transaction_policy(
&self,
_family: crate::MdEventFamily,
) -> crate::MtApiMaterializationTransactionPolicy {
return crate::MtApiMaterializationTransactionPolicy::SuccessfulCommittedOnly;
}
fn materialize(
&self,
observation: &crate::DcApiDecodedObservation,
) -> crate::MtApiMaterializerExecutionResult {
if !crate::MtApiEventMaterializer::accepts_observation(self, observation) {
return crate::MtApiMaterializerExecutionResult::ignored();
}
if observation.transaction_failed || !observation.observation_committed {
return crate::MtApiMaterializerExecutionResult::refused(
"failed_transaction_metaplex_token_metadata_refused",
"failed or uncommitted Metaplex Token Metadata instructions cannot create metadata outputs",
);
}
let operation = observation.event.event_name.0.as_str();
let (_, historical) = match instruction_contract(operation) {
std::option::Option::Some(value) => value,
std::option::Option::None => return crate::MtApiMaterializerExecutionResult::ignored(),
};
let deprecated = operation.starts_with("deprecated_");
let output = crate::MtApiMaterializedOutput {
output_key: format!(
"metaplex_token_metadata_instruction:{}:{}:{}",
observation.event.signature.0, observation.event.instruction_path.0, operation
),
family: crate::MdMaterializedEventFamily::Metadata,
payload_json: serde_json::json!({
"projectionVersion": crate::MT_METADATA_METAPLEX_TOKEN_METADATA_PROJECTION_VERSION,
"domain": "metaplex_token_metadata",
"projectionKind": "committed_instruction_fact",
"projectionSemantics": "committed_instruction_fact_non_authoritative_state",
"operation": operation,
"programId": observation.event.program_id.0.clone(),
"signature": observation.event.signature.0.clone(),
"slot": observation.event.slot.0,
"instructionPath": observation.event.instruction_path.0.clone(),
"sourceKind": observation.event.source_kind,
"transactionSucceeded": true,
"historical": historical,
"deprecated": deprecated,
"authoritativeFinalStateCaptured": false,
"stateProjectionAuthority": "post_execution_account_snapshot",
"parameters": observation.payload_json.get("parameters").cloned().unwrap_or(serde_json::Value::Null),
"accounts": observation.payload_json.get("accounts").cloned().unwrap_or(serde_json::Value::Null),
"decodedPayload": observation.payload_json.clone(),
"provenance": {
"processorName": crate::MT_METADATA_METAPLEX_TOKEN_METADATA_PROCESSOR_NAME,
"processorVersion": env!("CARGO_PKG_VERSION"),
"decoderProof": observation.proof.clone()
}
}),
};
tracing::debug!(
target: crate::TRACING_TARGET_MATERIALIZER_METADATA_METAPLEX_TOKEN_METADATA,
component_name = crate::MT_METADATA_METAPLEX_TOKEN_METADATA_COMPONENT_NAME,
processor_name = crate::MT_METADATA_METAPLEX_TOKEN_METADATA_PROCESSOR_NAME,
action = "materialize_metaplex_token_metadata_instruction",
operation = operation,
historical = historical,
deprecated = deprecated,
signature = %observation.event.signature.0,
instruction_path = %observation.event.instruction_path.0,
output_count = 1_usize,
"materialize committed Metaplex Token Metadata instruction"
);
return crate::MtApiMaterializerExecutionResult {
status: crate::MtApiMaterializerOutcomeStatus::Inserted,
outputs: std::vec![output],
diagnostics: std::vec::Vec::new(),
};
}
}
fn instruction_contract(operation: &str) -> std::option::Option<(u8, bool)> {
return crate::DC_METADATA_MTM_INSTRUCTION_ENTRIES
.iter()
.find(|(_, code, _)| return *code == operation)
.map(|(discriminator, _, historical)| return (*discriminator, *historical));
}
fn payload_contract_matches(observation: &crate::DcApiDecodedObservation) -> bool {
let instruction =
observation.payload_json.get("instruction").and_then(serde_json::Value::as_str);
let program_id = observation.payload_json.get("programId").and_then(serde_json::Value::as_str);
return instruction == std::option::Option::Some(observation.event.event_name.0.as_str())
&& program_id
== std::option::Option::Some(
kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID,
);
}
#[cfg(test)]
mod tests {
fn observation(operation: &str, committed: bool) -> crate::DcApiDecodedObservation {
return crate::DcApiDecodedObservation {
event_key: format!("metaplex_token_metadata:{operation}:0"),
event: crate::MdDecodedProtocolEvent {
signature: crate::MdSignature("signature111".to_string()),
slot: crate::MdSlot(42),
instruction_path: crate::MdInstructionPath("0".to_string()),
program_id: crate::MdProgramId(
kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID.to_string(),
),
protocol_code: crate::MdProtocolCode(
crate::DC_METADATA_MTM_SURFACE_CODE.to_string(),
),
surface_code: crate::MdSurfaceCode(crate::DC_METADATA_MTM_SURFACE_CODE.to_string()),
event_code: crate::MdEventCode(format!(
"{}.{}",
crate::DC_METADATA_MTM_SURFACE_CODE,
operation
)),
event_name: crate::MdEventName(operation.to_string()),
event_family: crate::MdEventFamily::Metadata,
source_kind: crate::MdEventSourceKind::Instruction,
confidence: crate::MdDecoderConfidence::ManualExact,
},
payload_json: serde_json::json!({
"programId": kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID,
"instruction": operation,
"parameters": {},
"accounts": []
}),
transaction_failed: !committed,
transaction_error: std::option::Option::None,
observation_committed: committed,
proof: crate::DcApiDecoderProof {
kind: crate::DcApiDecoderProofKind::Manual,
confidence: crate::MdDecoderConfidence::ManualExact,
evidence: std::vec!["wire".to_string()],
},
};
}
#[test]
fn every_decoded_instruction_has_one_metadata_owner() {
let materializer = crate::MtMetadataMetaplexTokenMetadataMaterializer;
for (_, operation, historical) in crate::DC_METADATA_MTM_INSTRUCTION_ENTRIES.iter().copied()
{
let value = observation(operation, true);
assert!(crate::MtApiEventMaterializer::accepts_observation(&materializer, &value));
let result = crate::MtApiEventMaterializer::materialize(&materializer, &value);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs.len(), 1);
assert_eq!(result.outputs[0].payload_json["operation"], operation);
assert_eq!(result.outputs[0].payload_json["historical"], historical);
assert_eq!(
result.outputs[0].payload_json["deprecated"],
operation.starts_with("deprecated_")
);
}
}
#[test]
fn failed_and_foreign_observations_do_not_create_outputs() {
let materializer = crate::MtMetadataMetaplexTokenMetadataMaterializer;
let failed = observation("create_metadata_account_v3", false);
let refused = crate::MtApiEventMaterializer::materialize(&materializer, &failed);
assert_eq!(refused.status, crate::MtApiMaterializerOutcomeStatus::Refused);
let mut foreign = observation("create_metadata_account_v3", true);
foreign.event.program_id =
crate::MdProgramId(kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID.to_string());
let ignored = crate::MtApiEventMaterializer::materialize(&materializer, &foreign);
assert_eq!(ignored.status, crate::MtApiMaterializerOutcomeStatus::Ignored);
}
}

View File

@@ -1,5 +1,5 @@
// file: kb-lib/src/materializer/metadata/solana_program_metadata.rs // file: kb-lib/src/materializer/metadata/solana_program_metadata.rs
// version: 2 // version: 3
//! Solana Program Metadata materialization surface. //! Solana Program Metadata materialization surface.
@@ -7,10 +7,8 @@ mod constants;
mod materializer; mod materializer;
mod state; mod state;
/// Compatibility materializer retaining the historical generic metadata type name. /// Solana Program Metadata instruction materializer.
pub use self::materializer::MtMetadataMaterializer; pub use self::materializer::MtMetadataSolanaProgramMaterializer;
/// Stable Solana Program Metadata instruction materializer.
pub use self::materializer::MtSolanaProgramMetadataMaterializer;
/// Materializes one decoded Solana Program Metadata account snapshot. /// Materializes one decoded Solana Program Metadata account snapshot.
pub use self::state::materializer_metadata_materialize_solana_program_metadata_account_snapshot; pub use self::state::materializer_metadata_materialize_solana_program_metadata_account_snapshot;
/// Materializes one bounded Solana Program Metadata Buffer account snapshot. /// Materializes one bounded Solana Program Metadata Buffer account snapshot.
@@ -19,12 +17,12 @@ pub use self::state::materializer_metadata_materialize_solana_program_metadata_b
pub use self::state::materializer_metadata_materialize_solana_program_metadata_metadata_snapshot; pub use self::state::materializer_metadata_materialize_solana_program_metadata_metadata_snapshot;
/// Event families accepted by the Solana Program Metadata instruction materializer. /// Event families accepted by the Solana Program Metadata instruction materializer.
pub(crate) use self::constants::MT_METADATA_SOLANA_PROGRAM_METADATA_ACCEPTED_FAMILIES; pub(crate) use self::constants::MT_METADATA_SOLANA_PROGRAM_ACCEPTED_FAMILIES;
/// Stable runtime component name for Solana Program Metadata projections. /// Stable runtime component name for Solana Program Metadata projections.
pub(crate) use self::constants::MT_METADATA_SOLANA_PROGRAM_METADATA_COMPONENT_NAME; pub(crate) use self::constants::MT_METADATA_SOLANA_PROGRAM_COMPONENT_NAME;
/// Stable processor name for Solana Program Metadata projections. /// Stable processor name for Solana Program Metadata projections.
pub(crate) use self::constants::MT_METADATA_SOLANA_PROGRAM_METADATA_PROCESSOR_NAME; pub(crate) use self::constants::MT_METADATA_SOLANA_PROGRAM_PROCESSOR_NAME;
/// Stable projection schema version for Solana Program Metadata outputs. /// Stable projection schema version for Solana Program Metadata outputs.
pub(crate) use self::constants::MT_METADATA_SOLANA_PROGRAM_METADATA_PROJECTION_VERSION; pub(crate) use self::constants::MT_METADATA_SOLANA_PROGRAM_PROJECTION_VERSION;
/// Canonical tracing target for Solana Program Metadata materialization. /// Canonical tracing target for Solana Program Metadata materialization.
pub(crate) use self::constants::TRACING_TARGET_MATERIALIZER_METADATA_SOLANA_PROGRAM_METADATA; pub(crate) use self::constants::TRACING_TARGET_MATERIALIZER_METADATA_SOLANA_PROGRAM;

View File

@@ -4,15 +4,15 @@
//! Local constants for the Solana Program Metadata materializer. //! Local constants for the Solana Program Metadata materializer.
/// Stable runtime component name for Solana Program Metadata materialization. /// Stable runtime component name for Solana Program Metadata materialization.
pub(crate) const MT_METADATA_SOLANA_PROGRAM_METADATA_COMPONENT_NAME: &str = pub(crate) const MT_METADATA_SOLANA_PROGRAM_COMPONENT_NAME: &str =
"kb-lib.materializer.metadata.solana_program_metadata"; "kb-lib.materializer.metadata.solana_program_metadata";
/// Stable processor name persisted by Solana Program Metadata projections. /// Stable processor name persisted by Solana Program Metadata projections.
pub(crate) const MT_METADATA_SOLANA_PROGRAM_METADATA_PROCESSOR_NAME: &str = pub(crate) const MT_METADATA_SOLANA_PROGRAM_PROCESSOR_NAME: &str =
"materializer.metadata.solana_program_metadata"; "materializer.metadata.solana_program_metadata";
/// Stable projection schema version for Solana Program Metadata outputs. /// Stable projection schema version for Solana Program Metadata outputs.
pub(crate) const MT_METADATA_SOLANA_PROGRAM_METADATA_PROJECTION_VERSION: u32 = 1; pub(crate) const MT_METADATA_SOLANA_PROGRAM_PROJECTION_VERSION: u32 = 1;
pub(crate) const MT_METADATA_SOLANA_PROGRAM_METADATA_ACCEPTED_FAMILIES: &[crate::MdEventFamily] = pub(crate) const MT_METADATA_SOLANA_PROGRAM_ACCEPTED_FAMILIES: &[crate::MdEventFamily] =
&[crate::MdEventFamily::Metadata]; &[crate::MdEventFamily::Metadata];
/// Canonical tracing target for Solana Program Metadata materialization. /// Canonical tracing target for Solana Program Metadata materialization.
pub(crate) const TRACING_TARGET_MATERIALIZER_METADATA_SOLANA_PROGRAM_METADATA: &str = pub(crate) const TRACING_TARGET_MATERIALIZER_METADATA_SOLANA_PROGRAM: &str =
"kb-lib.materializer.metadata.solana_program_metadata"; "kb-lib.materializer.metadata.solana_program_metadata";

View File

@@ -1,19 +1,15 @@
// file: kb-lib/src/materializer/metadata/solana_program_metadata/materializer.rs // file: kb-lib/src/materializer/metadata/solana_program_metadata/materializer.rs
// version: 4 // version: 5
//! Committed Solana Program Metadata instruction projections. //! Committed Solana Program Metadata instruction projections.
/// Stable Solana Program Metadata instruction materializer. /// Stable Solana Program Metadata instruction materializer.
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub struct MtSolanaProgramMetadataMaterializer; pub struct MtMetadataSolanaProgramMaterializer;
/// Compatibility materializer retaining the historical generic metadata type name. impl crate::MtMaterializer for crate::MtMetadataSolanaProgramMaterializer {
#[derive(Clone, Debug, Default)]
pub struct MtMetadataMaterializer;
impl crate::MtMaterializer for crate::MtSolanaProgramMetadataMaterializer {
fn materializer_name(&self) -> &'static str { fn materializer_name(&self) -> &'static str {
return crate::MT_METADATA_SOLANA_PROGRAM_METADATA_COMPONENT_NAME; return crate::MT_METADATA_SOLANA_PROGRAM_COMPONENT_NAME;
} }
fn materializer_version(&self) -> &'static str { fn materializer_version(&self) -> &'static str {
@@ -45,84 +41,16 @@ impl crate::MtMaterializer for crate::MtSolanaProgramMetadataMaterializer {
} }
} }
impl crate::MtMaterializer for crate::MtMetadataMaterializer { impl crate::MtApiEventMaterializer for crate::MtMetadataSolanaProgramMaterializer {
fn materializer_name(&self) -> &'static str {
return crate::MT_METADATA_SOLANA_PROGRAM_METADATA_COMPONENT_NAME;
}
fn materializer_version(&self) -> &'static str {
return env!("CARGO_PKG_VERSION");
}
fn accepts_event(&self, event: &crate::MdDecodedProtocolEvent) -> bool {
return crate::MtMaterializer::accepts_event(
&crate::MtSolanaProgramMetadataMaterializer,
event,
);
}
fn materialize_event(
&self,
event: &crate::MdDecodedProtocolEvent,
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
return crate::MtMaterializer::materialize_event(
&crate::MtSolanaProgramMetadataMaterializer,
event,
);
}
}
impl crate::MtApiEventMaterializer for crate::MtMetadataMaterializer {
fn identity(&self) -> crate::MtApiMaterializerIdentity {
return crate::MtApiEventMaterializer::identity(
&crate::MtSolanaProgramMetadataMaterializer,
);
}
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
return crate::MtApiEventMaterializer::accepted_families(
&crate::MtSolanaProgramMetadataMaterializer,
);
}
fn accepts_observation(&self, observation: &crate::DcApiDecodedObservation) -> bool {
return crate::MtApiEventMaterializer::accepts_observation(
&crate::MtSolanaProgramMetadataMaterializer,
observation,
);
}
fn transaction_policy(
&self,
family: crate::MdEventFamily,
) -> crate::MtApiMaterializationTransactionPolicy {
return crate::MtApiEventMaterializer::transaction_policy(
&crate::MtSolanaProgramMetadataMaterializer,
family,
);
}
fn materialize(
&self,
observation: &crate::DcApiDecodedObservation,
) -> crate::MtApiMaterializerExecutionResult {
return crate::MtApiEventMaterializer::materialize(
&crate::MtSolanaProgramMetadataMaterializer,
observation,
);
}
}
impl crate::MtApiEventMaterializer for crate::MtSolanaProgramMetadataMaterializer {
fn identity(&self) -> crate::MtApiMaterializerIdentity { fn identity(&self) -> crate::MtApiMaterializerIdentity {
return crate::MtApiMaterializerIdentity { return crate::MtApiMaterializerIdentity {
name: crate::MT_METADATA_SOLANA_PROGRAM_METADATA_PROCESSOR_NAME.to_string(), name: crate::MT_METADATA_SOLANA_PROGRAM_PROCESSOR_NAME.to_string(),
version: env!("CARGO_PKG_VERSION").to_string(), version: env!("CARGO_PKG_VERSION").to_string(),
}; };
} }
fn accepted_families(&self) -> &'static [crate::MdEventFamily] { fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
return crate::MT_METADATA_SOLANA_PROGRAM_METADATA_ACCEPTED_FAMILIES; return crate::MT_METADATA_SOLANA_PROGRAM_ACCEPTED_FAMILIES;
} }
fn accepts_observation(&self, observation: &crate::DcApiDecodedObservation) -> bool { fn accepts_observation(&self, observation: &crate::DcApiDecodedObservation) -> bool {
@@ -165,7 +93,7 @@ impl crate::MtApiEventMaterializer for crate::MtSolanaProgramMetadataMaterialize
), ),
family: crate::MdMaterializedEventFamily::Metadata, family: crate::MdMaterializedEventFamily::Metadata,
payload_json: serde_json::json!({ payload_json: serde_json::json!({
"projectionVersion": crate::MT_METADATA_SOLANA_PROGRAM_METADATA_PROJECTION_VERSION, "projectionVersion": crate::MT_METADATA_SOLANA_PROGRAM_PROJECTION_VERSION,
"domain": "solana_program_metadata", "domain": "solana_program_metadata",
"projectionKind": "committed_instruction_mutation", "projectionKind": "committed_instruction_mutation",
"projectionSemantics": "committed_instruction_fact_non_authoritative_state", "projectionSemantics": "committed_instruction_fact_non_authoritative_state",
@@ -183,14 +111,14 @@ impl crate::MtApiEventMaterializer for crate::MtSolanaProgramMetadataMaterialize
"externalContentFetched": false, "externalContentFetched": false,
"externalAccountRead": false, "externalAccountRead": false,
"provenance": { "provenance": {
"processorName": crate::MT_METADATA_SOLANA_PROGRAM_METADATA_PROCESSOR_NAME, "processorName": crate::MT_METADATA_SOLANA_PROGRAM_PROCESSOR_NAME,
"processorVersion": env!("CARGO_PKG_VERSION"), "processorVersion": env!("CARGO_PKG_VERSION"),
"decoderProof": observation.proof.clone() "decoderProof": observation.proof.clone()
} }
}), }),
}; };
tracing::debug!( tracing::debug!(
target: crate::TRACING_TARGET_MATERIALIZER_METADATA_SOLANA_PROGRAM_METADATA, target: crate::TRACING_TARGET_MATERIALIZER_METADATA_SOLANA_PROGRAM,
action = "materialize_solana_program_metadata_instruction", action = "materialize_solana_program_metadata_instruction",
operation = operation, operation = operation,
signature = %observation.event.signature.0, signature = %observation.event.signature.0,
@@ -355,9 +283,9 @@ mod tests {
#[test] #[test]
fn every_stable_instruction_has_one_materialized_mutation_fact() { fn every_stable_instruction_has_one_materialized_mutation_fact() {
let materializer = crate::MtSolanaProgramMetadataMaterializer; let materializer = crate::MtMetadataSolanaProgramMaterializer;
let identity = crate::MtApiEventMaterializer::identity(&materializer); let identity = crate::MtApiEventMaterializer::identity(&materializer);
assert_eq!(identity.name, crate::MT_METADATA_SOLANA_PROGRAM_METADATA_PROCESSOR_NAME); assert_eq!(identity.name, crate::MT_METADATA_SOLANA_PROGRAM_PROCESSOR_NAME);
for (_, operation) in crate::DC_METADATA_SPM_INSTRUCTION_ENTRIES.iter().copied() { for (_, operation) in crate::DC_METADATA_SPM_INSTRUCTION_ENTRIES.iter().copied() {
let value = observation(operation, true); let value = observation(operation, true);
assert!(crate::MtApiEventMaterializer::accepts_observation(&materializer, &value)); assert!(crate::MtApiEventMaterializer::accepts_observation(&materializer, &value));
@@ -369,14 +297,14 @@ mod tests {
assert_eq!(result.outputs[0].payload_json["projection"]["replaced"], false); assert_eq!(result.outputs[0].payload_json["projection"]["replaced"], false);
assert_eq!( assert_eq!(
result.outputs[0].payload_json["provenance"]["processorName"], result.outputs[0].payload_json["provenance"]["processorName"],
crate::MT_METADATA_SOLANA_PROGRAM_METADATA_PROCESSOR_NAME crate::MT_METADATA_SOLANA_PROGRAM_PROCESSOR_NAME
); );
} }
} }
#[test] #[test]
fn failed_instruction_is_refused_and_foreign_surface_is_ignored() { fn failed_instruction_is_refused_and_foreign_surface_is_ignored() {
let materializer = crate::MtSolanaProgramMetadataMaterializer; let materializer = crate::MtMetadataSolanaProgramMaterializer;
let failed = observation("set_data", false); let failed = observation("set_data", false);
let failure = crate::MtApiEventMaterializer::materialize(&materializer, &failed); let failure = crate::MtApiEventMaterializer::materialize(&materializer, &failed);
assert_eq!(failure.status, crate::MtApiMaterializerOutcomeStatus::Refused); assert_eq!(failure.status, crate::MtApiMaterializerOutcomeStatus::Refused);
@@ -424,7 +352,7 @@ mod tests {
#[test] #[test]
fn trim_keeps_prestable_alias_as_provenance_only() { fn trim_keeps_prestable_alias_as_provenance_only() {
let materializer = crate::MtSolanaProgramMetadataMaterializer; let materializer = crate::MtMetadataSolanaProgramMaterializer;
let value = observation("trim", true); let value = observation("trim", true);
let result = crate::MtApiEventMaterializer::materialize(&materializer, &value); let result = crate::MtApiEventMaterializer::materialize(&materializer, &value);
assert_eq!( assert_eq!(

View File

@@ -63,7 +63,7 @@ pub fn materializer_metadata_materialize_solana_program_metadata_buffer_snapshot
output_key: format!("solana_program_metadata_account_state:{}", snapshot.account), output_key: format!("solana_program_metadata_account_state:{}", snapshot.account),
family: crate::MdMaterializedEventFamily::Metadata, family: crate::MdMaterializedEventFamily::Metadata,
payload_json: serde_json::json!({ payload_json: serde_json::json!({
"projectionVersion": crate::MT_METADATA_SOLANA_PROGRAM_METADATA_PROJECTION_VERSION, "projectionVersion": crate::MT_METADATA_SOLANA_PROGRAM_PROJECTION_VERSION,
"domain": "solana_program_metadata", "domain": "solana_program_metadata",
"projectionKind": "buffer_state", "projectionKind": "buffer_state",
"projectionSemantics": "authoritative_bounded_owned_account_snapshot", "projectionSemantics": "authoritative_bounded_owned_account_snapshot",
@@ -83,7 +83,7 @@ pub fn materializer_metadata_materialize_solana_program_metadata_buffer_snapshot
"externalAccountRead": false, "externalAccountRead": false,
"instructionProjectionRole": "mutation_observation_only_no_duplicate_state", "instructionProjectionRole": "mutation_observation_only_no_duplicate_state",
"provenance": { "provenance": {
"processorName": crate::MT_METADATA_SOLANA_PROGRAM_METADATA_PROCESSOR_NAME, "processorName": crate::MT_METADATA_SOLANA_PROGRAM_PROCESSOR_NAME,
"processorVersion": env!("CARGO_PKG_VERSION"), "processorVersion": env!("CARGO_PKG_VERSION"),
"source": "bounded_solana_program_metadata_account_decoder" "source": "bounded_solana_program_metadata_account_decoder"
} }
@@ -150,7 +150,7 @@ pub fn materializer_metadata_materialize_solana_program_metadata_metadata_snapsh
output_key: format!("solana_program_metadata_account_state:{}", snapshot.account), output_key: format!("solana_program_metadata_account_state:{}", snapshot.account),
family: crate::MdMaterializedEventFamily::Metadata, family: crate::MdMaterializedEventFamily::Metadata,
payload_json: serde_json::json!({ payload_json: serde_json::json!({
"projectionVersion": crate::MT_METADATA_SOLANA_PROGRAM_METADATA_PROJECTION_VERSION, "projectionVersion": crate::MT_METADATA_SOLANA_PROGRAM_PROJECTION_VERSION,
"domain": "solana_program_metadata", "domain": "solana_program_metadata",
"projectionKind": "metadata_state", "projectionKind": "metadata_state",
"projectionSemantics": "authoritative_bounded_owned_account_snapshot", "projectionSemantics": "authoritative_bounded_owned_account_snapshot",
@@ -177,7 +177,7 @@ pub fn materializer_metadata_materialize_solana_program_metadata_metadata_snapsh
"externalAccountRead": false, "externalAccountRead": false,
"instructionProjectionRole": "mutation_observation_only_no_duplicate_state", "instructionProjectionRole": "mutation_observation_only_no_duplicate_state",
"provenance": { "provenance": {
"processorName": crate::MT_METADATA_SOLANA_PROGRAM_METADATA_PROCESSOR_NAME, "processorName": crate::MT_METADATA_SOLANA_PROGRAM_PROCESSOR_NAME,
"processorVersion": env!("CARGO_PKG_VERSION"), "processorVersion": env!("CARGO_PKG_VERSION"),
"source": "bounded_solana_program_metadata_account_decoder" "source": "bounded_solana_program_metadata_account_decoder"
} }

View File

@@ -1,16 +1,23 @@
// file: kb-lib/src/materializer/metadata/token_2022.rs // file: kb-lib/src/materializer/metadata/token_2022.rs
// version: 2 // version: 3
//! Embedded SPL Token-2022 metadata materialization surface. //! Embedded SPL Token-2022 metadata materialization surface.
mod constants; mod constants;
mod materializer;
mod state; mod state;
/// SPL Token-2022 embedded metadata and group instruction materializer.
pub use self::materializer::MtMetadataToken2022Materializer;
/// Materializes Token-2022 embedded metadata and group snapshots. /// Materializes Token-2022 embedded metadata and group snapshots.
pub use self::state::materializer_metadata_materialize_token_2022_snapshot; pub use self::state::materializer_metadata_materialize_token_2022_snapshot;
/// Event families accepted by the Token-2022 metadata instruction materializer.
pub(crate) use self::constants::MT_METADATA_TOKEN_2022_ACCEPTED_FAMILIES;
/// Stable runtime component name for Token-2022 embedded metadata projections. /// Stable runtime component name for Token-2022 embedded metadata projections.
pub(crate) use self::constants::MT_METADATA_TOKEN_2022_COMPONENT_NAME; pub(crate) use self::constants::MT_METADATA_TOKEN_2022_COMPONENT_NAME;
/// Exact embedded Token Metadata and Token Group instruction ownership.
pub(crate) use self::constants::MT_METADATA_TOKEN_2022_INSTRUCTION_ENTRIES;
/// Stable processor name for Token-2022 embedded metadata projections. /// Stable processor name for Token-2022 embedded metadata projections.
pub(crate) use self::constants::MT_METADATA_TOKEN_2022_PROCESSOR_NAME; pub(crate) use self::constants::MT_METADATA_TOKEN_2022_PROCESSOR_NAME;
/// Stable Token-2022 embedded metadata projection version. /// Stable Token-2022 embedded metadata projection version.

View File

@@ -1,5 +1,5 @@
// file: kb-lib/src/materializer/metadata/token_2022/constants.rs // file: kb-lib/src/materializer/metadata/token_2022/constants.rs
// version: 3 // version: 4
//! Local constants for the metadata token_2022 materializer. //! Local constants for the metadata token_2022 materializer.
@@ -8,6 +8,21 @@ pub(crate) const MT_METADATA_TOKEN_2022_COMPONENT_NAME: &str =
"kb-lib.materializer.metadata.token_2022"; "kb-lib.materializer.metadata.token_2022";
/// Stable processor name persisted by metadata projections. /// Stable processor name persisted by metadata projections.
pub(crate) const MT_METADATA_TOKEN_2022_PROCESSOR_NAME: &str = "materializer.metadata.token_2022"; pub(crate) const MT_METADATA_TOKEN_2022_PROCESSOR_NAME: &str = "materializer.metadata.token_2022";
/// Event families accepted by the Token-2022 metadata instruction materializer.
pub(crate) const MT_METADATA_TOKEN_2022_ACCEPTED_FAMILIES: &[crate::MdEventFamily] =
&[crate::MdEventFamily::Metadata];
/// Exact embedded Token Metadata and Token Group instruction ownership.
pub(crate) const MT_METADATA_TOKEN_2022_INSTRUCTION_ENTRIES: &[&str] = &[
"initialize_token_metadata",
"update_token_metadata_field",
"remove_token_metadata_key",
"update_token_metadata_authority",
"emit_token_metadata",
"initialize_token_group",
"update_token_group_max_size",
"update_token_group_authority",
"initialize_token_group_member",
];
/// Stable projection contract version. /// Stable projection contract version.
pub(crate) const MT_METADATA_TOKEN_2022_PROJECTION_VERSION: u32 = 2; pub(crate) const MT_METADATA_TOKEN_2022_PROJECTION_VERSION: u32 = 2;
/// Canonical tracing target for the metadata token_2022 materializer. /// Canonical tracing target for the metadata token_2022 materializer.

View File

@@ -0,0 +1,232 @@
// file: kb-lib/src/materializer/metadata/token_2022/materializer.rs
// version: 1
//! SPL Token-2022 embedded metadata and group instruction materialization.
/// SPL Token-2022 embedded metadata and group instruction materializer.
#[derive(Clone, Debug, Default)]
pub struct MtMetadataToken2022Materializer;
impl crate::MtMaterializer for crate::MtMetadataToken2022Materializer {
fn materializer_name(&self) -> &'static str {
return crate::MT_METADATA_TOKEN_2022_COMPONENT_NAME;
}
fn materializer_version(&self) -> &'static str {
return env!("CARGO_PKG_VERSION");
}
fn accepts_event(&self, event: &crate::MdDecodedProtocolEvent) -> bool {
return event.event_family == crate::MdEventFamily::Metadata
&& event.program_id.0 == kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID
&& event.surface_code.0 == crate::DC_SPL_TOKEN_2022_SURFACE_CODE
&& crate::MT_METADATA_TOKEN_2022_INSTRUCTION_ENTRIES
.iter()
.any(|code| return *code == event.event_name.0.as_str());
}
fn materialize_event(
&self,
event: &crate::MdDecodedProtocolEvent,
) -> kb_core::Result<std::vec::Vec<crate::MdMaterializedEvent>> {
if !crate::MtMaterializer::accepts_event(self, event) {
return std::result::Result::Ok(std::vec::Vec::new());
}
return std::result::Result::Ok(std::vec![crate::MdMaterializedEvent {
signature: event.signature.clone(),
slot: event.slot,
decoded_family: event.event_family,
materialized_family: crate::MdMaterializedEventFamily::Metadata,
}]);
}
}
impl crate::MtApiEventMaterializer for crate::MtMetadataToken2022Materializer {
fn identity(&self) -> crate::MtApiMaterializerIdentity {
return crate::MtApiMaterializerIdentity {
name: crate::MT_METADATA_TOKEN_2022_PROCESSOR_NAME.to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
};
}
fn accepted_families(&self) -> &'static [crate::MdEventFamily] {
return crate::MT_METADATA_TOKEN_2022_ACCEPTED_FAMILIES;
}
fn accepts_observation(&self, observation: &crate::DcApiDecodedObservation) -> bool {
return observation.event.event_family == crate::MdEventFamily::Metadata
&& observation.event.program_id.0 == kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID
&& observation.event.surface_code.0 == crate::DC_SPL_TOKEN_2022_SURFACE_CODE
&& crate::MT_METADATA_TOKEN_2022_INSTRUCTION_ENTRIES
.iter()
.any(|code| return *code == observation.event.event_name.0.as_str())
&& payload_contract_matches(observation);
}
fn transaction_policy(
&self,
_family: crate::MdEventFamily,
) -> crate::MtApiMaterializationTransactionPolicy {
return crate::MtApiMaterializationTransactionPolicy::SuccessfulCommittedOnly;
}
fn materialize(
&self,
observation: &crate::DcApiDecodedObservation,
) -> crate::MtApiMaterializerExecutionResult {
if !crate::MtApiEventMaterializer::accepts_observation(self, observation) {
return crate::MtApiMaterializerExecutionResult::ignored();
}
if observation.transaction_failed || !observation.observation_committed {
return crate::MtApiMaterializerExecutionResult::refused(
"failed_transaction_token_2022_metadata_refused",
"failed or uncommitted Token-2022 metadata instructions cannot create metadata outputs",
);
}
let operation = observation.event.event_name.0.as_str();
let output = crate::MtApiMaterializedOutput {
output_key: format!(
"token_2022_metadata_instruction:{}:{}:{}",
observation.event.signature.0, observation.event.instruction_path.0, operation
),
family: crate::MdMaterializedEventFamily::Metadata,
payload_json: serde_json::json!({
"projectionVersion": crate::MT_METADATA_TOKEN_2022_PROJECTION_VERSION,
"domain": "token_2022_metadata",
"projectionKind": projection_kind(operation),
"projectionSemantics": "committed_instruction_fact_non_authoritative_state",
"operation": operation,
"programId": observation.event.program_id.0.clone(),
"signature": observation.event.signature.0.clone(),
"slot": observation.event.slot.0,
"instructionPath": observation.event.instruction_path.0.clone(),
"sourceKind": observation.event.source_kind,
"transactionSucceeded": true,
"authoritativeFinalStateCaptured": false,
"stateProjectionAuthority": "post_execution_token_2022_mint_snapshot",
"parameters": observation.payload_json.get("parameters").cloned().unwrap_or(serde_json::Value::Null),
"accounts": observation.payload_json.get("accounts").cloned().unwrap_or(serde_json::Value::Null),
"authority": observation.payload_json.get("authority").cloned().unwrap_or(serde_json::Value::Null),
"decodedPayload": observation.payload_json.clone(),
"provenance": {
"processorName": crate::MT_METADATA_TOKEN_2022_PROCESSOR_NAME,
"processorVersion": env!("CARGO_PKG_VERSION"),
"decoderProof": observation.proof.clone()
}
}),
};
tracing::debug!(
target: crate::TRACING_TARGET_MATERIALIZER_METADATA_TOKEN_2022,
component_name = crate::MT_METADATA_TOKEN_2022_COMPONENT_NAME,
processor_name = crate::MT_METADATA_TOKEN_2022_PROCESSOR_NAME,
action = "materialize_token_2022_metadata_instruction",
operation = operation,
signature = %observation.event.signature.0,
instruction_path = %observation.event.instruction_path.0,
output_count = 1_usize,
"materialize committed Token-2022 metadata instruction"
);
return crate::MtApiMaterializerExecutionResult {
status: crate::MtApiMaterializerOutcomeStatus::Inserted,
outputs: std::vec![output],
diagnostics: std::vec::Vec::new(),
};
}
}
fn payload_contract_matches(observation: &crate::DcApiDecodedObservation) -> bool {
let instruction =
observation.payload_json.get("instruction").and_then(serde_json::Value::as_str);
let program_id = observation.payload_json.get("programId").and_then(serde_json::Value::as_str);
return instruction == std::option::Option::Some(observation.event.event_name.0.as_str())
&& program_id == std::option::Option::Some(kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID);
}
fn projection_kind(operation: &str) -> &'static str {
return match operation {
"initialize_token_metadata" => "metadata_initialized",
"update_token_metadata_field" => "metadata_field_updated",
"remove_token_metadata_key" => "metadata_key_removed",
"update_token_metadata_authority" => "metadata_authority_updated",
"emit_token_metadata" => "metadata_emitted",
"initialize_token_group" => "token_group_initialized",
"update_token_group_max_size" => "token_group_max_size_updated",
"update_token_group_authority" => "token_group_authority_updated",
"initialize_token_group_member" => "token_group_member_initialized",
_ => "unknown",
};
}
#[cfg(test)]
mod tests {
fn observation(operation: &str, committed: bool) -> crate::DcApiDecodedObservation {
return crate::DcApiDecodedObservation {
event_key: format!("token_2022:{operation}:0"),
event: crate::MdDecodedProtocolEvent {
signature: crate::MdSignature("signature111".to_string()),
slot: crate::MdSlot(42),
instruction_path: crate::MdInstructionPath("0".to_string()),
program_id: crate::MdProgramId(
kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID.to_string(),
),
protocol_code: crate::MdProtocolCode(
crate::DC_SPL_TOKEN_2022_SURFACE_CODE.to_string(),
),
surface_code: crate::MdSurfaceCode(
crate::DC_SPL_TOKEN_2022_SURFACE_CODE.to_string(),
),
event_code: crate::MdEventCode(format!(
"{}.{}",
crate::DC_SPL_TOKEN_2022_SURFACE_CODE,
operation
)),
event_name: crate::MdEventName(operation.to_string()),
event_family: crate::MdEventFamily::Metadata,
source_kind: crate::MdEventSourceKind::Instruction,
confidence: crate::MdDecoderConfidence::ManualExact,
},
payload_json: serde_json::json!({
"programId": kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID,
"instruction": operation,
"parameters": {},
"accounts": [],
"authority": null
}),
transaction_failed: !committed,
transaction_error: std::option::Option::None,
observation_committed: committed,
proof: crate::DcApiDecoderProof {
kind: crate::DcApiDecoderProofKind::Manual,
confidence: crate::MdDecoderConfidence::ManualExact,
evidence: std::vec!["wire".to_string()],
},
};
}
#[test]
fn every_embedded_metadata_and_group_instruction_has_one_owner() {
let materializer = crate::MtMetadataToken2022Materializer;
for operation in crate::MT_METADATA_TOKEN_2022_INSTRUCTION_ENTRIES {
let value = observation(operation, true);
assert!(crate::MtApiEventMaterializer::accepts_observation(&materializer, &value));
let result = crate::MtApiEventMaterializer::materialize(&materializer, &value);
assert_eq!(result.status, crate::MtApiMaterializerOutcomeStatus::Inserted);
assert_eq!(result.outputs.len(), 1);
assert_eq!(result.outputs[0].payload_json["operation"], *operation);
}
}
#[test]
fn failed_and_non_metadata_observations_do_not_create_outputs() {
let materializer = crate::MtMetadataToken2022Materializer;
let failed = observation("initialize_token_metadata", false);
let refused = crate::MtApiEventMaterializer::materialize(&materializer, &failed);
assert_eq!(refused.status, crate::MtApiMaterializerOutcomeStatus::Refused);
let mut unrelated = observation("initialize_token_metadata", true);
unrelated.event.event_name = crate::MdEventName("transfer_checked".to_string());
unrelated.payload_json["instruction"] =
serde_json::Value::String("transfer_checked".to_string());
let ignored = crate::MtApiEventMaterializer::materialize(&materializer, &unrelated);
assert_eq!(ignored.status, crate::MtApiMaterializerOutcomeStatus::Ignored);
}
}

View File

@@ -1,5 +1,5 @@
// file: kb-lib/tests/external_materializer_api.rs // file: kb-lib/tests/external_materializer_api.rs
// version: 7 // version: 8
//! Downstream-style compilation contract for externally implemented materializers. //! Downstream-style compilation contract for externally implemented materializers.
@@ -50,8 +50,9 @@ fn external_materializer_uses_only_the_public_kb_lib_contract() {
assert_event_materializer::<kb_lib::MtGovernanceMaterializer>(); assert_event_materializer::<kb_lib::MtGovernanceMaterializer>();
assert_event_materializer::<kb_lib::MtLendingMaterializer>(); assert_event_materializer::<kb_lib::MtLendingMaterializer>();
assert_event_materializer::<kb_lib::MtLifecycleMaterializer>(); assert_event_materializer::<kb_lib::MtLifecycleMaterializer>();
assert_event_materializer::<kb_lib::MtMetadataMaterializer>(); assert_event_materializer::<kb_lib::MtMetadataMetaplexTokenMetadataMaterializer>();
assert_event_materializer::<kb_lib::MtSolanaProgramMetadataMaterializer>(); assert_event_materializer::<kb_lib::MtMetadataSolanaProgramMaterializer>();
assert_event_materializer::<kb_lib::MtMetadataToken2022Materializer>();
assert_event_materializer::<kb_lib::MtLiquidityMaterializer>(); assert_event_materializer::<kb_lib::MtLiquidityMaterializer>();
assert_event_materializer::<kb_lib::MtNftMaterializer>(); assert_event_materializer::<kb_lib::MtNftMaterializer>();
assert_event_materializer::<kb_lib::MtOracleMaterializer>(); assert_event_materializer::<kb_lib::MtOracleMaterializer>();
@@ -71,8 +72,9 @@ fn external_materializer_uses_only_the_public_kb_lib_contract() {
assert_legacy_materializer::<kb_lib::MtGovernanceMaterializer>(); assert_legacy_materializer::<kb_lib::MtGovernanceMaterializer>();
assert_legacy_materializer::<kb_lib::MtLendingMaterializer>(); assert_legacy_materializer::<kb_lib::MtLendingMaterializer>();
assert_legacy_materializer::<kb_lib::MtLiquidityMaterializer>(); assert_legacy_materializer::<kb_lib::MtLiquidityMaterializer>();
assert_legacy_materializer::<kb_lib::MtMetadataMaterializer>(); assert_legacy_materializer::<kb_lib::MtMetadataMetaplexTokenMetadataMaterializer>();
assert_legacy_materializer::<kb_lib::MtSolanaProgramMetadataMaterializer>(); assert_legacy_materializer::<kb_lib::MtMetadataSolanaProgramMaterializer>();
assert_legacy_materializer::<kb_lib::MtMetadataToken2022Materializer>();
assert_legacy_materializer::<kb_lib::MtNftMaterializer>(); assert_legacy_materializer::<kb_lib::MtNftMaterializer>();
assert_legacy_materializer::<kb_lib::MtOracleMaterializer>(); assert_legacy_materializer::<kb_lib::MtOracleMaterializer>();
assert_legacy_materializer::<kb_lib::MtOrderbookMaterializer>(); assert_legacy_materializer::<kb_lib::MtOrderbookMaterializer>();

View File

@@ -1,8 +1,17 @@
<!-- file: kb-pipeline/CHANGELOG.md --> <!-- file: kb-pipeline/CHANGELOG.md -->
<!-- version: 15 --> <!-- version: 17 -->
# CHANGELOG — kb-pipeline # CHANGELOG — kb-pipeline
## 0.4.8-pre.008 — orchestration Solana Program Metadata généraliste
- ajoute les lectures de comptes `Buffer` et `Metadata` bornées, avec décodage et projection autoritative délégués à `kb-lib` ;
- ajoute le préflight stateful des neuf opérations stables, les preuves de préfinancement rent et lévaluation commune par `ExSafetyChecker` ;
- ajoute la readiness simulation-first, la résolution des signers et les postconditions confirmées sans transformer une absence de lecture en succès ;
- publie la matrice `METADATA_SOLANA_PROGRAM_PIPELINE_MATRIX.json` et maintient une frontière stricte avec les fixtures et scénarios Devnet de `kb-pipeline-demo-scenarios` ;
- renomme les modules internes selon la hiérarchie de domaine : `metadata_*` pour Metaplex et Solana Program Metadata, `spl_*` pour ATA, ElGamal Registry, Token et Token-2022, tout en conservant les fonctions publiques existantes ;
- remplace les assertions runtime sur constantes du test dAPI externe par des assertions `const`, afin de rester propre sous Clippy.
## 0.4.7-pre.016 — clôture documentaire Metaplex ## 0.4.7-pre.016 — clôture documentaire Metaplex
- aligne la documentation du pipeline sur la qualification finale des preuves réseau ; - aligne la documentation du pipeline sur la qualification finale des preuves réseau ;

View File

@@ -1,5 +1,5 @@
<!-- file: kb-pipeline/README.md --> <!-- file: kb-pipeline/README.md -->
<!-- version: 5 --> <!-- version: 7 -->
# kb-pipeline # kb-pipeline
@@ -15,26 +15,34 @@
- lectures stateful et corrélations ; - lectures stateful et corrélations ;
- préflights dexécution ; - préflights dexécution ;
- résolution des signers ; - résolution des signers ;
- orchestration simulation-first, confirmation, soumission et postvalidation ; - orchestration simulation-first, contrôles de readiness et postvalidation ;
- diagnostics stables exploitables par des applications ou scénarios. - diagnostics stables exploitables par des applications ou scénarios.
Pour Metaplex Token Metadata, la crate expose les lectures stateful bornées, le préflight, la validation de readiness et lagrégation des postconditions. Elle ne contient ni fixture Devnet, ni wallet de démonstration, ni séquence métier spécifique à un test. Pour Metaplex Token Metadata, la crate expose les lectures stateful bornées, le préflight, la validation de readiness et lagrégation des postconditions.
Pour Solana Program Metadata, elle expose les mêmes frontières réutilisables pour les neuf opérations stables : lecture `Buffer`/`Metadata`, préflight stateful, gouvernance `ExSafetyChecker`, readiness simulation-first et postconditions. Elle ne contient ni fixture Devnet, ni wallet de démonstration, ni séquence métier spécifique à un test.
## Surface publique principale ## Surface publique principale
- campagnes de backfill et progression ; - campagnes de backfill et progression ;
- extraction Core et decode replay ; - extraction Core et decode replay ;
- inspections stateful Solana Core et SPL ; - inspections stateful Solana Core et SPL ;
- `read_metaplex_token_metadata_stateful_account` ; - `read_metaplex_token_metadata_stateful_snapshot` ;
- `inspect_metaplex_token_metadata_preflight` ; - `inspect_metaplex_token_metadata_preflight` ;
- `validate_metaplex_token_metadata_execution_readiness` ; - `validate_metaplex_token_metadata_execution_readiness` ;
- `summarize_metaplex_token_metadata_postconditions`. - `summarize_metaplex_token_metadata_postconditions` ;
- `read_solana_program_metadata_stateful_snapshot` ;
- `inspect_solana_program_metadata_preflight` ;
- `validate_solana_program_metadata_execution_readiness` ;
- `inspect_solana_program_metadata_post_execution`.
Les fichiers internes suivent la même nomenclature que `kb-lib` : préfixe `metadata_` pour les programmes metadata, préfixe `spl_` pour les programmes SPL et préfixe `solana_` uniquement pour les contrats Solana Core transversaux. Les noms des fonctions publiques sont conservés pour éviter une rupture dAPI.
Voir [USAGE.md](USAGE.md) pour les exemples et invariants. Voir [USAGE.md](USAGE.md) pour les exemples et invariants.
## Relations avec le workspace ## Relations avec le workspace
La crate coordonne `kb-onchain-transport`, `kb-store`, `kb-lib`, `kb-core` et `kb-program-ids`. Elle ne dépend jamais de `kb-pipeline-demo-scenarios` ni de `kb-app-demo-desktop`. La crate coordonne `kb-onchain-transport`, `kb-store`, `kb-lib`, `kb-core` et `kb-program-ids`. Elle ne dépend jamais de `kb-pipeline-demo-scenarios` ni de `kb-app-demo-desktop`. Les fixtures, wallets temporaires et appels RPC de démonstration appartiennent aux scénarios ou aux applications qui consomment ces contrats.
## Documentation ## Documentation

View File

@@ -1,8 +1,14 @@
<!-- file: kb-pipeline/TODO.md --> <!-- file: kb-pipeline/TODO.md -->
<!-- version: 11 --> <!-- version: 12 -->
# TODO — kb-pipeline # TODO — kb-pipeline
## Validation réseau Solana Program Metadata
- [ ] `0.4.8-pre.009` - valider les neuf opérations sur Devnet depuis `kb-pipeline-demo-scenarios`, avec fixture native, simulation, soumission confirmée, lectures avant/après et replay.
- [ ] `0.4.8-pre.009` - vérifier depuis `demo_execution_metadata` que les scénarios desktop consomment les mêmes runners réutilisables sans dupliquer la logique du pipeline.
- [ ] Validation historique - exécuter `demo_backfill`, Core extraction et decode replay sur des signatures `ProgM6…` représentatives, puis contrôler les sorties matérialisées.
## Réaudit ultérieur des surfaces historiques ## Réaudit ultérieur des surfaces historiques
- [ ] Version à déterminer - après le réaudit des exécuteurs Solana Core et SPL dans `kb-lib`, vérifier que le pipeline orchestre sans filtrage implicite les opérations courantes et les opérations obsolètes explicitement approuvées. - [ ] Version à déterminer - après le réaudit des exécuteurs Solana Core et SPL dans `kb-lib`, vérifier que le pipeline orchestre sans filtrage implicite les opérations courantes et les opérations obsolètes explicitement approuvées.

View File

@@ -1,5 +1,5 @@
<!-- file: kb-pipeline/USAGE.md --> <!-- file: kb-pipeline/USAGE.md -->
<!-- version: 6 --> <!-- version: 8 -->
# Utilisation de kb-pipeline # Utilisation de kb-pipeline
@@ -152,6 +152,57 @@ where
Les rapports stateful ne constituent jamais une autorisation implicite dexécution. Les rapports stateful ne constituent jamais une autorisation implicite dexécution.
## Nomenclature interne des modules
Les modules privés sont organisés par domaine :
- `metadata_metaplex_token_metadata_*` ;
- `metadata_solana_program_*` ;
- `spl_ata_stateful` et `spl_elgamal_registry_stateful` ;
- `spl_token_stateful` et `spl_token_2022_*` ;
- `solana_stateful` uniquement pour les primitives Solana Core partagées.
Cette réorganisation ne renomme pas les fonctions et structures publiques existantes.
## Valider historiquement Solana Program Metadata
La validation historique reste un enchaînement de surfaces généralistes, pas un scénario Devnet :
1. `demo_backfill` hydrate un échantillon borné de signatures confirmées ;
2. lextraction Core résout les instructions et comptes ;
3. `demo_decode_replay` sélectionne le Program ID `ProgM6JCCvbYkfKqJYHePx4xxSUSqJp7rh8Lyv7nk7S` et le décodeur `metadata.solana_program_metadata` ;
4. `materializeAfterDecode` active `materializer.metadata.solana_program_metadata` ;
5. la requête bornée des événements matérialisés permet de vérifier les faits produits.
Le registre générique du desktop fournit désormais ce décodeur et ce matérialiseur. `kb-pipeline-demo-scenarios` nintervient pas dans ce parcours historique.
## Lire et prévalider Solana Program Metadata
```rust
async fn read_program_metadata(
pool: &kb_onchain_transport::HttpEndpointPool,
account: kb_lib::MdPubkey,
) -> kb_core::Result<kb_pipeline::SolanaProgramMetadataStatefulReadResult> {
let request = kb_pipeline::SolanaProgramMetadataStatefulReadRequest {
query_role: "rpc".to_string(),
account,
expected_state: kb_pipeline::SolanaProgramMetadataExpectedAccountState::Any,
min_context_slot: std::option::Option::None,
max_data_bytes: kb_pipeline::MAX_SOLANA_PROGRAM_METADATA_STATEFUL_ACCOUNT_BYTES,
};
let result = kb_pipeline::read_solana_program_metadata_stateful_snapshot(pool, &request).await;
return match result {
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(error) => std::result::Result::Err(error),
};
}
```
Le préflight reçoit lintent typé, le plan produit par `ExMetadataSolanaProgramMetadataExecutor`, les snapshots confirmés et, pour les opérations qui allouent ou agrandissent un compte, une observation explicite du minimum de rent. Il refuse un plan `Deny`, mais les opérations dangereuses restent exécutables lorsquelles sont explicitement approuvées et que lenveloppe commune de sécurité est satisfaite.
Après simulation du message exact, `validate_solana_program_metadata_execution_readiness` vérifie les signers et lautorisation de soumission. Après confirmation, `inspect_solana_program_metadata_post_execution` compare les lectures avant/après pour les neuf opérations.
## Observateurs ## Observateurs
Les campagnes longues exposent des traits dobservation distincts pour le backfill, lextraction Core et le replay. Lobservateur peut publier la progression et participer à lannulation coopérative selon le contrat concerné. Les campagnes longues exposent des traits dobservation distincts pour le backfill, lextraction Core et le replay. Lobservateur peut publier la progression et participer à lannulation coopérative selon le contrat concerné.

View File

@@ -1,5 +1,5 @@
// file: kb-pipeline/src/lib.rs // file: kb-pipeline/src/lib.rs
// version: 18 // version: 20
#![forbid(unsafe_code)] #![forbid(unsafe_code)]
#![deny(unreachable_pub)] #![deny(unreachable_pub)]
@@ -11,20 +11,23 @@ mod backfill;
mod constants; mod constants;
mod core_extraction; mod core_extraction;
mod decode_replay; 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 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_stateful;
mod solana_token_2022_correlation; mod spl_ata_stateful;
mod solana_token_2022_crypto_preflight; mod spl_elgamal_registry_stateful;
mod solana_token_2022_execution_orchestration; mod spl_token_2022_correlation;
mod solana_token_2022_preflight; mod spl_token_2022_crypto_preflight;
mod solana_token_2022_proof_orchestration; mod spl_token_2022_execution_orchestration;
mod solana_token_2022_stateful; mod spl_token_2022_preflight;
mod solana_token_stateful; mod spl_token_2022_proof_orchestration;
mod spl_token_2022_stateful;
mod spl_token_stateful;
/// Address category used by one targeted backfill campaign. /// Address category used by one targeted backfill campaign.
pub use self::backfill::BackfillAddressKind; 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; pub use self::decode_replay::execute_decode_replay;
/// Creates one stable process-local decode campaign identifier. /// Creates one stable process-local decode campaign identifier.
pub use self::decode_replay::new_decode_campaign_id; 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. /// Pipeline stage identifier.
pub use self::plan::PipelineStage; pub use self::plan::PipelineStage;
/// Replay selection scope. /// Replay selection scope.
pub use self::plan::ReplayScope; 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. /// One machine-readable native Solana stateful readiness check.
pub use self::solana_stateful::SolanaCoreStatefulCheck; pub use self::solana_stateful::SolanaCoreStatefulCheck;
/// One contextual fact measured during native Solana stateful readiness. /// 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; pub use self::solana_stateful::SolanaCoreStatefulReadinessStatus;
/// Inspects state required before simulating one native Solana operation. /// Inspects state required before simulating one native Solana operation.
pub use self::solana_stateful::inspect_solana_core_stateful_readiness; 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// Migrated Token2022PreflightAccountReport contract.
pub use self::solana_token_2022_preflight::Token2022PreflightAccountReport; pub use self::spl_token_2022_preflight::Token2022PreflightAccountReport;
/// Migrated Token2022PreflightReport contract. /// Migrated Token2022PreflightReport contract.
pub use self::solana_token_2022_preflight::Token2022PreflightReport; pub use self::spl_token_2022_preflight::Token2022PreflightReport;
/// Migrated Token2022PreflightRequest contract. /// Migrated Token2022PreflightRequest contract.
pub use self::solana_token_2022_preflight::Token2022PreflightRequest; pub use self::spl_token_2022_preflight::Token2022PreflightRequest;
/// Migrated Token2022PreflightRequirement contract. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// Migrated Token2022StatefulContext contract.
pub use self::solana_token_2022_stateful::Token2022StatefulContext; pub use self::spl_token_2022_stateful::Token2022StatefulContext;
/// Migrated Token2022StatefulReadRequest contract. /// Migrated Token2022StatefulReadRequest contract.
pub use self::solana_token_2022_stateful::Token2022StatefulReadRequest; pub use self::spl_token_2022_stateful::Token2022StatefulReadRequest;
/// Migrated Token2022StatefulReadResult contract. /// Migrated Token2022StatefulReadResult contract.
pub use self::solana_token_2022_stateful::Token2022StatefulReadResult; pub use self::spl_token_2022_stateful::Token2022StatefulReadResult;
/// Migrated Token2022StatefulSnapshotBundle contract. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// 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. /// Canonical tracing target for pipeline orchestration.
pub(crate) use self::constants::TRACING_TARGET; pub(crate) use self::constants::TRACING_TARGET;

View File

@@ -1,5 +1,5 @@
// file: kb-pipeline/src/solana_metaplex_token_metadata_execution_orchestration.rs // file: kb-pipeline/src/metadata_metaplex_token_metadata_execution_orchestration.rs
// version: 1 // version: 2
//! Simulation-first Metaplex Token Metadata execution orchestration. //! Simulation-first Metaplex Token Metadata execution orchestration.

View File

@@ -1,5 +1,5 @@
// file: kb-pipeline/src/solana_metaplex_token_metadata_preflight.rs // file: kb-pipeline/src/metadata_metaplex_token_metadata_preflight.rs
// version: 2 // version: 3
//! Stateful Metaplex Token Metadata preflight contracts. //! Stateful Metaplex Token Metadata preflight contracts.

View File

@@ -1,5 +1,5 @@
// file: kb-pipeline/src/solana_metaplex_token_metadata_stateful.rs // file: kb-pipeline/src/metadata_metaplex_token_metadata_stateful.rs
// version: 3 // version: 4
//! Bounded Metaplex Token Metadata account reads and canonical state projections. //! Bounded Metaplex Token Metadata account reads and canonical state projections.

View File

@@ -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
);
}
}

View 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());
}
}

View 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"))
);
}
}
}

View File

@@ -1,5 +1,5 @@
// file: kb-pipeline/src/solana_ata_stateful.rs // file: kb-pipeline/src/spl_ata_stateful.rs
// version: 3 // version: 4
//! Stateful Localnet and Devnet readiness checks for ATA operations. //! Stateful Localnet and Devnet readiness checks for ATA operations.

View File

@@ -1,5 +1,5 @@
// file: kb-pipeline/src/solana_elgamal_registry_stateful.rs // file: kb-pipeline/src/spl_elgamal_registry_stateful.rs
// version: 4 // version: 5
//! Contextual SPL ElGamal registry account-state validation and materialization routing. //! Contextual SPL ElGamal registry account-state validation and materialization routing.

View File

@@ -1,5 +1,5 @@
// file: kb-pipeline/src/solana_token_2022_correlation.rs // file: kb-pipeline/src/spl_token_2022_correlation.rs
// version: 2 // version: 3
//! Deterministic Token-2022 instruction-to-state correlation. //! Deterministic Token-2022 instruction-to-state correlation.

View File

@@ -1,5 +1,5 @@
// file: kb-pipeline/src/solana_token_2022_crypto_preflight.rs // file: kb-pipeline/src/spl_token_2022_crypto_preflight.rs
// version: 3 // version: 4
//! Bounded cryptographic preflight for Token-2022 proof context-state accounts. //! Bounded cryptographic preflight for Token-2022 proof context-state accounts.

View File

@@ -1,5 +1,5 @@
// file: kb-pipeline/src/solana_token_2022_execution_orchestration.rs // file: kb-pipeline/src/spl_token_2022_execution_orchestration.rs
// version: 3 // version: 4
//! Final Token-2022 execution-readiness and stateful postcondition contracts. //! Final Token-2022 execution-readiness and stateful postcondition contracts.

View File

@@ -1,5 +1,5 @@
// file: kb-pipeline/src/solana_token_2022_preflight.rs // file: kb-pipeline/src/spl_token_2022_preflight.rs
// version: 3 // version: 4
//! Bounded Token-2022 stateful preflight orchestration. //! Bounded Token-2022 stateful preflight orchestration.

View File

@@ -1,5 +1,5 @@
// file: kb-pipeline/src/solana_token_2022_proof_orchestration.rs // file: kb-pipeline/src/spl_token_2022_proof_orchestration.rs
// version: 4 // version: 5
//! Deterministic orchestration contract for mixed Token-2022 proof locations. //! Deterministic orchestration contract for mixed Token-2022 proof locations.

View File

@@ -1,5 +1,5 @@
// file: kb-pipeline/src/solana_token_2022_stateful.rs // file: kb-pipeline/src/spl_token_2022_stateful.rs
// version: 7 // version: 8
//! Contextual Token-2022 account-state validation and materialization routing. //! Contextual Token-2022 account-state validation and materialization routing.

View File

@@ -1,5 +1,5 @@
// file: kb-pipeline/src/solana_token_stateful.rs // file: kb-pipeline/src/spl_token_stateful.rs
// version: 1 // version: 2
//! Stateful Localnet and Devnet readiness checks for classic SPL Token operations. //! Stateful Localnet and Devnet readiness checks for classic SPL Token operations.

View File

@@ -0,0 +1,31 @@
// file: kb-pipeline/tests/external_metadata_solana_program_pipeline_api.rs
// version: 2
//! External-only coverage for the public Solana Program Metadata pipeline contract.
#[test]
fn external_consumers_use_only_public_program_metadata_pipeline_exports() {
let account = kb_lib::MdPubkey(solana_pubkey::Pubkey::new_from_array([7_u8; 32]).to_string());
let request = kb_pipeline::SolanaProgramMetadataStatefulReadRequest {
query_role: "rpc".to_string(),
account: account.clone(),
expected_state: kb_pipeline::SolanaProgramMetadataExpectedAccountState::Any,
min_context_slot: std::option::Option::None,
max_data_bytes: kb_pipeline::MAX_SOLANA_PROGRAM_METADATA_STATEFUL_ACCOUNT_BYTES,
};
assert!(request.validate().is_ok());
let postcondition = kb_pipeline::SolanaProgramMetadataPostcondition {
code: "metadata.solana_program_metadata.external_api".to_string(),
account,
status: kb_pipeline::SolanaProgramMetadataPostconditionStatus::NotApplicable,
diagnostic: "external API coverage".to_string(),
};
assert_eq!(
kb_pipeline::summarize_solana_program_metadata_postconditions(&[postcondition]),
kb_pipeline::SolanaProgramMetadataPostconditionStatus::NotApplicable
);
const {
assert!(kb_pipeline::MAX_SOLANA_PROGRAM_METADATA_EXECUTION_SIGNERS > 0);
assert!(kb_pipeline::MAX_SOLANA_PROGRAM_METADATA_PREFLIGHT_ACCOUNTS > 0);
}
}

View File

@@ -0,0 +1,77 @@
{
"schemaVersion": 1,
"programId": "ProgM6JCCvbYkfKqJYHePx4xxSUSqJp7rh8Lyv7nk7S",
"pipelineCrate": "kb-pipeline",
"demoScenarioCrate": "kb-pipeline-demo-scenarios",
"historicalValidationFlow": [
"demo_backfill",
"core_extraction",
"demo_decode_replay",
"materialized_event_query"
],
"operations": [
{
"code": "metadata.solana_program_metadata.write",
"before": ["buffer", "optional_source_buffer"],
"rentObservationPolicy": "none",
"explicitApprovalRequired": true,
"after": "buffer_bytes_match_source_at_offset"
},
{
"code": "metadata.solana_program_metadata.initialize",
"before": ["vacant_or_buffer"],
"rentObservationPolicy": "exact_target_space",
"explicitApprovalRequired": false,
"after": "metadata_header_and_content_match_intent"
},
{
"code": "metadata.solana_program_metadata.set_authority",
"before": ["buffer_or_metadata"],
"rentObservationPolicy": "none",
"explicitApprovalRequired": true,
"after": "authority_matches_intent"
},
{
"code": "metadata.solana_program_metadata.set_data",
"before": ["metadata", "optional_source_buffer"],
"rentObservationPolicy": "exact_target_space",
"explicitApprovalRequired": true,
"after": "metadata_descriptors_and_content_match_intent"
},
{
"code": "metadata.solana_program_metadata.set_immutable",
"before": ["metadata"],
"rentObservationPolicy": "none",
"explicitApprovalRequired": true,
"after": "metadata_is_immutable"
},
{
"code": "metadata.solana_program_metadata.trim",
"before": ["buffer_or_metadata"],
"rentObservationPolicy": "none",
"explicitApprovalRequired": true,
"after": "same_state_kind_with_non_increasing_space"
},
{
"code": "metadata.solana_program_metadata.close",
"before": ["buffer_or_metadata"],
"rentObservationPolicy": "none",
"explicitApprovalRequired": true,
"after": "account_is_missing"
},
{
"code": "metadata.solana_program_metadata.allocate",
"before": ["vacant"],
"rentObservationPolicy": "exact_target_space",
"explicitApprovalRequired": false,
"after": "buffer_header_matches_authority_seed_and_canonicity"
},
{
"code": "metadata.solana_program_metadata.extend",
"before": ["buffer_or_metadata"],
"rentObservationPolicy": "exact_target_space",
"explicitApprovalRequired": false,
"after": "same_state_kind_with_exact_extended_space"
}
]
}