From dcc1d382d97c4f89b4489b2caa831b8aeda68bda Mon Sep 17 00:00:00 2001 From: SinuS Von SifriduS Date: Fri, 7 Aug 2026 07:14:49 +0200 Subject: [PATCH] v0.4.8-pre.011 --- Cargo.toml | 6 +- docs/README.md | 7 +- ..._TOKEN_2022_METADATA_COMPLETENESS_AUDIT.md | 62 +++ ...TADATA_AND_TOKEN_2022_COMPLETENESS_PLAN.md | 14 +- ...OGRAM_METADATA_DEVNET_VALIDATION_REPORT.md | 59 +++ .../evidence/v0.4.8/pre.010/devnet/README.md | 17 + .../evidence/v0.4.8/pre.010/devnet/SHA256SUMS | 1 + .../devnet_evidence_v0.4.8-pre.010-01.zip | Bin 0 -> 91225 bytes kb-lib/CHANGELOG.md | 15 +- kb-lib/README.md | 4 +- kb-lib/USAGE.md | 6 +- kb-lib/src/decoder.rs | 4 +- kb-lib/src/decoder/spl.rs | 4 +- kb-lib/src/decoder/spl/token_2022.rs | 4 +- kb-lib/src/decoder/spl/token_2022/state.rs | 9 +- kb-lib/src/executor.rs | 6 +- kb-lib/src/executor/api/execution.rs | 5 +- kb-lib/src/executor/safety/evaluation.rs | 3 +- .../src/executor/solana/transaction/core.rs | 4 +- kb-lib/src/executor/spl.rs | 6 +- kb-lib/src/executor/spl/token_2022.rs | 6 +- kb-lib/src/executor/spl/token_2022/builder.rs | 171 +++++++- .../src/executor/spl/token_2022/executor.rs | 152 ++++--- kb-lib/src/executor/spl/token_2022/intent.rs | 33 +- kb-lib/src/lib.rs | 8 +- kb-onchain-transport/CHANGELOG.md | 7 +- kb-onchain-transport/src/execution_rpc.rs | 16 +- kb-pipeline-demo-scenarios/CHANGELOG.md | 7 +- .../src/metadata_solana_program_validation.rs | 8 +- .../src/solana_execution.rs | 3 +- kb-pipeline/CHANGELOG.md | 11 +- kb-pipeline/Cargo.toml | 3 +- kb-pipeline/README.md | 4 +- kb-pipeline/USAGE.md | 6 +- kb-pipeline/src/lib.rs | 13 +- kb-pipeline/src/spl_token_2022_metadata.rs | 376 ++++++++++++++++ .../OPERATION_NAMING_MATRIX.json | 22 + ...RAM_METADATA_DEVNET_VALIDATION_MATRIX.json | 405 +++++++++++++++++- 38 files changed, 1358 insertions(+), 129 deletions(-) create mode 100644 docs/audits/V0_4_8_PRE_011_TOKEN_2022_METADATA_COMPLETENESS_AUDIT.md create mode 100644 docs/validation/V0_4_8_PRE_010_SOLANA_PROGRAM_METADATA_DEVNET_VALIDATION_REPORT.md create mode 100644 docs/validation/evidence/v0.4.8/pre.010/devnet/README.md create mode 100644 docs/validation/evidence/v0.4.8/pre.010/devnet/SHA256SUMS create mode 100644 docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip create mode 100644 kb-pipeline/src/spl_token_2022_metadata.rs diff --git a/Cargo.toml b/Cargo.toml index 469de47..8c545ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ # file: Cargo.toml -# version: 32 +# version: 34 [workspace] resolver = "3" @@ -18,7 +18,7 @@ members = [ ] [workspace.package] -version = "0.4.8-pre.10" +version = "0.4.8-pre.11" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/khadhroony-bot3" @@ -99,7 +99,7 @@ tracing = { version = "^0.1", features = [] } tracing-appender = { version = "^0.2", features = [] } tracing-subscriber = { version = "^0.3", features = ["ansi", "env-filter", "chrono", "serde", "json"] } wincode = { version = "^0.5", features = ["alloc", "bytes", "derive", "indexmap", "smallvec", "uuid", "uuid-serde-compat"] } -ts-rs = { version = "^12.0", features = ["bigdecimal", "bson", "bytes", "chrono", "indexmap", "ordered-float", "serde_json", "tokio", "url", "uuid"] } +ts-rs = { version = "^12.0", features = ["bigdecimal", "bson", "bytes", "chrono", "indexmap", "ordered-float", "serde_json", "serde-json-impl", "tokio", "url", "uuid"] } yellowstone-grpc-client = { version = "^13.1", features = [] } yellowstone-grpc-proto = { version = "^12.5", features = [] } uuid = { version = "^1.23", features = ["v4", "serde"] } diff --git a/docs/README.md b/docs/README.md index 0807fb3..690e0fe 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,5 +1,5 @@ - + # Documentation active de Khadhroony Bot3 @@ -100,6 +100,7 @@ Les onze crates possèdent désormais `README.md`, `TODO.md`, `USAGE.md` et `CHA - [`validation/WEBSOCKET_MAINNET_RESEARCH_VALIDATION_REPORT.md`](validation/WEBSOCKET_MAINNET_RESEARCH_VALIDATION_REPORT.md) ; - [`validation/MAINNET_RESEARCH_BACKFILL_VALIDATION_SCENARIO.md`](validation/MAINNET_RESEARCH_BACKFILL_VALIDATION_SCENARIO.md) ; - [`validation/V0_4_8_PRE_008_SOLANA_PROGRAM_METADATA_MAINNET_REPLAY_REPORT.md`](validation/V0_4_8_PRE_008_SOLANA_PROGRAM_METADATA_MAINNET_REPLAY_REPORT.md) ; +- [`validation/V0_4_8_PRE_010_SOLANA_PROGRAM_METADATA_DEVNET_VALIDATION_REPORT.md`](validation/V0_4_8_PRE_010_SOLANA_PROGRAM_METADATA_DEVNET_VALIDATION_REPORT.md) — preuve de deux campagnes Devnet complètes couvrant les neuf opérations Solana Program Metadata ; ## Audits actifs @@ -109,7 +110,8 @@ Les onze crates possèdent désormais `README.md`, `TODO.md`, `USAGE.md` et `CHA - [`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) ; -- [`Audit 0.4.8-pre.009 — scénarios Devnet Solana Program Metadata`](audits/V0_4_8_PRE_009_METADATA_SOLANA_PROGRAM_DEVNET_SCENARIOS_AUDIT.md). +- [`Audit 0.4.8-pre.009 — scénarios Devnet Solana Program Metadata`](audits/V0_4_8_PRE_009_METADATA_SOLANA_PROGRAM_DEVNET_SCENARIOS_AUDIT.md) ; +- [`Audit 0.4.8-pre.011 — complétude Token-2022 Token Metadata`](audits/V0_4_8_PRE_011_TOKEN_2022_METADATA_COMPLETENESS_AUDIT.md). ## Plans de version actifs @@ -118,3 +120,4 @@ Les onze crates possèdent désormais `README.md`, `TODO.md`, `USAGE.md` et `CHA ## Prompt de reprise - [`Prompt actif 0.4.8`](../prompts/028_v0_4_8_spl_token_metadata_and_offchain_decision.md). + diff --git a/docs/audits/V0_4_8_PRE_011_TOKEN_2022_METADATA_COMPLETENESS_AUDIT.md b/docs/audits/V0_4_8_PRE_011_TOKEN_2022_METADATA_COMPLETENESS_AUDIT.md new file mode 100644 index 0000000..8884dee --- /dev/null +++ b/docs/audits/V0_4_8_PRE_011_TOKEN_2022_METADATA_COMPLETENESS_AUDIT.md @@ -0,0 +1,62 @@ + + + +# Audit `0.4.8-pre.011` — complétude Token-2022 Token Metadata + +## Portée + +Cette prerelease ferme les écarts d’exécution synthétique des cinq instructions `spl-token-metadata-interface` implémentées par Token-2022. Elle ne crée aucun Program ID metadata autonome et ne lance aucune campagne Devnet, réservée à `pre.012`. + +## Résultat fonctionnel + +| Instruction | Wire | Intent | Builder | Retour/stateful | Matérialisation | +|-------------------|-----:|-------:|--------:|------------------------:|---------------------------------------:| +| `Initialize` | oui | oui | oui | snapshot TLV | instruction + snapshot | +| `UpdateField` | oui | oui | oui | snapshot TLV | instruction + snapshot | +| `RemoveKey` | oui | oui | oui | snapshot TLV | instruction + snapshot | +| `UpdateAuthority` | oui | oui | oui | postcondition authority | instruction + snapshot | +| `Emit` | oui | oui | oui | `returnData` borné | fait d’instruction, sans faux snapshot | + +Les cinq opérations sont présentes dans les intents, les builders et les capacités de l’exécuteur. `UpdateAuthority` encode l’autorité nullable avec le type officiel. `Emit` accepte la forme complète sans plage ou une plage explicite d’au plus 1 024 octets, refuse `start > end`, les plages trop grandes et `start` sans `end`, puis conserve le `returnData` JSON-RPC sans dépendre des logs. + +Un retour complet est décodé avec consommation exacte du format `TokenMetadata`. Un retour partiel conserve seulement les octets et ne prétend pas représenter un état complet. La postcondition d’autorité lit le snapshot TLV autoritatif et couvre une nouvelle autorité ou sa suppression. + +## Ownership des matérialisateurs + +- `MtMetadataToken2022Materializer` possède les neuf faits instructionnels Token Metadata et Token Group ; +- les snapshots `TokenMetadata`, `TokenGroup` et `TokenGroupMember` restent distincts sous `metadata/token_2022/` ; +- `InitializeMetadataPointer` et `UpdateMetadataPointer` restent possédés par le matérialiseur d’administration ; +- aucun fait n’est dupliqué entre le matérialiseur metadata spécialisé et le matérialiseur d’administration. + +## Correctifs de la livraison + +- `pre.011-fix-001` active `serde-json-impl` pour le derive TS-RS de `serde_json::Value` et refuse la plage `Emit` ouverte avec `start` sans `end` ; +- `pre.011-fix-002` remplace le variant inexistant `ExApiExecutionBlockhashKind::Recent` par le variant canonique `Latest` dans la fixture `kb-pipeline` ; +- `pre.011-fix-003` est remplacé et ne doit pas être appliqué comme base finale ; +- `pre.011-fix-004` rattache `update_token_metadata_authority` et `emit_token_metadata` à la famille `token_metadata` par un helper testé directement ; +- `pre.011-fix-005` clôt les documents et ajoute les régressions directes couvrant `new_authority = None`, `Emit` sans plage et la plage maximale exactement égale à 1 024 octets. +- `pre.011-fix-006` corrige uniquement les deux assertions de `operation_code` introduites par `fix-005` : les plans utilisent la hiérarchie canonique `spl.token_2022.*`, pas les noms courts des opérations. + +## Validation locale observée le 6 août 2026 + +Avant `fix-005`, l’état fonctionnel final de `fix-004` a réussi : + +```text +cargo fmt --all réussi +cargo clippy --all-targets réussi +cargo check --workspace réussi +python3 scripts/audit_rust_workspace_rules.py propre +cargo test -p kb-lib 693 unitaires + 8 intégration, 0 échec +cargo test -p kb-onchain-transport 118 tests, 0 échec +cargo test -p kb-pipeline 105 unitaires + 1 API externe, 0 échec +cargo test -p kb-pipeline-demo-scenarios 62 + 1 CLI + 1 API externe, 0 échec +cargo test --workspace 1 285 tests, 0 échec, doc-tests propres +``` + +Le `cargo clean` préalable a supprimé les artefacts obsolètes qui empêchaient Cargo de reconstruire le fichier `executor.rs` extrait depuis l’archive. La recompilation complète a ensuite exécuté le test direct `token_metadata_operations_share_matrix_family` et le test général de parité de matrice avec succès. + +`fix-005` ne modifie aucun code de production et ajoute deux tests unitaires. La première reprise locale a confirmé que tout le workspace compilait, passait Clippy et les audits, mais ces deux nouveaux tests échouaient uniquement parce qu’ils attendaient les noms courts `update_token_metadata_authority` et `emit_token_metadata` au lieu des codes canoniques `spl.token_2022.update_token_metadata_authority` et `spl.token_2022.emit_token_metadata`. `fix-006` corrige ces deux assertions sans modifier le comportement de production. Le nombre attendu de tests unitaires `kb-lib` reste 695. + +## Frontières maintenues + +Les fixtures, soumissions Devnet, confirmations et preuves réseau restent dans `pre.012`. Le sous-panneau desktop Token-2022 reste dans `pre.013`. Aucune résolution off-chain n’est ajoutée par cette phase. diff --git a/docs/plans/V0_4_8_SOLANA_PROGRAM_METADATA_AND_TOKEN_2022_COMPLETENESS_PLAN.md b/docs/plans/V0_4_8_SOLANA_PROGRAM_METADATA_AND_TOKEN_2022_COMPLETENESS_PLAN.md index df23060..26b90ed 100644 --- a/docs/plans/V0_4_8_SOLANA_PROGRAM_METADATA_AND_TOKEN_2022_COMPLETENESS_PLAN.md +++ b/docs/plans/V0_4_8_SOLANA_PROGRAM_METADATA_AND_TOKEN_2022_COMPLETENESS_PLAN.md @@ -1,11 +1,11 @@ - + # Plan `0.4.8` — Solana Program Metadata et complétude Token-2022 ## 1. Statut et rôle -Ce document est le livrable principal de `0.4.8-pre.001`, réconcilié après l’audit contractuel de `0.4.8-pre.002`, les modèles de `pre.003`, les décodeurs de comptes de `pre.004`, le décodeur d’instructions de `pre.005`, la matérialisation de `pre.006`, l’exécuteur de `pre.007`, l’orchestration généraliste de `pre.008`, les scénarios réutilisables de `pre.009` et leur intégration desktop de `pre.010`. Il constitue le plan vivant de la version et reste modifiable lorsque l’audit du code, des interfaces officielles, de l’IDL ou des validations Devnet impose un ajustement. +Ce document est le livrable principal de `0.4.8-pre.001`, réconcilié après l’audit contractuel de `0.4.8-pre.002`, les modèles de `pre.003`, les décodeurs de comptes de `pre.004`, le décodeur d’instructions de `pre.005`, la matérialisation de `pre.006`, l’exécuteur de `pre.007`, l’orchestration généraliste de `pre.008`, les scénarios réutilisables de `pre.009`, leur intégration desktop et leur campagne Devnet de `pre.010`, puis la complétude Token-2022 Token Metadata de `pre.011`. Il constitue le plan vivant de la version et reste modifiable lorsque l’audit du code, des interfaces officielles, de l’IDL 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. @@ -306,7 +306,7 @@ Travaux parallèles intégrés : les fonctions de matérialisation Metaplex exis - matrice Devnet conservative à `not_run` avant exécution réelle ; - aucune modification de `demo_execution_metadata` dans cette prerelease. -### `0.4.8-pre.010` — intégration desktop et campagne Devnet Solana Program Metadata — terminé côté delta +### `0.4.8-pre.010` — intégration desktop et campagne Devnet Solana Program Metadata — terminé et validé - sous-panneau `ProgM6…` séparé dans `demo_execution_metadata`, consommant exclusivement les runners de `kb-pipeline-demo-scenarios` ; - profil Devnet synchronisé avec la section Metaplex et préparation PostgreSQL réutilisée ; @@ -320,9 +320,11 @@ Travaux parallèles intégrés : les fonctions de matérialisation Metaplex exis - borne des lectures stateful complètes alignée sur les `65536` octets acceptés par le transport, sans réduire la capacité offline du décodeur ; - retry borné des réponses HTTP ou JSON-RPC `429` afin que la campagne puisse respecter les limites de l’endpoint public Devnet ; - la validation historique `backfill → extraction → replay → matérialisation` reste la preuve de décodage de `pre.008` ; la campagne Devnet fournit séparément les preuves d’exécution et les snapshots stateful confirmés ; -- la matrice Devnet reste `not_run` jusqu’à l’exécution opérateur réelle, particulièrement pour `Trim` et `Extend`. +- validation opérateur finale : deux campagnes indépendantes, 22 transactions confirmées et 18 postconditions `Confirmed` ; +- `Extend` et `Trim` sont désormais observées et validées sur Devnet ; +- preuve brute et rapport conservés sous `docs/validation/evidence/v0.4.8/pre.010/devnet/` et `docs/validation/V0_4_8_PRE_010_SOLANA_PROGRAM_METADATA_DEVNET_VALIDATION_REPORT.md`. -### `0.4.8-pre.011` — complétude Token-2022 Token Metadata +### `0.4.8-pre.011` — complétude Token-2022 Token Metadata — terminée, validation fonctionnelle acquise - fermeture des écarts d’intents/builders/exécution confirmés pour `UpdateAuthority` et `Emit`, après réaudit des cinq instructions `Initialize`, `UpdateField`, `RemoveKey`, `UpdateAuthority` et `Emit` ; - conservation des snapshots dédiés `metadata/token_2022/` pour `TokenMetadata`, `TokenGroup` et `TokenGroupMember` ; @@ -330,6 +332,8 @@ Travaux parallèles intégrés : les fonctions de matérialisation Metaplex exis - maintien de `InitializeMetadataPointer` et `UpdateMetadataPointer` dans le matérialiseur d’administration qui en possède déjà les faits ; - tests synthétiques et stateful. +Résultat durable : les cinq instructions de `spl-token-metadata-interface` disposent désormais d’intents et de builders exacts. `UpdateAuthority` accepte une nouvelle autorité nullable ; `Emit` valide une plage maximale de 1 024 octets, refuse une plage ouverte avec `start` sans `end`, conserve le `returnData` de simulation et décode uniquement les réponses complètes. La postcondition stateful vérifie l’autorité finale depuis le snapshot TLV autoritatif. Les interfaces Token Group restent possédées par le même matérialiseur spécialisé, tandis que Metadata Pointer reste au matérialiseur d’administration. `pre.011-fix-001` active l’implémentation TS-RS de `serde_json::Value` via `serde-json-impl` et ferme la plage `Emit` ouverte ; `pre.011-fix-002` aligne la fixture de `kb-pipeline` sur `ExApiExecutionBlockhashKind::Latest` ; `pre.011-fix-004` remplace `fix-003`, rattache `UpdateAuthority` et `Emit` à l’entrée `token_metadata` et ajoute un test direct de cette projection. Après nettoyage intégral du cache Cargo, `cargo test -p kb-lib` réussit avec 693 tests unitaires et toutes les APIs externes, puis `cargo test --workspace` réussit avec 1 285 tests et tous les doc-tests. `pre.011-fix-005` clôt la documentation et ajoute les régressions directes pour l’autorité `None`, l’émission complète et la plage maximale de 1 024 octets. Leur première reprise a révélé uniquement deux attentes de nommage incorrectes dans les tests eux-mêmes : les plans exposent les codes canoniques préfixés `spl.token_2022.*`. `pre.011-fix-006` corrige ces deux assertions sans toucher au code de production ; sa validation ciblée constitue le dernier contrôle avant de commencer `pre.012`. + ### `0.4.8-pre.012` — campagne Devnet Token-2022 Token Metadata - fixture mint ; diff --git a/docs/validation/V0_4_8_PRE_010_SOLANA_PROGRAM_METADATA_DEVNET_VALIDATION_REPORT.md b/docs/validation/V0_4_8_PRE_010_SOLANA_PROGRAM_METADATA_DEVNET_VALIDATION_REPORT.md new file mode 100644 index 0000000..517faaf --- /dev/null +++ b/docs/validation/V0_4_8_PRE_010_SOLANA_PROGRAM_METADATA_DEVNET_VALIDATION_REPORT.md @@ -0,0 +1,59 @@ + + + +# Validation Devnet Solana Program Metadata — `0.4.8-pre.010` + +## 1. Objet + +Ce rapport clôt la validation réseau de l’intégration Solana Program Metadata livrée entre `0.4.8-pre.009` et `0.4.8-pre.010`. + +La preuve brute est conservée sous [`evidence/v0.4.8/pre.010/devnet/`](evidence/v0.4.8/pre.010/devnet/README.md). + +## 2. Environnement observé + +- cluster : Solana Devnet ; +- endpoint : profil `local_devnet` ; +- autorité opérateur : wallet temporaire persistant du profil ; +- programme : `ProgM6JCCvbYkfKqJYHePx4xxSUSqJp7rh8Lyv7nk7S` ; +- mode : simulation obligatoire, soumission contrôlée, confirmation, lecture stateful et postcondition. + +Le nom source de l’archive indiquait `mainnet_research`, mais son contenu utilise Devnet. Elle est donc classée comme preuve Devnet. + +## 3. Campagnes exécutées + +Deux fixtures indépendantes ont été préparées. Chaque campagne a exécuté : + +1. deux transferts de préfinancement ; +2. le parcours Buffer `Allocate → Extend → Write → SetAuthority → Trim → Close` ; +3. le parcours Metadata `Initialize → SetData → SetImmutable`. + +Le corpus contient ainsi : + +- 2 campagnes complètes ; +- 22 transactions confirmées ; +- 18 opérations Solana Program Metadata ; +- 18 postconditions `Confirmed` ; +- 0 fichier d’erreur non vide ; +- 0 réponse HTTP ou JSON-RPC `429` observée. + +## 4. Résultats par opération + +| Opération | Exécutions confirmées | Postconditions confirmées | +|----------------|----------------------:|--------------------------:| +| `Allocate` | 2 | 2 | +| `Extend` | 2 | 2 | +| `Write` | 2 | 2 | +| `SetAuthority` | 2 | 2 | +| `Trim` | 2 | 2 | +| `Close` | 2 | 2 | +| `Initialize` | 2 | 2 | +| `SetData` | 2 | 2 | +| `SetImmutable` | 2 | 2 | + +`Close` est validée par l’absence finale du compte. Les autres opérations sont validées par une lecture stateful et une projection cohérente avec la postcondition attendue. + +## 5. Conclusion + +La couverture exécutable des neuf instructions stables Solana Program Metadata est validée sur Devnet, y compris `Extend` et `Trim`, qui n’avaient pas été observées dans l’échantillon historique mainnet de `pre.008`. + +La surface Solana Program Metadata peut être considérée comme complète pour `0.4.8`, sous réserve des réconciliations transversales et documentaires prévues avant la clôture finale de la version. diff --git a/docs/validation/evidence/v0.4.8/pre.010/devnet/README.md b/docs/validation/evidence/v0.4.8/pre.010/devnet/README.md new file mode 100644 index 0000000..b6809ac --- /dev/null +++ b/docs/validation/evidence/v0.4.8/pre.010/devnet/README.md @@ -0,0 +1,17 @@ + + + +# Preuve Devnet `0.4.8-pre.010` + +Ce répertoire conserve l’archive opérateur de la validation finale de Solana Program Metadata sur Devnet. + +## Contenu + +- `devnet_evidence_v0.4.8-pre.010-01.zip` : archive originale fournie après deux campagnes complètes réussies ; +- `SHA256SUMS` : empreinte de contrôle de l’archive conservée. + +L’archive a été renommée lors de son intégration : son nom source contenait `mainnet_research`, alors que les endpoints et les transactions observés appartiennent à Devnet. Son contenu n’a pas été modifié. + +Cette preuve est documentaire. Aucun test, runner ou composant runtime ne la charge. + +Le rapport associé est [`V0_4_8_PRE_010_SOLANA_PROGRAM_METADATA_DEVNET_VALIDATION_REPORT.md`](../../../../V0_4_8_PRE_010_SOLANA_PROGRAM_METADATA_DEVNET_VALIDATION_REPORT.md). diff --git a/docs/validation/evidence/v0.4.8/pre.010/devnet/SHA256SUMS b/docs/validation/evidence/v0.4.8/pre.010/devnet/SHA256SUMS new file mode 100644 index 0000000..e6f43c7 --- /dev/null +++ b/docs/validation/evidence/v0.4.8/pre.010/devnet/SHA256SUMS @@ -0,0 +1 @@ +475e10aec63ccbd17627dd30ba2fb66f83374b8a6f70fb6f3cb26a93a534ec1c devnet_evidence_v0.4.8-pre.010-01.zip diff --git a/docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip b/docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip new file mode 100644 index 0000000000000000000000000000000000000000..8c48a4bfbc929d43e2b3c7ec65753fe71cdc5dde GIT binary patch literal 91225 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

6dNo7C) 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 kAzmx= - + # CHANGELOG — kb-lib +## `0.4.8-pre.011` — complétude Token-2022 Token Metadata + +- ajoute les intents et builders exacts `UpdateTokenMetadataAuthority` et `EmitTokenMetadata` ; +- encode l’autorité nullable via le contrat officiel et borne les plages `Emit` à 1 024 octets ; +- conserve le `returnData` de simulation et expose le parseur strict de la valeur `TokenMetadata` ; +- corrige le feature-gating TS-RS du `serde_json::Value` exposé par le résultat de simulation afin que le derive `TS` et la régénération des bindings utilisent `JsonValue` ; +- refuse les plages `Emit` ouvertes avec `start` sans `end`, dont la taille ne peut pas être bornée avant simulation ; +- aligne le test de parité de la matrice d’exécution sur les cinq opérations de `spl-token-metadata-interface`, en rattachant également `UpdateAuthority` et `Emit` à l’entrée `token_metadata` ; +- extrait le mapping capacités → familles de matrice dans un helper testé directement, afin de rendre explicite et vérifiable le rattachement de `update_token_metadata_authority` et `emit_token_metadata` à `token_metadata` ; +- ajoute les régressions de clôture couvrant l’autorité nullable `None`, la forme `Emit` complète sans plage et la plage maximale exactement égale à 1 024 octets. +- corrige dans `pre.011-fix-006` les deux attentes de nommage de ces régressions afin de vérifier les codes d’opération canoniques `spl.token_2022.update_token_metadata_authority` et `spl.token_2022.emit_token_metadata`. + + ## 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 à d’autres instructions ne sont plus rejetés ; diff --git a/kb-lib/README.md b/kb-lib/README.md index 2d7d024..64d71d2 100644 --- a/kb-lib/README.md +++ b/kb-lib/README.md @@ -1,5 +1,5 @@ - + # kb-lib @@ -103,3 +103,5 @@ Le registre ElGamal n’est pas déclaré validé réellement sur réseau. - [Matrice de matérialisation Solana Program Metadata](../test-fixtures/contract-matrices/SOLANA_PROGRAM_METADATA_MATERIALIZATION_MATRIX.json) - [Matrice d’exécution Solana Program Metadata](../test-fixtures/contract-matrices/SOLANA_PROGRAM_METADATA_EXECUTOR_MATRIX.json) - [Audit de l’inventaire et de l’historique des instructions](../docs/audits/V0_4_8_PRE_005_SOLANA_PROGRAM_METADATA_INSTRUCTION_HISTORY_AUDIT.md) + +- complétude d’exécution des cinq instructions Token-2022 Token Metadata, y compris `UpdateAuthority` et `Emit` ; diff --git a/kb-lib/USAGE.md b/kb-lib/USAGE.md index 95d91ce..488122c 100644 --- a/kb-lib/USAGE.md +++ b/kb-lib/USAGE.md @@ -1,5 +1,5 @@ - + # Utilisation de kb-lib @@ -414,3 +414,7 @@ Pour un NFT classique créé par le runner : - un mint dont le PDA metadata est déjà créé ne peut pas être réutilisé pour un second `Create`. La matérialisation Metaplex est optionnelle. Lorsqu’elle est activée après une soumission confirmée, chaque compte déclaré dans les lectures de postcondition est relu, validé, décodé canoniquement puis projeté comme snapshot d’état. Aucun contenu URI off-chain n’est téléchargé par ce parcours. + +## Token-2022 Token Metadata complet + +Les opérations `InitializeTokenMetadata`, `UpdateTokenMetadataField`, `RemoveTokenMetadataKey`, `UpdateTokenMetadataAuthority` et `EmitTokenMetadata` sont constructibles. `EmitTokenMetadata` est en lecture seule et ne réclame pas une matérialisation post-exécution. diff --git a/kb-lib/src/decoder.rs b/kb-lib/src/decoder.rs index 5e67faf..571e7f2 100644 --- a/kb-lib/src/decoder.rs +++ b/kb-lib/src/decoder.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder.rs -// version: 16 +// version: 17 //! Consolidated decoder modules. @@ -745,6 +745,8 @@ pub(crate) use self::spl::decoder_spl_token_2022_decode; pub(crate) use self::spl::decoder_spl_token_2022_entry_for_tag; /// Parses one Token-2022 Mint, Account, or Multisig state. pub use self::spl::decoder_spl_token_2022_parse_token_2022_state; +/// Parses one exact Token Metadata interface value. +pub use self::spl::decoder_spl_token_2022_parse_token_metadata_value; /// Returns the first byte of one retained Token-2022 payload. pub(crate) use self::spl::decoder_spl_token_2022_payload_tag; /// Decodes one bounded classic SPL Token instruction. diff --git a/kb-lib/src/decoder/spl.rs b/kb-lib/src/decoder/spl.rs index 6519ed5..05265ce 100644 --- a/kb-lib/src/decoder/spl.rs +++ b/kb-lib/src/decoder/spl.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/spl.rs -// version: 10 +// version: 11 //! `spl` decoder family. @@ -147,5 +147,7 @@ pub(crate) use self::token_2022::decoder_spl_token_2022_decode; pub(crate) use self::token_2022::decoder_spl_token_2022_entry_for_tag; /// Parses one Token-2022 Mint, Account, or Multisig state. pub use self::token_2022::decoder_spl_token_2022_parse_token_2022_state; +/// Parses one exact Token Metadata interface value. +pub use self::token_2022::decoder_spl_token_2022_parse_token_metadata_value; /// Returns the first byte of one retained Token-2022 payload. pub(crate) use self::token_2022::decoder_spl_token_2022_payload_tag; diff --git a/kb-lib/src/decoder/spl/token_2022.rs b/kb-lib/src/decoder/spl/token_2022.rs index 510188c..571f6c4 100644 --- a/kb-lib/src/decoder/spl/token_2022.rs +++ b/kb-lib/src/decoder/spl/token_2022.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/spl/token_2022.rs -// version: 4 +// version: 5 //! Exact Token-2022 instruction and state decoder component. @@ -18,6 +18,8 @@ pub use self::state::DcToken2022StateKind; pub use self::state::DcToken2022TlvEntry; /// Parses one Token-2022 Mint, Account, or Multisig state. pub use self::state::decoder_spl_token_2022_parse_token_2022_state; +/// Parses one exact Token Metadata interface value. +pub use self::state::decoder_spl_token_2022_parse_token_metadata_value; /// Maximum prefix retained in bounded Token-2022 diagnostics. pub(crate) use self::constants::DC_SPL_TOKEN_2022_DIAGNOSTIC_PREFIX_BYTES; diff --git a/kb-lib/src/decoder/spl/token_2022/state.rs b/kb-lib/src/decoder/spl/token_2022/state.rs index 2eefbdd..6eea778 100644 --- a/kb-lib/src/decoder/spl/token_2022/state.rs +++ b/kb-lib/src/decoder/spl/token_2022/state.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/spl/token_2022/state.rs -// version: 3 +// version: 4 //! Strict Token-2022 base-state and TLV parsing. @@ -429,6 +429,13 @@ fn decode_token_group_member(value: &[u8]) -> Result })); } +/// Parses one exact Token Metadata interface value with full-consumption checks. +pub fn decoder_spl_token_2022_parse_token_metadata_value( + value: &[u8], +) -> std::result::Result { + return decode_token_metadata(value); +} + fn decode_token_metadata(value: &[u8]) -> Result { if value.len() < 76 { return Err(format!( diff --git a/kb-lib/src/executor.rs b/kb-lib/src/executor.rs index c3358aa..e9a8a15 100644 --- a/kb-lib/src/executor.rs +++ b/kb-lib/src/executor.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/executor.rs -// version: 16 +// version: 17 //! Consolidated executor modules. @@ -714,6 +714,8 @@ pub use self::spl::EX_SPL_TOKEN_2022_DISABLE_REQUIRED_TRANSFER_MEMOS_OPERATION; pub use self::spl::EX_SPL_TOKEN_2022_ELGAMAL_CIPHERTEXT_BYTES; /// Exposes `EX_SPL_TOKEN_2022_ELGAMAL_PUBKEY_BYTES`. pub use self::spl::EX_SPL_TOKEN_2022_ELGAMAL_PUBKEY_BYTES; +/// Exposes `EX_SPL_TOKEN_2022_EMIT_TOKEN_METADATA_OPERATION`. +pub use self::spl::EX_SPL_TOKEN_2022_EMIT_TOKEN_METADATA_OPERATION; /// Exposes `EX_SPL_TOKEN_2022_EMPTY_CONFIDENTIAL_TRANSFER_ACCOUNT_OPERATION`. pub use self::spl::EX_SPL_TOKEN_2022_EMPTY_CONFIDENTIAL_TRANSFER_ACCOUNT_OPERATION; /// Exposes `EX_SPL_TOKEN_2022_ENABLE_CONFIDENTIAL_CREDITS_OPERATION`. @@ -844,6 +846,8 @@ pub use self::spl::EX_SPL_TOKEN_2022_UPDATE_METADATA_POINTER_OPERATION; pub use self::spl::EX_SPL_TOKEN_2022_UPDATE_SCALED_UI_AMOUNT_MULTIPLIER_OPERATION; /// Exposes `EX_SPL_TOKEN_2022_UPDATE_TOKEN_GROUP_MAX_SIZE_OPERATION`. pub use self::spl::EX_SPL_TOKEN_2022_UPDATE_TOKEN_GROUP_MAX_SIZE_OPERATION; +/// Exposes `EX_SPL_TOKEN_2022_UPDATE_TOKEN_METADATA_AUTHORITY_OPERATION`. +pub use self::spl::EX_SPL_TOKEN_2022_UPDATE_TOKEN_METADATA_AUTHORITY_OPERATION; /// Exposes `EX_SPL_TOKEN_2022_UPDATE_TOKEN_METADATA_FIELD_OPERATION`. pub use self::spl::EX_SPL_TOKEN_2022_UPDATE_TOKEN_METADATA_FIELD_OPERATION; /// Exposes `EX_SPL_TOKEN_2022_UPDATE_TRANSFER_HOOK_OPERATION`. diff --git a/kb-lib/src/executor/api/execution.rs b/kb-lib/src/executor/api/execution.rs index c8e8300..372a677 100644 --- a/kb-lib/src/executor/api/execution.rs +++ b/kb-lib/src/executor/api/execution.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/executor/api/execution.rs -// version: 8 +// version: 9 //! Typed execution plans, policies and result contracts. @@ -378,6 +378,9 @@ pub struct ExApiExecutionSimulationResult { pub estimated_fee_lamports: std::option::Option, /// Simulation logs in runtime order. pub logs: std::vec::Vec, + /// Program return data retained as provider-neutral JSON when reported. + #[serde(default)] + pub return_data: std::option::Option, /// Stable or provider error text when simulation failed. pub error: std::option::Option, } diff --git a/kb-lib/src/executor/safety/evaluation.rs b/kb-lib/src/executor/safety/evaluation.rs index 07b8f71..a55fe80 100644 --- a/kb-lib/src/executor/safety/evaluation.rs +++ b/kb-lib/src/executor/safety/evaluation.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/executor/safety/evaluation.rs -// version: 2 +// version: 3 //! Safety checks applied before simulation, signing or sending. @@ -516,6 +516,7 @@ mod tests { units_consumed: std::option::Option::Some(500), estimated_fee_lamports: std::option::Option::Some(5_000), logs: std::vec::Vec::new(), + return_data: std::option::Option::None, error: std::option::Option::None, }; } diff --git a/kb-lib/src/executor/solana/transaction/core.rs b/kb-lib/src/executor/solana/transaction/core.rs index dd93b4d..5b23222 100644 --- a/kb-lib/src/executor/solana/transaction/core.rs +++ b/kb-lib/src/executor/solana/transaction/core.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/executor/solana/transaction/core.rs -// version: 9 +// version: 10 //! Legacy and durable nonce Solana transaction assembly, simulation binding and signing. @@ -750,6 +750,7 @@ mod tests { units_consumed: std::option::Option::Some(500), estimated_fee_lamports: std::option::Option::Some(5_000), logs: std::vec::Vec::new(), + return_data: std::option::Option::None, error: std::option::Option::None, }; } @@ -773,6 +774,7 @@ mod tests { units_consumed: std::option::Option::Some(700), estimated_fee_lamports: std::option::Option::Some(10_000), logs: std::vec::Vec::new(), + return_data: std::option::Option::None, error: std::option::Option::None, }; } diff --git a/kb-lib/src/executor/spl.rs b/kb-lib/src/executor/spl.rs index fb1512b..ab4bed7 100644 --- a/kb-lib/src/executor/spl.rs +++ b/kb-lib/src/executor/spl.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/executor/spl.rs -// version: 7 +// version: 8 //! `spl` executor family. @@ -183,6 +183,8 @@ pub use self::token_2022::EX_SPL_TOKEN_2022_DISABLE_REQUIRED_TRANSFER_MEMOS_OPER pub use self::token_2022::EX_SPL_TOKEN_2022_ELGAMAL_CIPHERTEXT_BYTES; /// Exposes `EX_SPL_TOKEN_2022_ELGAMAL_PUBKEY_BYTES`. pub use self::token_2022::EX_SPL_TOKEN_2022_ELGAMAL_PUBKEY_BYTES; +/// Exposes `EX_SPL_TOKEN_2022_EMIT_TOKEN_METADATA_OPERATION`. +pub use self::token_2022::EX_SPL_TOKEN_2022_EMIT_TOKEN_METADATA_OPERATION; /// Exposes `EX_SPL_TOKEN_2022_EMPTY_CONFIDENTIAL_TRANSFER_ACCOUNT_OPERATION`. pub use self::token_2022::EX_SPL_TOKEN_2022_EMPTY_CONFIDENTIAL_TRANSFER_ACCOUNT_OPERATION; /// Exposes `EX_SPL_TOKEN_2022_ENABLE_CONFIDENTIAL_CREDITS_OPERATION`. @@ -313,6 +315,8 @@ pub use self::token_2022::EX_SPL_TOKEN_2022_UPDATE_METADATA_POINTER_OPERATION; pub use self::token_2022::EX_SPL_TOKEN_2022_UPDATE_SCALED_UI_AMOUNT_MULTIPLIER_OPERATION; /// Exposes `EX_SPL_TOKEN_2022_UPDATE_TOKEN_GROUP_MAX_SIZE_OPERATION`. pub use self::token_2022::EX_SPL_TOKEN_2022_UPDATE_TOKEN_GROUP_MAX_SIZE_OPERATION; +/// Exposes `EX_SPL_TOKEN_2022_UPDATE_TOKEN_METADATA_AUTHORITY_OPERATION`. +pub use self::token_2022::EX_SPL_TOKEN_2022_UPDATE_TOKEN_METADATA_AUTHORITY_OPERATION; /// Exposes `EX_SPL_TOKEN_2022_UPDATE_TOKEN_METADATA_FIELD_OPERATION`. pub use self::token_2022::EX_SPL_TOKEN_2022_UPDATE_TOKEN_METADATA_FIELD_OPERATION; /// Exposes `EX_SPL_TOKEN_2022_UPDATE_TRANSFER_HOOK_OPERATION`. diff --git a/kb-lib/src/executor/spl/token_2022.rs b/kb-lib/src/executor/spl/token_2022.rs index 4bd6f71..24ac91a 100644 --- a/kb-lib/src/executor/spl/token_2022.rs +++ b/kb-lib/src/executor/spl/token_2022.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/executor/spl/token_2022.rs -// version: 5 +// version: 6 //! SPL Token-2022 executor. @@ -86,6 +86,8 @@ pub use self::intent::EX_SPL_TOKEN_2022_DISABLE_CPI_GUARD_OPERATION; pub use self::intent::EX_SPL_TOKEN_2022_DISABLE_NON_CONFIDENTIAL_CREDITS_OPERATION; /// Exposes `EX_SPL_TOKEN_2022_DISABLE_REQUIRED_TRANSFER_MEMOS_OPERATION`. pub use self::intent::EX_SPL_TOKEN_2022_DISABLE_REQUIRED_TRANSFER_MEMOS_OPERATION; +/// Exposes `EX_SPL_TOKEN_2022_EMIT_TOKEN_METADATA_OPERATION`. +pub use self::intent::EX_SPL_TOKEN_2022_EMIT_TOKEN_METADATA_OPERATION; /// Exposes `EX_SPL_TOKEN_2022_EMPTY_CONFIDENTIAL_TRANSFER_ACCOUNT_OPERATION`. pub use self::intent::EX_SPL_TOKEN_2022_EMPTY_CONFIDENTIAL_TRANSFER_ACCOUNT_OPERATION; /// Exposes `EX_SPL_TOKEN_2022_ENABLE_CONFIDENTIAL_CREDITS_OPERATION`. @@ -214,6 +216,8 @@ pub use self::intent::EX_SPL_TOKEN_2022_UPDATE_METADATA_POINTER_OPERATION; pub use self::intent::EX_SPL_TOKEN_2022_UPDATE_SCALED_UI_AMOUNT_MULTIPLIER_OPERATION; /// Exposes `EX_SPL_TOKEN_2022_UPDATE_TOKEN_GROUP_MAX_SIZE_OPERATION`. pub use self::intent::EX_SPL_TOKEN_2022_UPDATE_TOKEN_GROUP_MAX_SIZE_OPERATION; +/// Exposes `EX_SPL_TOKEN_2022_UPDATE_TOKEN_METADATA_AUTHORITY_OPERATION`. +pub use self::intent::EX_SPL_TOKEN_2022_UPDATE_TOKEN_METADATA_AUTHORITY_OPERATION; /// Exposes `EX_SPL_TOKEN_2022_UPDATE_TOKEN_METADATA_FIELD_OPERATION`. pub use self::intent::EX_SPL_TOKEN_2022_UPDATE_TOKEN_METADATA_FIELD_OPERATION; /// Exposes `EX_SPL_TOKEN_2022_UPDATE_TRANSFER_HOOK_OPERATION`. diff --git a/kb-lib/src/executor/spl/token_2022/builder.rs b/kb-lib/src/executor/spl/token_2022/builder.rs index 74a4f80..4c8fdb6 100644 --- a/kb-lib/src/executor/spl/token_2022/builder.rs +++ b/kb-lib/src/executor/spl/token_2022/builder.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/executor/spl/token_2022/builder.rs -// version: 30 +// version: 34 //! Official Token-2022 builders and conservative plan validation. @@ -172,6 +172,41 @@ fn validate_metadata_string(value: &str, field: &str) -> kb_core::Result<()> { return std::result::Result::Ok(()); } +fn validate_token_metadata_emit_range( + start: std::option::Option, + end: std::option::Option, +) -> kb_core::Result<()> { + if let (std::option::Option::Some(start), std::option::Option::Some(end)) = (start, end) { + if start > end { + return std::result::Result::Err(kb_core::Error::new( + "execution_spl_token_2022_metadata_emit_range_invalid", + format!("Token metadata Emit start {start} exceeds end {end}"), + )); + } + if end - start > 1_024 { + return std::result::Result::Err(kb_core::Error::new( + "execution_spl_token_2022_metadata_emit_range_too_large", + "Token metadata Emit range exceeds the 1024-byte Solana return-data bound", + )); + } + } + if let (std::option::Option::None, std::option::Option::Some(end)) = (start, end) { + if end > 1_024 { + return std::result::Result::Err(kb_core::Error::new( + "execution_spl_token_2022_metadata_emit_range_too_large", + "Token metadata Emit range from zero exceeds the 1024-byte Solana return-data bound", + )); + } + } + if let (std::option::Option::Some(_), std::option::Option::None) = (start, end) { + return std::result::Result::Err(kb_core::Error::new( + "execution_spl_token_2022_metadata_emit_range_end_required", + "Token metadata Emit with a start offset requires an explicit end to remain bounded", + )); + } + return std::result::Result::Ok(()); +} + fn default_account_state( state: crate::ExSplTokenDefaultAccountState, ) -> spl_token_2022_interface::state::AccountState { @@ -1162,6 +1197,40 @@ fn build_single( *idempotent, )) }, + crate::ExSplTokenSingleOperation::UpdateTokenMetadataAuthority { + metadata, + current_authority, + new_authority, + } => { + let metadata = parse_key!(metadata, "metadata"); + let current_authority = parse_key!(current_authority, "current_authority"); + let new_authority = parse_optional_key!(new_authority, "new_authority"); + let nullable_authority = match new_authority.try_into() { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => { + return std::result::Result::Err(kb_core::Error::new( + "execution_spl_token_2022_metadata_authority_invalid", + "Token metadata authority cannot be encoded as a nullable address", + )); + }, + }; + std::result::Result::Ok(spl_token_metadata_interface::instruction::update_authority( + program_id, + &metadata, + ¤t_authority, + nullable_authority, + )) + }, + crate::ExSplTokenSingleOperation::EmitTokenMetadata { metadata, start, end } => { + match validate_token_metadata_emit_range(*start, *end) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let metadata = parse_key!(metadata, "metadata"); + std::result::Result::Ok(spl_token_metadata_interface::instruction::emit( + program_id, &metadata, *start, *end, + )) + }, crate::ExSplTokenSingleOperation::InitializeTokenGroup { group, mint, @@ -3566,6 +3635,22 @@ pub(crate) mod tests { idempotent: true, }, ), + ( + 255, + crate::ExSplTokenSingleOperation::UpdateTokenMetadataAuthority { + metadata: first.clone(), + current_authority: second.clone(), + new_authority: std::option::Option::Some(third.clone()), + }, + ), + ( + 255, + crate::ExSplTokenSingleOperation::EmitTokenMetadata { + metadata: first.clone(), + start: std::option::Option::Some(0), + end: std::option::Option::Some(64), + }, + ), ( 255, crate::ExSplTokenSingleOperation::InitializeTokenGroup { @@ -4706,6 +4791,7 @@ pub(crate) mod tests { uri: std::string::String::from("https://example.invalid/token.json"), }, 4_usize, + true, ), ( crate::ExSplTokenSingleOperation::UpdateTokenMetadataField { @@ -4717,6 +4803,7 @@ pub(crate) mod tests { value: std::string::String::from("https://example.invalid"), }, 2_usize, + true, ), ( crate::ExSplTokenSingleOperation::RemoveTokenMetadataKey { @@ -4726,6 +4813,25 @@ pub(crate) mod tests { idempotent: true, }, 2_usize, + true, + ), + ( + crate::ExSplTokenSingleOperation::UpdateTokenMetadataAuthority { + metadata: first.clone(), + current_authority: second.clone(), + new_authority: std::option::Option::Some(third.clone()), + }, + 2_usize, + true, + ), + ( + crate::ExSplTokenSingleOperation::EmitTokenMetadata { + metadata: first.clone(), + start: std::option::Option::Some(8), + end: std::option::Option::Some(72), + }, + 1_usize, + false, ), ( crate::ExSplTokenSingleOperation::InitializeTokenGroup { @@ -4736,6 +4842,7 @@ pub(crate) mod tests { max_size: 42, }, 3_usize, + true, ), ( crate::ExSplTokenSingleOperation::UpdateTokenGroupMaxSize { @@ -4744,6 +4851,7 @@ pub(crate) mod tests { max_size: 84, }, 2_usize, + true, ), ( crate::ExSplTokenSingleOperation::InitializeTokenGroupMember { @@ -4754,9 +4862,10 @@ pub(crate) mod tests { group_update_authority: second, }, 5_usize, + true, ), ]; - for (operation, account_count) in cases { + for (operation, account_count, first_writable) in cases { let intent = intent(crate::ExSplToken2022Operation::Instruction { value: std::boxed::Box::new(operation), }); @@ -4767,7 +4876,63 @@ pub(crate) mod tests { .unwrap_or_else(|error| panic!("metadata or group plan failed: {error}")); assert!(plan.instructions[0].data.len() >= 8); assert_eq!(plan.instructions[0].accounts.len(), account_count); - assert!(plan.instructions[0].accounts[0].is_writable); + assert_eq!(plan.instructions[0].accounts[0].is_writable, first_writable); + } + } + + #[test] + fn metadata_update_authority_accepts_nullable_none() { + let planned = planned_single_instruction( + crate::ExSplTokenSingleOperation::UpdateTokenMetadataAuthority { + metadata: pubkey(kb_program_ids::SYSTEM_PROGRAM_ID), + current_authority: pubkey(kb_program_ids::CONFIG_PROGRAM_ID), + new_authority: std::option::Option::None, + }, + ); + assert_eq!(planned.operation_code, "spl.token_2022.update_token_metadata_authority"); + assert_eq!(planned.accounts.len(), 2); + assert!(planned.accounts[0].is_writable); + assert!(planned.accounts[1].is_signer); + } + + #[test] + fn metadata_emit_accepts_complete_and_maximum_bounded_ranges() { + for (start, end) in [ + (std::option::Option::None, std::option::Option::None), + (std::option::Option::Some(0), std::option::Option::Some(1_024)), + ] { + let planned = + planned_single_instruction(crate::ExSplTokenSingleOperation::EmitTokenMetadata { + metadata: pubkey(kb_program_ids::SYSTEM_PROGRAM_ID), + start, + end, + }); + assert_eq!(planned.operation_code, "spl.token_2022.emit_token_metadata"); + assert_eq!(planned.accounts.len(), 1); + assert!(!planned.accounts[0].is_writable); + } + } + + #[test] + fn metadata_emit_rejects_reversed_oversized_and_open_ended_ranges() { + let metadata = pubkey(kb_program_ids::SYSTEM_PROGRAM_ID); + for (start, end) in [ + (std::option::Option::Some(9), std::option::Option::Some(8)), + (std::option::Option::Some(0), std::option::Option::Some(1_025)), + (std::option::Option::Some(1), std::option::Option::None), + ] { + let value = crate::ExSplTokenSingleOperation::EmitTokenMetadata { + metadata: metadata.clone(), + start, + end, + }; + let result = crate::ExApiTypedInstructionExecutor::build_prepared_plan( + &crate::ExSplToken2022Executor, + &intent(crate::ExSplToken2022Operation::Instruction { + value: std::boxed::Box::new(value), + }), + ); + assert!(result.is_err()); } } diff --git a/kb-lib/src/executor/spl/token_2022/executor.rs b/kb-lib/src/executor/spl/token_2022/executor.rs index ef44b76..2421973 100644 --- a/kb-lib/src/executor/spl/token_2022/executor.rs +++ b/kb-lib/src/executor/spl/token_2022/executor.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/executor/spl/token_2022/executor.rs -// version: 23 +// version: 25 //! Exact Token-2022 capability dispatch and typed plan construction. @@ -220,6 +220,86 @@ mod tests { ); } + fn matrix_operation_name(name: &str) -> &str { + return match name { + "initialize_interest_bearing_mint" | "update_interest_bearing_rate" => { + "interest_bearing_mint_extension" + }, + "initialize_transfer_fee_config" + | "set_transfer_fee" + | "transfer_checked_with_fee" + | "withdraw_withheld_tokens_from_mint" + | "withdraw_withheld_tokens_from_accounts" + | "harvest_withheld_tokens_to_mint" => "transfer_fee_extension", + "initialize_default_account_state" | "update_default_account_state" => { + "default_account_state_extension" + }, + "enable_required_transfer_memos" | "disable_required_transfer_memos" => { + "memo_transfer_extension" + }, + "enable_cpi_guard" | "disable_cpi_guard" => "cpi_guard_extension", + "initialize_transfer_hook" | "update_transfer_hook" => "transfer_hook_extension", + "initialize_metadata_pointer" | "update_metadata_pointer" => { + "metadata_pointer_extension" + }, + "initialize_group_pointer" | "update_group_pointer" => "group_pointer_extension", + "initialize_group_member_pointer" | "update_group_member_pointer" => { + "group_member_pointer_extension" + }, + "initialize_token_metadata" + | "update_token_metadata_field" + | "remove_token_metadata_key" + | "update_token_metadata_authority" + | "emit_token_metadata" => "token_metadata", + "initialize_token_group" | "update_token_group_max_size" => "token_group", + "initialize_token_group_member" => "token_group_member", + "initialize_scaled_ui_amount" | "update_scaled_ui_amount_multiplier" => { + "scaled_ui_amount_extension" + }, + "initialize_pausable_config" | "pause" | "resume" => "pausable_extension", + "initialize_permissioned_burn" + | "permissioned_burn" + | "permissioned_burn_checked" + | "permissioned_confidential_burn" => "permissioned_burn_extension", + "initialize_confidential_transfer_mint" + | "update_confidential_transfer_mint" + | "apply_pending_confidential_balance" + | "approve_confidential_transfer_account" + | "deposit_confidential_tokens" + | "enable_confidential_credits" + | "disable_confidential_credits" + | "enable_non_confidential_credits" + | "disable_non_confidential_credits" + | "configure_confidential_transfer_account" + | "configure_confidential_transfer_account_with_registry" + | "empty_confidential_transfer_account" + | "withdraw_confidential_tokens" + | "transfer_confidential_tokens" + | "transfer_confidential_tokens_with_fee" => "confidential_transfer_extension", + "initialize_confidential_transfer_fee_config" + | "enable_confidential_transfer_fee_harvest" + | "disable_confidential_transfer_fee_harvest" + | "harvest_confidential_withheld_tokens_to_mint" + | "withdraw_confidential_withheld_tokens_from_mint" + | "withdraw_confidential_withheld_tokens_from_accounts" => { + "confidential_transfer_fee_extension" + }, + "initialize_confidential_mint_burn_mint" + | "rotate_confidential_supply_elgamal_pubkey" + | "update_confidential_decryptable_supply" + | "apply_pending_confidential_burn" + | "confidential_mint" + | "confidential_burn" => "confidential_mint_burn_extension", + _ => name, + }; + } + + #[test] + fn token_metadata_operations_share_matrix_family() { + assert_eq!(matrix_operation_name("update_token_metadata_authority"), "token_metadata"); + assert_eq!(matrix_operation_name("emit_token_metadata"), "token_metadata"); + } + #[test] fn machine_readable_matrix_matches_executor_policy() { let matrix = serde_json::from_str::(include_str!( @@ -242,75 +322,7 @@ mod tests { let name = operation_code .strip_prefix("spl.token_2022.") .unwrap_or_else(|| panic!("invalid operation code {operation_code}")); - let matrix_name = match name { - "initialize_interest_bearing_mint" | "update_interest_bearing_rate" => { - "interest_bearing_mint_extension" - }, - "initialize_transfer_fee_config" - | "set_transfer_fee" - | "transfer_checked_with_fee" - | "withdraw_withheld_tokens_from_mint" - | "withdraw_withheld_tokens_from_accounts" - | "harvest_withheld_tokens_to_mint" => "transfer_fee_extension", - "initialize_default_account_state" | "update_default_account_state" => { - "default_account_state_extension" - }, - "enable_required_transfer_memos" | "disable_required_transfer_memos" => { - "memo_transfer_extension" - }, - "enable_cpi_guard" | "disable_cpi_guard" => "cpi_guard_extension", - "initialize_transfer_hook" | "update_transfer_hook" => "transfer_hook_extension", - "initialize_metadata_pointer" | "update_metadata_pointer" => { - "metadata_pointer_extension" - }, - "initialize_group_pointer" | "update_group_pointer" => "group_pointer_extension", - "initialize_group_member_pointer" | "update_group_member_pointer" => { - "group_member_pointer_extension" - }, - "initialize_token_metadata" - | "update_token_metadata_field" - | "remove_token_metadata_key" => "token_metadata", - "initialize_token_group" | "update_token_group_max_size" => "token_group", - "initialize_token_group_member" => "token_group_member", - "initialize_scaled_ui_amount" | "update_scaled_ui_amount_multiplier" => { - "scaled_ui_amount_extension" - }, - "initialize_pausable_config" | "pause" | "resume" => "pausable_extension", - "initialize_permissioned_burn" - | "permissioned_burn" - | "permissioned_burn_checked" - | "permissioned_confidential_burn" => "permissioned_burn_extension", - "initialize_confidential_transfer_mint" - | "update_confidential_transfer_mint" - | "apply_pending_confidential_balance" - | "approve_confidential_transfer_account" - | "deposit_confidential_tokens" - | "enable_confidential_credits" - | "disable_confidential_credits" - | "enable_non_confidential_credits" - | "disable_non_confidential_credits" - | "configure_confidential_transfer_account" - | "configure_confidential_transfer_account_with_registry" - | "empty_confidential_transfer_account" - | "withdraw_confidential_tokens" - | "transfer_confidential_tokens" - | "transfer_confidential_tokens_with_fee" => "confidential_transfer_extension", - "initialize_confidential_transfer_fee_config" - | "enable_confidential_transfer_fee_harvest" - | "disable_confidential_transfer_fee_harvest" - | "harvest_confidential_withheld_tokens_to_mint" - | "withdraw_confidential_withheld_tokens_from_mint" - | "withdraw_confidential_withheld_tokens_from_accounts" => { - "confidential_transfer_fee_extension" - }, - "initialize_confidential_mint_burn_mint" - | "rotate_confidential_supply_elgamal_pubkey" - | "update_confidential_decryptable_supply" - | "apply_pending_confidential_burn" - | "confidential_mint" - | "confidential_burn" => "confidential_mint_burn_extension", - _ => name, - }; + let matrix_name = matrix_operation_name(name); assert!( matrix_names.contains(matrix_name), "matrix is missing supported operation {name}" diff --git a/kb-lib/src/executor/spl/token_2022/intent.rs b/kb-lib/src/executor/spl/token_2022/intent.rs index 4e58a6b..ffb4032 100644 --- a/kb-lib/src/executor/spl/token_2022/intent.rs +++ b/kb-lib/src/executor/spl/token_2022/intent.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/executor/spl/token_2022/intent.rs -// version: 26 +// version: 27 //! Typed Token-2022 execution intents. @@ -138,6 +138,12 @@ pub const EX_SPL_TOKEN_2022_UPDATE_TOKEN_METADATA_FIELD_OPERATION: &str = /// Stable operation code for removing an embedded metadata key. pub const EX_SPL_TOKEN_2022_REMOVE_TOKEN_METADATA_KEY_OPERATION: &str = "spl.token_2022.remove_token_metadata_key"; +/// Stable operation code for updating or clearing embedded metadata authority. +pub const EX_SPL_TOKEN_2022_UPDATE_TOKEN_METADATA_AUTHORITY_OPERATION: &str = + "spl.token_2022.update_token_metadata_authority"; +/// Stable operation code for emitting bounded embedded metadata return data. +pub const EX_SPL_TOKEN_2022_EMIT_TOKEN_METADATA_OPERATION: &str = + "spl.token_2022.emit_token_metadata"; /// Stable operation code for initializing an embedded token group. pub const EX_SPL_TOKEN_2022_INITIALIZE_TOKEN_GROUP_OPERATION: &str = "spl.token_2022.initialize_token_group"; @@ -307,6 +313,8 @@ pub const EX_SPL_TOKEN_2022_SUPPORTED_OPERATION_CODES: &[&str] = &[ EX_SPL_TOKEN_2022_INITIALIZE_TOKEN_METADATA_OPERATION, EX_SPL_TOKEN_2022_UPDATE_TOKEN_METADATA_FIELD_OPERATION, EX_SPL_TOKEN_2022_REMOVE_TOKEN_METADATA_KEY_OPERATION, + EX_SPL_TOKEN_2022_UPDATE_TOKEN_METADATA_AUTHORITY_OPERATION, + EX_SPL_TOKEN_2022_EMIT_TOKEN_METADATA_OPERATION, EX_SPL_TOKEN_2022_INITIALIZE_TOKEN_GROUP_OPERATION, EX_SPL_TOKEN_2022_UPDATE_TOKEN_GROUP_MAX_SIZE_OPERATION, EX_SPL_TOKEN_2022_INITIALIZE_TOKEN_GROUP_MEMBER_OPERATION, @@ -919,6 +927,24 @@ pub enum ExSplTokenSingleOperation { /// Whether a missing key is accepted. idempotent: bool, }, + /// Update or permanently clear embedded metadata authority. + UpdateTokenMetadataAuthority { + /// Metadata account. + metadata: crate::MdPubkey, + /// Current update authority signer. + current_authority: crate::MdPubkey, + /// New authority, or `None` to make metadata immutable. + new_authority: std::option::Option, + }, + /// Emit all or one bounded range of embedded metadata through return data. + EmitTokenMetadata { + /// Metadata account. + metadata: crate::MdPubkey, + /// Optional inclusive byte-range start. + start: std::option::Option, + /// Optional exclusive byte-range end. + end: std::option::Option, + }, /// Initialize a Token Group entry. InitializeTokenGroup { /// Group state account, normally the group mint. @@ -1454,6 +1480,10 @@ impl crate::ExSplTokenSingleOperation { Self::InitializeTokenMetadata { .. } => crate::EX_SPL_TOKEN_2022_INITIALIZE_TOKEN_METADATA_OPERATION, Self::UpdateTokenMetadataField { .. } => crate::EX_SPL_TOKEN_2022_UPDATE_TOKEN_METADATA_FIELD_OPERATION, Self::RemoveTokenMetadataKey { .. } => crate::EX_SPL_TOKEN_2022_REMOVE_TOKEN_METADATA_KEY_OPERATION, + Self::UpdateTokenMetadataAuthority { .. } => { + crate::EX_SPL_TOKEN_2022_UPDATE_TOKEN_METADATA_AUTHORITY_OPERATION + }, + Self::EmitTokenMetadata { .. } => crate::EX_SPL_TOKEN_2022_EMIT_TOKEN_METADATA_OPERATION, Self::InitializeTokenGroup { .. } => crate::EX_SPL_TOKEN_2022_INITIALIZE_TOKEN_GROUP_OPERATION, Self::UpdateTokenGroupMaxSize { .. } => crate::EX_SPL_TOKEN_2022_UPDATE_TOKEN_GROUP_MAX_SIZE_OPERATION, Self::InitializeTokenGroupMember { .. } => { @@ -1566,6 +1596,7 @@ impl crate::ExSplTokenSingleOperation { return !matches!( self, Self::GetAccountDataSize { .. } + | Self::EmitTokenMetadata { .. } | Self::InitializeImmutableOwner { .. } | Self::AmountToUiAmount { .. } | Self::UiAmountToAmount { .. } diff --git a/kb-lib/src/lib.rs b/kb-lib/src/lib.rs index 5b1a4f4..68891c3 100644 --- a/kb-lib/src/lib.rs +++ b/kb-lib/src/lib.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/lib.rs -// version: 37 +// version: 38 //! Consolidated decoder, executor, materializer and shared model library. #![warn(missing_docs)] @@ -401,6 +401,8 @@ pub use self::decoder::decoder_metadata_solana_program_metadata_decode_metadata_ pub use self::decoder::decoder_spl_elgamal_registry_parse_elgamal_registry_state; /// Parses one Token-2022 Mint, Account, or Multisig state. pub use self::decoder::decoder_spl_token_2022_parse_token_2022_state; +/// Parses one exact Token Metadata interface value. +pub use self::decoder::decoder_spl_token_2022_parse_token_metadata_value; /// Stable Solana Program Metadata `Allocate` operation code. pub use self::executor::EX_METADATA_SPM_ALLOCATE_OPERATION; /// Stable Solana Program Metadata `Close` operation code. @@ -791,6 +793,8 @@ pub use self::executor::EX_SPL_TOKEN_2022_DISABLE_REQUIRED_TRANSFER_MEMOS_OPERAT pub use self::executor::EX_SPL_TOKEN_2022_ELGAMAL_CIPHERTEXT_BYTES; /// Exposes `EX_SPL_TOKEN_2022_ELGAMAL_PUBKEY_BYTES`. pub use self::executor::EX_SPL_TOKEN_2022_ELGAMAL_PUBKEY_BYTES; +/// Exposes `EX_SPL_TOKEN_2022_EMIT_TOKEN_METADATA_OPERATION`. +pub use self::executor::EX_SPL_TOKEN_2022_EMIT_TOKEN_METADATA_OPERATION; /// Exposes `EX_SPL_TOKEN_2022_EMPTY_CONFIDENTIAL_TRANSFER_ACCOUNT_OPERATION`. pub use self::executor::EX_SPL_TOKEN_2022_EMPTY_CONFIDENTIAL_TRANSFER_ACCOUNT_OPERATION; /// Exposes `EX_SPL_TOKEN_2022_ENABLE_CONFIDENTIAL_CREDITS_OPERATION`. @@ -921,6 +925,8 @@ pub use self::executor::EX_SPL_TOKEN_2022_UPDATE_METADATA_POINTER_OPERATION; pub use self::executor::EX_SPL_TOKEN_2022_UPDATE_SCALED_UI_AMOUNT_MULTIPLIER_OPERATION; /// Exposes `EX_SPL_TOKEN_2022_UPDATE_TOKEN_GROUP_MAX_SIZE_OPERATION`. pub use self::executor::EX_SPL_TOKEN_2022_UPDATE_TOKEN_GROUP_MAX_SIZE_OPERATION; +/// Exposes `EX_SPL_TOKEN_2022_UPDATE_TOKEN_METADATA_AUTHORITY_OPERATION`. +pub use self::executor::EX_SPL_TOKEN_2022_UPDATE_TOKEN_METADATA_AUTHORITY_OPERATION; /// Exposes `EX_SPL_TOKEN_2022_UPDATE_TOKEN_METADATA_FIELD_OPERATION`. pub use self::executor::EX_SPL_TOKEN_2022_UPDATE_TOKEN_METADATA_FIELD_OPERATION; /// Exposes `EX_SPL_TOKEN_2022_UPDATE_TRANSFER_HOOK_OPERATION`. diff --git a/kb-onchain-transport/CHANGELOG.md b/kb-onchain-transport/CHANGELOG.md index 0940b3a..ad74ab6 100644 --- a/kb-onchain-transport/CHANGELOG.md +++ b/kb-onchain-transport/CHANGELOG.md @@ -1,8 +1,13 @@ - + # CHANGELOG — kb-onchain-transport +## `0.4.8-pre.011` + +- propage le champ JSON-RPC `returnData` de `simulateTransaction` vers le résultat commun d’exécution sans interprétation fournisseur. + + ## 0.4.8-pre.010 - documente que les limites de rôles doivent être additionnées pour respecter le quota global d’un endpoint public et que le quota par méthode peut être plus strict que le quota global ; diff --git a/kb-onchain-transport/src/execution_rpc.rs b/kb-onchain-transport/src/execution_rpc.rs index fdd7898..f6fe851 100644 --- a/kb-onchain-transport/src/execution_rpc.rs +++ b/kb-onchain-transport/src/execution_rpc.rs @@ -1,5 +1,5 @@ // file: kb-onchain-transport/src/execution_rpc.rs -// version: 9 +// version: 10 //! Typed Solana JSON-RPC adapters used by execution orchestration. @@ -443,6 +443,7 @@ impl crate::SimulateTransactionResult { units_consumed: self.units_consumed, estimated_fee_lamports, logs: self.logs.clone(), + return_data: self.return_data.clone(), error, }; } @@ -2346,7 +2347,10 @@ mod tests { last_valid_block_height: 101, }, ), - return_data: std::option::Option::None, + return_data: std::option::Option::Some(serde_json::json!({ + "programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", + "data": ["AQ==", "base64"] + })), inner_instructions: std::option::Option::None, accounts: std::option::Option::None, }; @@ -2370,6 +2374,14 @@ mod tests { assert_eq!(execution.estimated_fee_lamports, std::option::Option::Some(5000)); assert_eq!(execution.replacement_last_valid_block_height, std::option::Option::Some(101)); assert!(execution.replacement_blockhash.is_some()); + assert_eq!( + execution + .return_data + .as_ref() + .and_then(|value| return value.get("programId")) + .and_then(serde_json::Value::as_str), + std::option::Option::Some("TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb") + ); assert!(execution.error.is_some()); } diff --git a/kb-pipeline-demo-scenarios/CHANGELOG.md b/kb-pipeline-demo-scenarios/CHANGELOG.md index d314e7e..d10a09c 100644 --- a/kb-pipeline-demo-scenarios/CHANGELOG.md +++ b/kb-pipeline-demo-scenarios/CHANGELOG.md @@ -1,8 +1,13 @@ - + # CHANGELOG — kb-pipeline-demo-scenarios +## `0.4.8-pre.011` + +- adapte la fixture synthétique de simulation au nouveau champ optionnel `return_data` du contrat commun ; +- promeut les neuf lignes Solana Program Metadata à `confirmed` avec les preuves des deux campagnes Devnet finales de `pre.010`. + ## 0.4.8-pre.009 — scénarios Devnet Solana Program Metadata - déplace `SetAuthority` du compte Metadata non canonique vers le Buffer non canonique, conformément au processeur officiel qui refuse cette opération sur les metadata non canoniques ; diff --git a/kb-pipeline-demo-scenarios/src/metadata_solana_program_validation.rs b/kb-pipeline-demo-scenarios/src/metadata_solana_program_validation.rs index 264f3ba..1427c85 100644 --- a/kb-pipeline-demo-scenarios/src/metadata_solana_program_validation.rs +++ b/kb-pipeline-demo-scenarios/src/metadata_solana_program_validation.rs @@ -1,5 +1,5 @@ // file: kb-pipeline-demo-scenarios/src/metadata_solana_program_validation.rs -// version: 2 +// version: 3 //! Conservative Solana Program Metadata validation status and evidence contracts. @@ -170,7 +170,9 @@ mod tests { std::result::Result::Err(error) => panic!("matrix load failed: {error}"), }; assert_eq!(matrix.operations.len(), 9); - assert!(matrix.operations.iter().all(|operation| return operation.status - == crate::SolanaProgramMetadataValidationStatus::NotRun)); + assert!(matrix.operations.iter().all(|operation| { + return operation.status == crate::SolanaProgramMetadataValidationStatus::Confirmed + && operation.evidence.len() == 10; + })); } } diff --git a/kb-pipeline-demo-scenarios/src/solana_execution.rs b/kb-pipeline-demo-scenarios/src/solana_execution.rs index e9807a9..1ef16ff 100644 --- a/kb-pipeline-demo-scenarios/src/solana_execution.rs +++ b/kb-pipeline-demo-scenarios/src/solana_execution.rs @@ -1,5 +1,5 @@ // file: kb-pipeline-demo-scenarios/src/solana_execution.rs -// version: 9 +// version: 10 //! Devnet Solana execution orchestration with canonical post-validation. @@ -1322,6 +1322,7 @@ mod tests { units_consumed: std::option::Option::Some(150), estimated_fee_lamports: std::option::Option::Some(5_000), logs: std::vec!["Program log: insufficient rent".to_string()], + return_data: std::option::Option::None, error: std::option::Option::Some("InsufficientFundsForRent".to_string()), }; let message = super::simulation_failure_message(&simulation); diff --git a/kb-pipeline/CHANGELOG.md b/kb-pipeline/CHANGELOG.md index 9e7acab..eca9c32 100644 --- a/kb-pipeline/CHANGELOG.md +++ b/kb-pipeline/CHANGELOG.md @@ -1,8 +1,17 @@ - + # CHANGELOG — kb-pipeline +## `0.4.8-pre.011` — validation Token-2022 Token Metadata + +- ajoute l’extraction bornée du retour base64 produit par `Emit` ; +- aligne la fixture de simulation Token-2022 sur le variant canonique `ExApiExecutionBlockhashKind::Latest` ; +- refuse les plages `Emit` ouvertes avec `start` sans `end`, en cohérence avec le builder et la limite de retour Solana ; +- décode uniquement un retour complet et conserve un retour partiel sans inventer un état ; +- ajoute la postcondition stateful de changement ou suppression de l’autorité metadata. + + ## 0.4.8-pre.010 - remplace l’assertion runtime sur la relation entre borne RPC et borne de décodage par une assertion `const`, afin de supprimer le dernier warning `clippy::assertions_on_constants` ; diff --git a/kb-pipeline/Cargo.toml b/kb-pipeline/Cargo.toml index 142ff29..fcc24fd 100644 --- a/kb-pipeline/Cargo.toml +++ b/kb-pipeline/Cargo.toml @@ -1,5 +1,5 @@ # file: kb-pipeline/Cargo.toml -# version: 14 +# version: 15 [package] name = "kb-pipeline" @@ -10,6 +10,7 @@ publish.workspace = true [dependencies] async-trait.workspace = true +base64.workspace = true bs58.workspace = true chrono.workspace = true futures-util.workspace = true diff --git a/kb-pipeline/README.md b/kb-pipeline/README.md index 3bcac19..602e1b0 100644 --- a/kb-pipeline/README.md +++ b/kb-pipeline/README.md @@ -1,5 +1,5 @@ - + # kb-pipeline @@ -50,3 +50,5 @@ La crate coordonne `kb-onchain-transport`, `kb-store`, `kb-lib`, `kb-core` et `k - [Travaux restant à réaliser](TODO.md) - [Historique des changements](CHANGELOG.md) - [Architecture du pipeline](../docs/architecture/PIPELINE_ARCHITECTURE.md) + +- validation bornée du retour `Emit` Token-2022 et postcondition stateful de l’autorité metadata ; diff --git a/kb-pipeline/USAGE.md b/kb-pipeline/USAGE.md index 92072c4..4ee43d1 100644 --- a/kb-pipeline/USAGE.md +++ b/kb-pipeline/USAGE.md @@ -1,5 +1,5 @@ - + # Utilisation de kb-pipeline @@ -350,3 +350,7 @@ fn summarize_metaplex_postconditions( - les comptes et projections nécessaires aux postconditions. Lorsque `materialize_after_confirmation` est activé, une soumission exige au moins une lecture de postcondition. Après confirmation, les snapshots bornés sont décodés et projetés. Lorsque l’option est désactivée, le runner conserve la simulation, la soumission, la confirmation et les postconditions demandées, mais n’émet pas les projections de matérialisation. + +## Validation Token-2022 Token Metadata + +`inspect_token_2022_metadata_emit_simulation` valide le `returnData` d’une simulation `Emit`. `inspect_token_2022_metadata_authority_postcondition` compare l’autorité attendue au snapshot TLV autoritatif après confirmation. diff --git a/kb-pipeline/src/lib.rs b/kb-pipeline/src/lib.rs index 971c998..59afd64 100644 --- a/kb-pipeline/src/lib.rs +++ b/kb-pipeline/src/lib.rs @@ -1,5 +1,5 @@ // file: kb-pipeline/src/lib.rs -// version: 20 +// version: 21 #![forbid(unsafe_code)] #![deny(unreachable_pub)] @@ -24,6 +24,7 @@ mod spl_elgamal_registry_stateful; mod spl_token_2022_correlation; mod spl_token_2022_crypto_preflight; mod spl_token_2022_execution_orchestration; +mod spl_token_2022_metadata; mod spl_token_2022_preflight; mod spl_token_2022_proof_orchestration; mod spl_token_2022_stateful; @@ -251,6 +252,16 @@ pub use self::spl_token_2022_execution_orchestration::Token2022ExecutionReadines pub use self::spl_token_2022_execution_orchestration::summarize_token_2022_postconditions; /// Validates the complete Token-2022 execution envelope before signing. pub use self::spl_token_2022_execution_orchestration::validate_token_2022_execution_readiness; +/// Maximum Token Metadata bytes accepted from Solana return data. +pub use self::spl_token_2022_metadata::MAX_TOKEN_2022_METADATA_EMIT_BYTES; +/// Bounded evidence extracted from one successful Token Metadata `Emit` simulation. +pub use self::spl_token_2022_metadata::Token2022MetadataEmitEvidence; +/// Checks the final embedded metadata authority against one authoritative snapshot. +pub use self::spl_token_2022_metadata::inspect_token_2022_metadata_authority_postcondition; +/// Extracts and validates Token Metadata return data from one exact simulation. +pub use self::spl_token_2022_metadata::inspect_token_2022_metadata_emit_simulation; +/// Validates one Token Metadata `Emit` range against Solana return-data bounds. +pub use self::spl_token_2022_metadata::validate_token_2022_metadata_emit_range; /// Migrated MAX_TOKEN_2022_PREFLIGHT_ACCOUNTS contract. pub use self::spl_token_2022_preflight::MAX_TOKEN_2022_PREFLIGHT_ACCOUNTS; /// Migrated MAX_TOKEN_2022_PREFLIGHT_TOTAL_BYTES contract. diff --git a/kb-pipeline/src/spl_token_2022_metadata.rs b/kb-pipeline/src/spl_token_2022_metadata.rs new file mode 100644 index 0000000..573098a --- /dev/null +++ b/kb-pipeline/src/spl_token_2022_metadata.rs @@ -0,0 +1,376 @@ +// file: kb-pipeline/src/spl_token_2022_metadata.rs +// version: 3 + +//! Token-2022 Token Metadata return-data and authority postcondition contracts. + +use base64::Engine; // rust-rules: trait-import + +/// Maximum Token Metadata bytes accepted from Solana return data. +pub const MAX_TOKEN_2022_METADATA_EMIT_BYTES: usize = 1_024; + +/// Bounded evidence extracted from one successful Token Metadata `Emit` simulation. +#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct Token2022MetadataEmitEvidence { + /// Program that produced the return data. + pub program_id: std::string::String, + /// Optional requested range start. + pub start: std::option::Option, + /// Optional requested range end. + pub end: std::option::Option, + /// Decoded return-data bytes. + pub data: std::vec::Vec, + /// Exact complete Token Metadata projection, only for an un-ranged emit. + pub decoded_metadata: std::option::Option, +} + +/// Validates one Token Metadata `Emit` range against Solana return-data bounds. +pub fn validate_token_2022_metadata_emit_range( + start: std::option::Option, + end: std::option::Option, +) -> kb_core::Result<()> { + if let (std::option::Option::Some(start), std::option::Option::Some(end)) = (start, end) { + if start > end { + return std::result::Result::Err(kb_core::Error::new( + "token_2022_metadata_emit_range_invalid", + format!("Token Metadata Emit start {start} exceeds end {end}"), + )); + } + if end - start > crate::MAX_TOKEN_2022_METADATA_EMIT_BYTES as u64 { + return std::result::Result::Err(kb_core::Error::new( + "token_2022_metadata_emit_range_too_large", + format!( + "Token Metadata Emit range exceeds {} bytes", + crate::MAX_TOKEN_2022_METADATA_EMIT_BYTES + ), + )); + } + } + if let (std::option::Option::None, std::option::Option::Some(end)) = (start, end) { + if end > crate::MAX_TOKEN_2022_METADATA_EMIT_BYTES as u64 { + return std::result::Result::Err(kb_core::Error::new( + "token_2022_metadata_emit_range_too_large", + format!( + "Token Metadata Emit range from zero exceeds {} bytes", + crate::MAX_TOKEN_2022_METADATA_EMIT_BYTES + ), + )); + } + } + if let (std::option::Option::Some(_), std::option::Option::None) = (start, end) { + return std::result::Result::Err(kb_core::Error::new( + "token_2022_metadata_emit_range_end_required", + "Token Metadata Emit with a start offset requires an explicit end to remain bounded", + )); + } + return std::result::Result::Ok(()); +} + +/// Extracts and validates Token Metadata return data from one exact simulation. +pub fn inspect_token_2022_metadata_emit_simulation( + simulation: &kb_lib::ExApiExecutionSimulationResult, + start: std::option::Option, + end: std::option::Option, +) -> kb_core::Result { + match crate::validate_token_2022_metadata_emit_range(start, end) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + if !simulation.simulated || !simulation.success { + return std::result::Result::Err(kb_core::Error::new( + "token_2022_metadata_emit_simulation_required", + "Token Metadata Emit evidence requires a successful simulation", + )); + } + let return_data = match simulation.return_data.as_ref() { + std::option::Option::Some(value) => value, + std::option::Option::None => { + return std::result::Result::Err(kb_core::Error::new( + "token_2022_metadata_emit_return_data_missing", + "Token Metadata Emit simulation did not return program data", + )); + }, + }; + let program_id = match return_data.get("programId").and_then(serde_json::Value::as_str) { + std::option::Option::Some(value) => value, + std::option::Option::None => { + return std::result::Result::Err(kb_core::Error::new( + "token_2022_metadata_emit_program_id_missing", + "Token Metadata Emit return data is missing programId", + )); + }, + }; + if program_id != kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID { + return std::result::Result::Err(kb_core::Error::new( + "token_2022_metadata_emit_program_id_mismatch", + format!("Token Metadata Emit return data came from {program_id}"), + )); + } + let data = match return_data.get("data").and_then(serde_json::Value::as_array) { + std::option::Option::Some(value) if value.len() == 2 => value, + _ => { + return std::result::Result::Err(kb_core::Error::new( + "token_2022_metadata_emit_data_invalid", + "Token Metadata Emit return data must be [payload, encoding]", + )); + }, + }; + let payload = match data.first().and_then(|value| return value.as_str()) { + std::option::Option::Some(value) => value, + std::option::Option::None => { + return std::result::Result::Err(kb_core::Error::new( + "token_2022_metadata_emit_payload_invalid", + "Token Metadata Emit return payload must be a base64 string", + )); + }, + }; + let encoding = match data.get(1).and_then(|value| return value.as_str()) { + std::option::Option::Some(value) => value, + std::option::Option::None => { + return std::result::Result::Err(kb_core::Error::new( + "token_2022_metadata_emit_encoding_invalid", + "Token Metadata Emit return data encoding is missing", + )); + }, + }; + if encoding != "base64" { + return std::result::Result::Err(kb_core::Error::new( + "token_2022_metadata_emit_encoding_invalid", + "Token Metadata Emit return data encoding must be base64", + )); + } + let decoded = match base64::engine::general_purpose::STANDARD.decode(payload) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => { + return std::result::Result::Err(kb_core::Error::new( + "token_2022_metadata_emit_base64_invalid", + format!("Token Metadata Emit return payload is invalid base64: {error}"), + )); + }, + }; + if decoded.len() > crate::MAX_TOKEN_2022_METADATA_EMIT_BYTES { + return std::result::Result::Err(kb_core::Error::new( + "token_2022_metadata_emit_data_too_large", + format!( + "Token Metadata Emit returned {} bytes, maximum is {}", + decoded.len(), + crate::MAX_TOKEN_2022_METADATA_EMIT_BYTES + ), + )); + } + let decoded_metadata = if start.is_none() && end.is_none() { + match kb_lib::decoder_spl_token_2022_parse_token_metadata_value(decoded.as_slice()) { + std::result::Result::Ok(value) => std::option::Option::Some(value), + std::result::Result::Err(error) => { + return std::result::Result::Err(kb_core::Error::new( + "token_2022_metadata_emit_payload_decode_failed", + error, + )); + }, + } + } else { + std::option::Option::None + }; + return std::result::Result::Ok(crate::Token2022MetadataEmitEvidence { + program_id: program_id.to_string(), + start, + end, + data: decoded, + decoded_metadata, + }); +} + +/// Checks the final embedded metadata authority against one authoritative snapshot. +pub fn inspect_token_2022_metadata_authority_postcondition( + account: &kb_lib::MdPubkey, + expected_authority: std::option::Option<&kb_lib::MdPubkey>, + snapshot: &crate::Token2022StatefulSnapshotBundle, +) -> crate::Token2022ExecutionPostcondition { + if snapshot.account_key != account.0 { + return crate::Token2022ExecutionPostcondition { + role: "metadata".to_string(), + account: account.clone(), + status: crate::Token2022ExecutionPostconditionStatus::Contradicted, + diagnostic: "Token-2022 metadata snapshot belongs to a different account".to_string(), + }; + } + let output = snapshot.outputs.iter().find(|output| { + return output.family == kb_lib::MdMaterializedEventFamily::Metadata + && output.payload_json.get("projectionKind").and_then(serde_json::Value::as_str) + == std::option::Option::Some("token_metadata"); + }); + let observed = output.and_then(|value| { + return value.payload_json.get("valueFields").and_then(|fields| { + return fields.get("updateAuthority"); + }); + }); + let confirmed = match (expected_authority, observed) { + (std::option::Option::Some(expected), std::option::Option::Some(value)) => { + value.as_str() == std::option::Option::Some(expected.0.as_str()) + }, + (std::option::Option::None, std::option::Option::Some(value)) => value.is_null(), + _ => false, + }; + return crate::Token2022ExecutionPostcondition { + role: "metadata".to_string(), + account: account.clone(), + status: if confirmed { + crate::Token2022ExecutionPostconditionStatus::Confirmed + } else { + crate::Token2022ExecutionPostconditionStatus::Contradicted + }, + diagnostic: if confirmed { + "authoritative Token-2022 metadata snapshot matches the expected update authority" + .to_string() + } else { + "authoritative Token-2022 metadata snapshot contradicts or omits the expected update authority" + .to_string() + }, + }; +} + +#[cfg(test)] +mod tests { + use base64::Engine; // rust-rules: trait-import + + fn simulation(data: &[u8]) -> kb_lib::ExApiExecutionSimulationResult { + return kb_lib::ExApiExecutionSimulationResult { + simulated: true, + success: true, + cluster: kb_lib::ExApiExecutionCluster::Devnet, + blockhash_kind: kb_lib::ExApiExecutionBlockhashKind::Latest, + blockhash_age_slots: std::option::Option::Some(0), + replacement_blockhash: std::option::Option::None, + replacement_last_valid_block_height: std::option::Option::None, + nonce_account: std::option::Option::None, + nonce_authority: std::option::Option::None, + units_consumed: std::option::Option::Some(1), + estimated_fee_lamports: std::option::Option::Some(5_000), + logs: std::vec::Vec::new(), + return_data: std::option::Option::Some(serde_json::json!({ + "programId": kb_program_ids::SPL_TOKEN_2022_PROGRAM_ID, + "data": [base64::engine::general_purpose::STANDARD.encode(data), "base64"] + })), + error: std::option::Option::None, + }; + } + + fn metadata_bytes(authority: std::option::Option<[u8; 32]>) -> std::vec::Vec { + let mut value = std::vec::Vec::new(); + value.extend_from_slice(authority.unwrap_or([0_u8; 32]).as_slice()); + value.extend_from_slice([7_u8; 32].as_slice()); + for field in ["Token", "TKN", "https://example.invalid/token.json"] { + value.extend_from_slice((field.len() as u32).to_le_bytes().as_slice()); + value.extend_from_slice(field.as_bytes()); + } + value.extend_from_slice(0_u32.to_le_bytes().as_slice()); + return value; + } + + #[test] + fn complete_emit_decodes_exact_metadata_and_ranged_emit_keeps_only_bytes() { + let complete = crate::inspect_token_2022_metadata_emit_simulation( + &simulation(metadata_bytes(std::option::Option::Some([9_u8; 32])).as_slice()), + std::option::Option::None, + std::option::Option::None, + ) + .unwrap_or_else(|error| panic!("complete Emit evidence failed: {error}")); + assert_eq!( + complete + .decoded_metadata + .as_ref() + .and_then(|value| return value.get("name")) + .and_then(serde_json::Value::as_str), + std::option::Option::Some("Token") + ); + let ranged = crate::inspect_token_2022_metadata_emit_simulation( + &simulation(&[1_u8, 2_u8, 3_u8]), + std::option::Option::Some(0), + std::option::Option::Some(3), + ) + .unwrap_or_else(|error| panic!("ranged Emit evidence failed: {error}")); + assert!(ranged.decoded_metadata.is_none()); + assert_eq!(ranged.data, vec![1_u8, 2_u8, 3_u8]); + } + + #[test] + fn emit_range_and_provider_contract_fail_closed() { + assert!( + crate::validate_token_2022_metadata_emit_range( + std::option::Option::Some(2), + std::option::Option::Some(1), + ) + .is_err() + ); + assert!( + crate::validate_token_2022_metadata_emit_range( + std::option::Option::Some(0), + std::option::Option::Some(1_025), + ) + .is_err() + ); + assert!( + crate::validate_token_2022_metadata_emit_range( + std::option::Option::Some(1), + std::option::Option::None, + ) + .is_err() + ); + let mut invalid = simulation(&[1_u8]); + invalid.return_data = std::option::Option::Some(serde_json::json!({ + "programId": kb_program_ids::SYSTEM_PROGRAM_ID, + "data": ["AQ==", "base64"] + })); + assert!( + crate::inspect_token_2022_metadata_emit_simulation( + &invalid, + std::option::Option::Some(0), + std::option::Option::Some(1) + ) + .is_err() + ); + } + + #[test] + fn authority_postcondition_accepts_exact_value_and_null() { + let account = kb_lib::MdPubkey(bs58::encode([3_u8; 32]).into_string()); + let authority = kb_lib::MdPubkey(bs58::encode([4_u8; 32]).into_string()); + let mut snapshot = crate::Token2022StatefulSnapshotBundle { + account_key: account.0.clone(), + slot: 9, + state_kind: "mint".to_string(), + extension_names: vec!["token_metadata".to_string()], + outputs: vec![kb_lib::MtApiMaterializedOutput { + output_key: "metadata".to_string(), + family: kb_lib::MdMaterializedEventFamily::Metadata, + payload_json: serde_json::json!({ + "projectionKind": "token_metadata", + "valueFields": {"updateAuthority": authority.0} + }), + }], + }; + let confirmed = crate::inspect_token_2022_metadata_authority_postcondition( + &account, + std::option::Option::Some(&authority), + &snapshot, + ); + assert_eq!(confirmed.status, crate::Token2022ExecutionPostconditionStatus::Confirmed); + snapshot.outputs[0].payload_json["valueFields"]["updateAuthority"] = + serde_json::Value::Null; + let cleared = crate::inspect_token_2022_metadata_authority_postcondition( + &account, + std::option::Option::None, + &snapshot, + ); + assert_eq!(cleared.status, crate::Token2022ExecutionPostconditionStatus::Confirmed); + snapshot.account_key = bs58::encode([8_u8; 32]).into_string(); + let wrong_account = crate::inspect_token_2022_metadata_authority_postcondition( + &account, + std::option::Option::None, + &snapshot, + ); + assert_eq!( + wrong_account.status, + crate::Token2022ExecutionPostconditionStatus::Contradicted + ); + } +} diff --git a/test-fixtures/contract-matrices/OPERATION_NAMING_MATRIX.json b/test-fixtures/contract-matrices/OPERATION_NAMING_MATRIX.json index 7837b23..4fe72a7 100644 --- a/test-fixtures/contract-matrices/OPERATION_NAMING_MATRIX.json +++ b/test-fixtures/contract-matrices/OPERATION_NAMING_MATRIX.json @@ -1780,6 +1780,17 @@ "status": "implemented", "source": "kb-lib/src/executor/spl/token_2022/intent.rs" }, + { + "programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", + "processorName": "spl.token_2022", + "surfaceCode": "spl.token_2022", + "operationCode": "spl.token_2022.emit_token_metadata", + "eventCode": "spl.token_2022.emit_token_metadata", + "executorName": "kb-lib.executor.spl.token_2022", + "decoderName": "kb-lib.decoder.spl.token_2022", + "status": "implemented", + "source": "kb-lib/src/executor/spl/token_2022/intent.rs" + }, { "programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", "processorName": "spl.token_2022", @@ -2473,6 +2484,17 @@ "status": "implemented", "source": "kb-lib/src/executor/spl/token_2022/intent.rs" }, + { + "programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", + "processorName": "spl.token_2022", + "surfaceCode": "spl.token_2022", + "operationCode": "spl.token_2022.update_token_metadata_authority", + "eventCode": "spl.token_2022.update_token_metadata_authority", + "executorName": "kb-lib.executor.spl.token_2022", + "decoderName": "kb-lib.decoder.spl.token_2022", + "status": "implemented", + "source": "kb-lib/src/executor/spl/token_2022/intent.rs" + }, { "programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb", "processorName": "spl.token_2022", diff --git a/test-fixtures/contract-matrices/SOLANA_PROGRAM_METADATA_DEVNET_VALIDATION_MATRIX.json b/test-fixtures/contract-matrices/SOLANA_PROGRAM_METADATA_DEVNET_VALIDATION_MATRIX.json index fe1b536..f1dc747 100644 --- a/test-fixtures/contract-matrices/SOLANA_PROGRAM_METADATA_DEVNET_VALIDATION_MATRIX.json +++ b/test-fixtures/contract-matrices/SOLANA_PROGRAM_METADATA_DEVNET_VALIDATION_MATRIX.json @@ -5,7 +5,7 @@ { "operationCode": "metadata.solana_program_metadata.allocate", "scenarioStepId": "spm_buffer_allocate", - "status": "not_run", + "status": "confirmed", "requiresExplicitApproval": false, "requiredEvidence": [ "stateful_preflight", @@ -14,12 +14,53 @@ "stateful_postcondition", "materialized_snapshot" ], - "evidence": [] + "evidence": [ + { + "kind": "stateful_preflight", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-1" + }, + { + "kind": "stateful_preflight", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-2" + }, + { + "kind": "rpc_simulation", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-1" + }, + { + "kind": "rpc_simulation", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-2" + }, + { + "kind": "confirmed_signature", + "value": "hrruFbF7MPHBrQaAFES8DtssekHBbodzZxJ13PvXKLE8RLrkExtAixeVLS8p3mDNWtLvi5uFjKZUJund7uSJEm9" + }, + { + "kind": "confirmed_signature", + "value": "2cDx9LHzFYZkhEwyYEtUBS2cUraMnNx13eJrTMmPbeKpm8QhMUP6tenQS6MN3ryEityQPEVNBdJ3djDXRbUube52" + }, + { + "kind": "stateful_postcondition", + "value": "confirmed:campaign-1" + }, + { + "kind": "stateful_postcondition", + "value": "confirmed:campaign-2" + }, + { + "kind": "materialized_snapshot", + "value": "CzQ7jreDruBGuiTXVL4bH21x15zzdFcGx1bLbLStBNXe" + }, + { + "kind": "materialized_snapshot", + "value": "S8zSKJRR1SaavfUyVBctDjdFE36R85NDz99hmL8ER5A" + } + ] }, { "operationCode": "metadata.solana_program_metadata.extend", "scenarioStepId": "spm_buffer_extend", - "status": "not_run", + "status": "confirmed", "requiresExplicitApproval": false, "requiredEvidence": [ "stateful_preflight", @@ -28,12 +69,53 @@ "stateful_postcondition", "materialized_snapshot" ], - "evidence": [] + "evidence": [ + { + "kind": "stateful_preflight", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-1" + }, + { + "kind": "stateful_preflight", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-2" + }, + { + "kind": "rpc_simulation", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-1" + }, + { + "kind": "rpc_simulation", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-2" + }, + { + "kind": "confirmed_signature", + "value": "4oeoQ6pyQmwb1btDA6tmDRnqxAtvUAjgRe8RV7NyZAp6CZQvLaEh15BPVxNEx9wzgiEHXbLD41u8Jn6QdWE7mcsi" + }, + { + "kind": "confirmed_signature", + "value": "2UbjRTzGCyW6XjRUHiWBicQbBYzKQFYBX2pux2nBu2AHYuSS7R4FRNMY4ugc3AAXPqEYor37AJKKzWsGQUB6Wdoq" + }, + { + "kind": "stateful_postcondition", + "value": "confirmed:campaign-1" + }, + { + "kind": "stateful_postcondition", + "value": "confirmed:campaign-2" + }, + { + "kind": "materialized_snapshot", + "value": "CzQ7jreDruBGuiTXVL4bH21x15zzdFcGx1bLbLStBNXe" + }, + { + "kind": "materialized_snapshot", + "value": "S8zSKJRR1SaavfUyVBctDjdFE36R85NDz99hmL8ER5A" + } + ] }, { "operationCode": "metadata.solana_program_metadata.write", "scenarioStepId": "spm_buffer_write", - "status": "not_run", + "status": "confirmed", "requiresExplicitApproval": true, "requiredEvidence": [ "stateful_preflight", @@ -42,12 +124,53 @@ "stateful_postcondition", "materialized_snapshot" ], - "evidence": [] + "evidence": [ + { + "kind": "stateful_preflight", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-1" + }, + { + "kind": "stateful_preflight", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-2" + }, + { + "kind": "rpc_simulation", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-1" + }, + { + "kind": "rpc_simulation", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-2" + }, + { + "kind": "confirmed_signature", + "value": "583BJdxkXezHJfJjDFHHKUpS5Ry8yesh7YXav1NDGoUbdZ7i7z9UJofGjwJ2GjegLHjZ6q9e4UDVSUK3KiRqBJkS" + }, + { + "kind": "confirmed_signature", + "value": "5qQFYc2DuwohE4fe29sBatLXGPwmKKbPfgWEWEYXymYXqj4ntm3BvvVxZEhBydzX7hpqRhXMYzssJ2UDwsh93sar" + }, + { + "kind": "stateful_postcondition", + "value": "confirmed:campaign-1" + }, + { + "kind": "stateful_postcondition", + "value": "confirmed:campaign-2" + }, + { + "kind": "materialized_snapshot", + "value": "CzQ7jreDruBGuiTXVL4bH21x15zzdFcGx1bLbLStBNXe" + }, + { + "kind": "materialized_snapshot", + "value": "S8zSKJRR1SaavfUyVBctDjdFE36R85NDz99hmL8ER5A" + } + ] }, { "operationCode": "metadata.solana_program_metadata.set_authority", "scenarioStepId": "spm_buffer_set_authority", - "status": "not_run", + "status": "confirmed", "requiresExplicitApproval": true, "requiredEvidence": [ "stateful_preflight", @@ -56,12 +179,53 @@ "stateful_postcondition", "materialized_snapshot" ], - "evidence": [] + "evidence": [ + { + "kind": "stateful_preflight", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-1" + }, + { + "kind": "stateful_preflight", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-2" + }, + { + "kind": "rpc_simulation", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-1" + }, + { + "kind": "rpc_simulation", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-2" + }, + { + "kind": "confirmed_signature", + "value": "39YD2QCKKSdy1Rt6g6oFXwjoRA5jMVGAwerbzJ9q1dk748Qj5jaiFHPy7JPqarExKuNsByBF6K2BiTaVML8LQAyk" + }, + { + "kind": "confirmed_signature", + "value": "5jRZZAghEtAj3fj5eNVsYwpPiz4ofaWDtQ6DXb3UzbxJ8s6rqDL2prWiPsxrjMqFk21bEtDfMB6ykF2eZiaWiai9" + }, + { + "kind": "stateful_postcondition", + "value": "confirmed:campaign-1" + }, + { + "kind": "stateful_postcondition", + "value": "confirmed:campaign-2" + }, + { + "kind": "materialized_snapshot", + "value": "CzQ7jreDruBGuiTXVL4bH21x15zzdFcGx1bLbLStBNXe" + }, + { + "kind": "materialized_snapshot", + "value": "S8zSKJRR1SaavfUyVBctDjdFE36R85NDz99hmL8ER5A" + } + ] }, { "operationCode": "metadata.solana_program_metadata.trim", "scenarioStepId": "spm_buffer_trim", - "status": "not_run", + "status": "confirmed", "requiresExplicitApproval": true, "requiredEvidence": [ "stateful_preflight", @@ -70,12 +234,53 @@ "stateful_postcondition", "materialized_snapshot" ], - "evidence": [] + "evidence": [ + { + "kind": "stateful_preflight", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-1" + }, + { + "kind": "stateful_preflight", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-2" + }, + { + "kind": "rpc_simulation", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-1" + }, + { + "kind": "rpc_simulation", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-2" + }, + { + "kind": "confirmed_signature", + "value": "55VveVj7EBhFvcGi7CLWdv21eWUqAgYJ2iouNnn57n4hxN2CsUcPikS94bdCrd5sUDieBk9QWNse3hDJx2fpTjai" + }, + { + "kind": "confirmed_signature", + "value": "4hwdgGM4VUsawwEThAGbRZuaVu4rzj7Nj2hvD1W6nPyofEgDrSYix6BhGq1m8GbQ45CpeG7SNBHGXEnsnY6aKfih" + }, + { + "kind": "stateful_postcondition", + "value": "confirmed:campaign-1" + }, + { + "kind": "stateful_postcondition", + "value": "confirmed:campaign-2" + }, + { + "kind": "materialized_snapshot", + "value": "CzQ7jreDruBGuiTXVL4bH21x15zzdFcGx1bLbLStBNXe" + }, + { + "kind": "materialized_snapshot", + "value": "S8zSKJRR1SaavfUyVBctDjdFE36R85NDz99hmL8ER5A" + } + ] }, { "operationCode": "metadata.solana_program_metadata.close", "scenarioStepId": "spm_buffer_close", - "status": "not_run", + "status": "confirmed", "requiresExplicitApproval": true, "requiredEvidence": [ "stateful_preflight", @@ -84,12 +289,53 @@ "stateful_postcondition", "account_absence" ], - "evidence": [] + "evidence": [ + { + "kind": "stateful_preflight", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-1" + }, + { + "kind": "stateful_preflight", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-2" + }, + { + "kind": "rpc_simulation", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-1" + }, + { + "kind": "rpc_simulation", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-2" + }, + { + "kind": "confirmed_signature", + "value": "3uTnm7BLvsKiwgUytoarYWfFxudZJGaouziEKTHb2VuG6gSTNZzeXP3npgJ8RLqw7EcVCY15ZWZEdeEzoaf2Bv5F" + }, + { + "kind": "confirmed_signature", + "value": "2w8HXb2JS6M7ipMXb7uDTbzCGUwAQYKcW8rSGy5AMhx5MayiQ4Z2pK7ZiMAJ3wxEFSetTAh2UC1BSJ9p2MaC7vSp" + }, + { + "kind": "stateful_postcondition", + "value": "confirmed:campaign-1" + }, + { + "kind": "stateful_postcondition", + "value": "confirmed:campaign-2" + }, + { + "kind": "account_absence", + "value": "CzQ7jreDruBGuiTXVL4bH21x15zzdFcGx1bLbLStBNXe" + }, + { + "kind": "account_absence", + "value": "S8zSKJRR1SaavfUyVBctDjdFE36R85NDz99hmL8ER5A" + } + ] }, { "operationCode": "metadata.solana_program_metadata.initialize", "scenarioStepId": "spm_metadata_initialize", - "status": "not_run", + "status": "confirmed", "requiresExplicitApproval": false, "requiredEvidence": [ "stateful_preflight", @@ -98,12 +344,53 @@ "stateful_postcondition", "materialized_snapshot" ], - "evidence": [] + "evidence": [ + { + "kind": "stateful_preflight", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-1" + }, + { + "kind": "stateful_preflight", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-2" + }, + { + "kind": "rpc_simulation", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-1" + }, + { + "kind": "rpc_simulation", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-2" + }, + { + "kind": "confirmed_signature", + "value": "4gGG6pxuFoR84yDsw1w3dskq5b1YFEbDqCKoLDTV7KKFrv5RJwfQDNh7gu3xaHu12KKKgK26qKj22dGQ8Er5LAEt" + }, + { + "kind": "confirmed_signature", + "value": "2YvSmrXNLBb5kQuBW4mVWZ1QwuFFMNtM2QaqmbUdHRZtJ7DxBE3MhciqXF2zsNSJiLkYdCahib5h2SZXbEHkJTbU" + }, + { + "kind": "stateful_postcondition", + "value": "confirmed:campaign-1" + }, + { + "kind": "stateful_postcondition", + "value": "confirmed:campaign-2" + }, + { + "kind": "materialized_snapshot", + "value": "FfFfxwnaoiK8Z5sjA5uFi4Jej4oaZLJczVXQZoCouDvF" + }, + { + "kind": "materialized_snapshot", + "value": "CHfUbAnQYw7WYYsd3weLDNzsCj25vMUGUVqxNMG57xEJ" + } + ] }, { "operationCode": "metadata.solana_program_metadata.set_data", "scenarioStepId": "spm_metadata_set_data", - "status": "not_run", + "status": "confirmed", "requiresExplicitApproval": true, "requiredEvidence": [ "stateful_preflight", @@ -112,12 +399,53 @@ "stateful_postcondition", "materialized_snapshot" ], - "evidence": [] + "evidence": [ + { + "kind": "stateful_preflight", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-1" + }, + { + "kind": "stateful_preflight", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-2" + }, + { + "kind": "rpc_simulation", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-1" + }, + { + "kind": "rpc_simulation", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-2" + }, + { + "kind": "confirmed_signature", + "value": "3C14hqBQVfDGEUxMa14rgKzavr2fXdysK7A7F2crhdoHBbFDnt2cGPMHRwNGJMpojRvBEniQXtqstMyTAQFr13jf" + }, + { + "kind": "confirmed_signature", + "value": "S8DyKTZVM78s5TgZKaTVoFopRyj4SMZaRRZ9u8RzMciYvKvQKYbX9jj3RkPVJub3e9AAuxNPRvxrDc52Ckg4eX6" + }, + { + "kind": "stateful_postcondition", + "value": "confirmed:campaign-1" + }, + { + "kind": "stateful_postcondition", + "value": "confirmed:campaign-2" + }, + { + "kind": "materialized_snapshot", + "value": "FfFfxwnaoiK8Z5sjA5uFi4Jej4oaZLJczVXQZoCouDvF" + }, + { + "kind": "materialized_snapshot", + "value": "CHfUbAnQYw7WYYsd3weLDNzsCj25vMUGUVqxNMG57xEJ" + } + ] }, { "operationCode": "metadata.solana_program_metadata.set_immutable", "scenarioStepId": "spm_metadata_set_immutable", - "status": "not_run", + "status": "confirmed", "requiresExplicitApproval": true, "requiredEvidence": [ "stateful_preflight", @@ -126,7 +454,48 @@ "stateful_postcondition", "materialized_snapshot" ], - "evidence": [] + "evidence": [ + { + "kind": "stateful_preflight", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-1" + }, + { + "kind": "stateful_preflight", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-2" + }, + { + "kind": "rpc_simulation", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-1" + }, + { + "kind": "rpc_simulation", + "value": "docs/validation/evidence/v0.4.8/pre.010/devnet/devnet_evidence_v0.4.8-pre.010-01.zip#campaign-2" + }, + { + "kind": "confirmed_signature", + "value": "3obFqQAPr1usqcJJXho56qxgktC4HRtCQBD9eC29NyKaSMa5eNXKLcY9TWCMPo9iEVekxt6YvmUVdGAEGUCABSho" + }, + { + "kind": "confirmed_signature", + "value": "Y8Sf1ApvCmgiQZ4BSFumJUxsEyybrfU2zCv5mzcaMsn5opJnDnAMNu5Vb5xRm6xUMFcoLuAPm7QsBH5AdySXwjr" + }, + { + "kind": "stateful_postcondition", + "value": "confirmed:campaign-1" + }, + { + "kind": "stateful_postcondition", + "value": "confirmed:campaign-2" + }, + { + "kind": "materialized_snapshot", + "value": "FfFfxwnaoiK8Z5sjA5uFi4Jej4oaZLJczVXQZoCouDvF" + }, + { + "kind": "materialized_snapshot", + "value": "CHfUbAnQYw7WYYsd3weLDNzsCj25vMUGUVqxNMG57xEJ" + } + ] } ] }