v0.4.7-pre.003
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# file: Cargo.toml
|
||||
# version: 9
|
||||
# version: 10
|
||||
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
@@ -18,7 +18,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.4.7-pre.2"
|
||||
version = "0.4.7-pre.3"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-bot3"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/plans/V0_4_7_METAPLEX_TOKEN_METADATA_COMPLETION_PLAN.md -->
|
||||
<!-- version: 3 -->
|
||||
<!-- version: 4 -->
|
||||
|
||||
# Plan `0.4.7` — achèvement de Metaplex Token Metadata
|
||||
|
||||
@@ -155,7 +155,7 @@ Le développement fonctionnel de `0.4.7` est limité aux éléments suivants.
|
||||
- `decode_only_historical` ;
|
||||
- `decode_only_bridge_boundary` ;
|
||||
- `unsupported_with_reason` ;
|
||||
- `decode_only_obsolete` ;
|
||||
- `executable_deprecated` ;
|
||||
- `decode_only_replaced`.
|
||||
3. Attribuer à chaque instruction exécutable : builder officiel, comptes, signers, préconditions, confirmation et postconditions.
|
||||
4. Maintenir l’inventaire exhaustif `0..57`.
|
||||
@@ -351,7 +351,7 @@ La numérotation ci-dessous est un plan fermé initial. Elle peut être regroup
|
||||
**Travaux**
|
||||
|
||||
- intents communs, enveloppe d’exécution et politiques de coût ;
|
||||
- builders create/update actuels officiellement constructibles ;
|
||||
- builders des wrappers courants `Create` (42) et `Update` (50), sans reconstruire les anciennes instructions remplacées ;
|
||||
- signers, comptes, PDA et mutabilité ;
|
||||
- simulation obligatoire ;
|
||||
- postconditions de création et mise à jour.
|
||||
@@ -366,10 +366,9 @@ La numérotation ci-dessous est un plan fermé initial. Elle peut être regroup
|
||||
|
||||
**Travaux**
|
||||
|
||||
- verify/unverify/set-and-verify ;
|
||||
- sized collection items ;
|
||||
- collection authority approve/revoke ;
|
||||
- creator sign/unverify et primary sale ;
|
||||
- wrappers courants `Verify` et `Unverify` pour collections et créateurs ;
|
||||
- wrapper courant `Delegate`/`Revoke` pour les autorités de collection lorsque la variante correspondante est prouvée ;
|
||||
- aucune reconstruction de `SignMetadata`, des instructions sized collection historiques ni de `UpdatePrimarySaleHappenedViaToken` ;
|
||||
- confirmations opérateur et postconditions.
|
||||
|
||||
**Acceptation**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: docs/rules/RULES_SPECIFIC_KHADHROONY.md -->
|
||||
<!-- version: 7 -->
|
||||
<!-- version: 8 -->
|
||||
|
||||
# Règles spécifiques à `khadhroony-bot3`
|
||||
|
||||
@@ -56,11 +56,15 @@ Toute divergence avec une règle générale doit être explicitement documentée
|
||||
|
||||
### Couverture des exécuteurs
|
||||
|
||||
- Pour chaque programme Solana, les exécuteurs doivent couvrir maximalement toutes les opérations officiellement constructibles ou appelables qui ne sont ni obsolètes ni remplacées par une variante canonique imposée.
|
||||
- Pour chaque programme Solana, les exécuteurs doivent couvrir maximalement toutes les opérations officiellement constructibles ou appelables, y compris les opérations obsolètes qui restent techniquement constructibles.
|
||||
- Cette obligation couvre les opérations courantes, récentes, expérimentales, en développement et publiées avant leur déploiement généralisé dès lors que leur wire, leurs comptes, leurs signers et leurs garde-fous exacts sont prouvés.
|
||||
- Une opération ne peut pas être exclue parce qu'elle n'a pas été arbitrairement retenue pour une version. Chaque opération officiellement connue doit recevoir un statut déterministe : `Supported`, `Unsupported(reason)`, `DecodeOnlyHistorical`, `DecodeOnlyObsolete`, `DecodeOnlyReplaced` ou frontière de programme explicitement documentée.
|
||||
- Lorsqu'une chaîne de versions a été remplacée, seule la dernière version canonique de remplacement reçoit un builder public courant. Toutes les versions intermédiaires remplacées restent décodables et matérialisables, déclarent leur remplacement canonique final et ne reçoivent pas de builder public distinct.
|
||||
- Une opération obsolète sans remplacement canonique final doit rester exécutable lorsqu'elle est encore officiellement constructible. Son API Rust publique doit être marquée avec `#[deprecated]`, sa matrice doit la déclarer `ExecutableDeprecated` et la surface runtime doit l’identifier explicitement comme dépréciée, et son exécution doit exiger une approbation opérateur explicite en plus des confirmations destructives ou sensibles éventuellement requises.
|
||||
- Une opération obsolète devenue techniquement impossible à construire peut rester decode-only, mais cette exception doit indiquer l'impossibilité contractuelle exacte et les sources qui la prouvent.
|
||||
- Une opération ne peut pas être exclue parce qu'elle n'a pas été arbitrairement retenue pour une version. Chaque opération officiellement connue doit recevoir un statut déterministe : `Supported`, `ExecutableDeprecated`, `Unsupported(reason)`, `DecodeOnlyHistorical`, `DecodeOnlyReplaced` ou frontière de programme explicitement documentée.
|
||||
- `Unsupported(reason)` est réservé à une impossibilité technique ou contractuelle précise et vérifiable. Il ne doit jamais masquer un travail non réalisé, un choix de priorité ou une absence d'inventaire.
|
||||
- Les opérations historiques, dépréciées, obsolètes ou remplacées restent décodables et matérialisables lorsqu'elles prouvent des faits, mais ne doivent pas être exposées à l'exécution lorsque la variante n'est plus légitime à construire.
|
||||
- Le marquage Rust `#[deprecated]` avertit le développeur à la compilation ; l'approbation opérateur protège l'exécution au runtime. L'un ne remplace jamais l'autre.
|
||||
- Les opérations historiques ou remplacées restent décodables et matérialisables lorsqu'elles prouvent des faits. Elles ne sont pas exposées à l'exécution lorsqu'un remplacement canonique final existe, sauf surface de compatibilité historique explicitement isolée, justifiée et elle-même dépréciée.
|
||||
- Le déploiement sur un cluster, la disponibilité d'une fixture, la simulation réseau et l'autorisation d'envoi sont des niveaux de preuve distincts ; leur absence ne supprime pas un builder universel officiellement constructible.
|
||||
- Toute exclusion d'exécution doit être explicite dans la matrice contractuelle, les capacités runtime et la documentation publique de la surface.
|
||||
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
<!-- file: docs/validation/V0_4_7_PRE_002_METAPLEX_COVERAGE_AND_FACT_OWNERSHIP.md -->
|
||||
<!-- version: 1 -->
|
||||
<!-- version: 2 -->
|
||||
|
||||
# `0.4.7-pre.002` — couverture Metaplex et propriété des faits
|
||||
|
||||
## Résultat
|
||||
## Résultat corrigé
|
||||
|
||||
Le contrat couvre les 58 discriminants `0..57` sans trou ni statut ambigu. La source des comptes et signers est l’IDL Metaplex archivée `V1_14_0`; la constructibilité courante est confrontée à l’inventaire `mpl-token-metadata 5.1.1`.
|
||||
Le contrat couvre les 58 discriminants `0..57` sans trou. La classification a été corrigée pendant `pre.003` après confrontation avec les recommandations de dépréciation officielles de Metaplex : une instruction encore décodable dans l’IDL historique n’est pas nécessairement légitime à reconstruire.
|
||||
|
||||
### Classification fermée
|
||||
- `decode_only_bridge_boundary` : 1 ;
|
||||
- `decode_only_obsolete` : 15 ;
|
||||
- `decode_only_replaced` : 22 ;
|
||||
- `executable_current` : 20.
|
||||
|
||||
- `decode_only_bridge_boundary` : 1 instruction(s) ;
|
||||
- `decode_only_obsolete` : 7 instruction(s) ;
|
||||
- `decode_only_replaced` : 9 instruction(s) ;
|
||||
- `executable_current` : 41 instruction(s) ;
|
||||
|
||||
Les opérations `executable_current` doivent toutes posséder un intent et un builder à la fin de `pre.006`. Les statuts decode-only ne peuvent être utilisés que pour une frontière Bubblegum, une opération obsolète ou une opération remplacée par une variante canonique.
|
||||
Les 20 opérations courantes doivent toutes posséder un intent et un builder à la fin de `pre.006`. Les instructions remplacées ou obsolètes restent décodées et peuvent produire les faits stables prouvés, mais leur reconstruction est interdite.
|
||||
|
||||
## Contrat par instruction
|
||||
|
||||
@@ -24,38 +22,38 @@ Les opérations `executable_current` doivent toutes posséder un intent et un bu
|
||||
| 1 | `UpdateMetadataAccount` | `decode_only_replaced` | `forbidden_replaced` | `—` | updateAuthority |
|
||||
| 2 | `DeprecatedCreateMasterEdition` | `decode_only_obsolete` | `forbidden_obsolete` | `—` | updateAuthority, printingMintAuthority, mintAuthority, payer, oneTimePrintingAuthorizationMintAuthority |
|
||||
| 3 | `DeprecatedMintNewEditionFromMasterEditionViaPrintingToken` | `decode_only_obsolete` | `forbidden_obsolete` | `—` | mintAuthority, burnAuthority, payer |
|
||||
| 4 | `UpdatePrimarySaleHappenedViaToken` | `executable_current` | `required` | `pre.004` | owner |
|
||||
| 4 | `UpdatePrimarySaleHappenedViaToken` | `decode_only_obsolete` | `forbidden_obsolete` | `—` | owner |
|
||||
| 5 | `DeprecatedSetReservationList` | `decode_only_obsolete` | `forbidden_obsolete` | `—` | resource |
|
||||
| 6 | `DeprecatedCreateReservationList` | `decode_only_obsolete` | `forbidden_obsolete` | `—` | payer, updateAuthority |
|
||||
| 7 | `SignMetadata` | `executable_current` | `required` | `pre.004` | creator |
|
||||
| 7 | `SignMetadata` | `decode_only_replaced` | `forbidden_replaced` | `—` | creator |
|
||||
| 8 | `DeprecatedMintPrintingTokensViaToken` | `decode_only_obsolete` | `forbidden_obsolete` | `—` | burnAuthority |
|
||||
| 9 | `DeprecatedMintPrintingTokens` | `decode_only_obsolete` | `forbidden_obsolete` | `—` | updateAuthority |
|
||||
| 10 | `CreateMasterEdition` | `decode_only_replaced` | `forbidden_replaced` | `—` | updateAuthority, mintAuthority, payer |
|
||||
| 11 | `MintNewEditionFromMasterEditionViaToken` | `executable_current` | `required` | `pre.006` | newMintAuthority, payer, tokenAccountOwner |
|
||||
| 12 | `ConvertMasterEditionV1ToV2` | `decode_only_replaced` | `forbidden_replaced` | `—` | aucun |
|
||||
| 11 | `MintNewEditionFromMasterEditionViaToken` | `decode_only_replaced` | `forbidden_replaced` | `—` | newMintAuthority, payer, tokenAccountOwner |
|
||||
| 12 | `ConvertMasterEditionV1ToV2` | `decode_only_obsolete` | `forbidden_obsolete` | `—` | aucun |
|
||||
| 13 | `MintNewEditionFromMasterEditionViaVaultProxy` | `decode_only_obsolete` | `forbidden_obsolete` | `—` | newMintAuthority, payer, vaultAuthority |
|
||||
| 14 | `PuffMetadata` | `executable_current` | `required` | `pre.003` | aucun |
|
||||
| 15 | `UpdateMetadataAccountV2` | `executable_current` | `required` | `pre.003` | updateAuthority |
|
||||
| 14 | `PuffMetadata` | `decode_only_obsolete` | `forbidden_obsolete` | `—` | aucun |
|
||||
| 15 | `UpdateMetadataAccountV2` | `decode_only_replaced` | `forbidden_replaced` | `—` | updateAuthority |
|
||||
| 16 | `CreateMetadataAccountV2` | `decode_only_replaced` | `forbidden_replaced` | `—` | mintAuthority, payer |
|
||||
| 17 | `CreateMasterEditionV3` | `executable_current` | `required` | `pre.006` | updateAuthority, mintAuthority, payer |
|
||||
| 18 | `VerifyCollection` | `executable_current` | `required` | `pre.004` | collectionAuthority, payer |
|
||||
| 19 | `Utilize` | `executable_current` | `required` | `pre.006` | useAuthority |
|
||||
| 20 | `ApproveUseAuthority` | `executable_current` | `required` | `pre.006` | owner, payer |
|
||||
| 21 | `RevokeUseAuthority` | `executable_current` | `required` | `pre.006` | owner |
|
||||
| 22 | `UnverifyCollection` | `executable_current` | `required` | `pre.004` | collectionAuthority |
|
||||
| 23 | `ApproveCollectionAuthority` | `executable_current` | `required` | `pre.004` | updateAuthority, payer |
|
||||
| 24 | `RevokeCollectionAuthority` | `executable_current` | `required` | `pre.004` | revokeAuthority |
|
||||
| 25 | `SetAndVerifyCollection` | `executable_current` | `required` | `pre.004` | collectionAuthority, payer |
|
||||
| 17 | `CreateMasterEditionV3` | `decode_only_replaced` | `forbidden_replaced` | `—` | updateAuthority, mintAuthority, payer |
|
||||
| 18 | `VerifyCollection` | `decode_only_replaced` | `forbidden_replaced` | `—` | collectionAuthority, payer |
|
||||
| 19 | `Utilize` | `decode_only_obsolete` | `forbidden_obsolete` | `—` | useAuthority |
|
||||
| 20 | `ApproveUseAuthority` | `decode_only_obsolete` | `forbidden_obsolete` | `—` | owner, payer |
|
||||
| 21 | `RevokeUseAuthority` | `decode_only_obsolete` | `forbidden_obsolete` | `—` | owner |
|
||||
| 22 | `UnverifyCollection` | `decode_only_replaced` | `forbidden_replaced` | `—` | collectionAuthority |
|
||||
| 23 | `ApproveCollectionAuthority` | `decode_only_replaced` | `forbidden_replaced` | `—` | updateAuthority, payer |
|
||||
| 24 | `RevokeCollectionAuthority` | `decode_only_replaced` | `forbidden_replaced` | `—` | revokeAuthority |
|
||||
| 25 | `SetAndVerifyCollection` | `decode_only_replaced` | `forbidden_replaced` | `—` | collectionAuthority, payer |
|
||||
| 26 | `FreezeDelegatedAccount` | `decode_only_replaced` | `forbidden_replaced` | `—` | delegate |
|
||||
| 27 | `ThawDelegatedAccount` | `decode_only_replaced` | `forbidden_replaced` | `—` | delegate |
|
||||
| 28 | `RemoveCreatorVerification` | `executable_current` | `required` | `pre.004` | creator |
|
||||
| 28 | `RemoveCreatorVerification` | `decode_only_replaced` | `forbidden_replaced` | `—` | creator |
|
||||
| 29 | `BurnNft` | `decode_only_replaced` | `forbidden_replaced` | `—` | owner |
|
||||
| 30 | `VerifySizedCollectionItem` | `executable_current` | `required` | `pre.004` | collectionAuthority, payer |
|
||||
| 31 | `UnverifySizedCollectionItem` | `executable_current` | `required` | `pre.004` | collectionAuthority, payer |
|
||||
| 32 | `SetAndVerifySizedCollectionItem` | `executable_current` | `required` | `pre.004` | collectionAuthority, payer |
|
||||
| 33 | `CreateMetadataAccountV3` | `executable_current` | `required` | `pre.003` | mintAuthority, payer |
|
||||
| 34 | `SetCollectionSize` | `executable_current` | `required` | `pre.004` | collectionAuthority |
|
||||
| 35 | `SetTokenStandard` | `executable_current` | `required` | `pre.003` | updateAuthority |
|
||||
| 30 | `VerifySizedCollectionItem` | `decode_only_replaced` | `forbidden_replaced` | `—` | collectionAuthority, payer |
|
||||
| 31 | `UnverifySizedCollectionItem` | `decode_only_replaced` | `forbidden_replaced` | `—` | collectionAuthority, payer |
|
||||
| 32 | `SetAndVerifySizedCollectionItem` | `decode_only_replaced` | `forbidden_replaced` | `—` | collectionAuthority, payer |
|
||||
| 33 | `CreateMetadataAccountV3` | `decode_only_replaced` | `forbidden_replaced` | `—` | mintAuthority, payer |
|
||||
| 34 | `SetCollectionSize` | `decode_only_obsolete` | `forbidden_obsolete` | `—` | collectionAuthority |
|
||||
| 35 | `SetTokenStandard` | `decode_only_obsolete` | `forbidden_obsolete` | `—` | updateAuthority |
|
||||
| 36 | `BubblegumSetCollectionSize` | `decode_only_bridge_boundary` | `forbidden_cross_program_boundary` | `—` | collectionAuthority, bubblegumSigner |
|
||||
| 37 | `BurnEditionNft` | `decode_only_replaced` | `forbidden_replaced` | `—` | owner |
|
||||
| 38 | `CreateEscrowAccount` | `executable_current` | `required` | `pre.006` | payer, authority |
|
||||
@@ -79,51 +77,18 @@ Les opérations `executable_current` doivent toutes posséder un intent et un bu
|
||||
| 56 | `Resize` | `executable_current` | `required` | `pre.006` | authority |
|
||||
| 57 | `CloseAccounts` | `executable_current` | `required` | `pre.005` | authority |
|
||||
|
||||
Les listes positionnelles complètes des comptes, leurs flags writable/signer, les préflights et les postconditions sont conservés sous `instruction_execution_contract` dans la matrice JSON active.
|
||||
## Propriété des faits
|
||||
|
||||
## Propriété unique des faits
|
||||
La matrice contient 26 faits à propriétaire unique. Les domaines Metaplex et Token-2022 embedded restent séparés.
|
||||
|
||||
| Fait | Propriétaire | Source autoritative |
|
||||
|--------------------------------------|-------------------|---------------------------------------------------|
|
||||
| `metadata.name` | `metadata` | `MetadataV1.name` |
|
||||
| `metadata.symbol` | `metadata` | `MetadataV1.symbol` |
|
||||
| `metadata.uri` | `metadata` | `MetadataV1.uri` |
|
||||
| `metadata.seller_fee_basis_points` | `metadata` | `MetadataV1.seller_fee_basis_points` |
|
||||
| `metadata.creators` | `metadata` | `MetadataV1.data.creators` |
|
||||
| `metadata.collection` | `metadata` | `MetadataV1.collection` |
|
||||
| `metadata.uses` | `metadata` | `MetadataV1.uses` |
|
||||
| `metadata.token_standard` | `metadata` | `MetadataV1.token_standard` |
|
||||
| `metadata.is_mutable` | `metadata` | `MetadataV1.is_mutable` |
|
||||
| `metadata.programmable_config` | `metadata` | `MetadataV1.programmable_config` |
|
||||
| `admin.update_authority` | `admin` | `MetadataV1.update_authority` |
|
||||
| `admin.collection_authority` | `admin` | `CollectionAuthorityRecord` |
|
||||
| `admin.use_authority` | `admin` | `UseAuthorityRecord` |
|
||||
| `admin.delegate` | `admin` | `MetadataDelegate/HolderDelegate/TokenRecord` |
|
||||
| `lifecycle.primary_sale_happened` | `lifecycle` | `MetadataV1.primary_sale_happened` |
|
||||
| `lifecycle.edition` | `lifecycle` | `Edition/MasterEdition` |
|
||||
| `lifecycle.edition_marker` | `lifecycle` | `EditionMarker/EditionMarkerV2` |
|
||||
| `lifecycle.programmable_token_state` | `lifecycle` | `TokenRecord.state` |
|
||||
| `lifecycle.locked_transfer` | `lifecycle` | `TokenRecord.locked_transfer` |
|
||||
| `lifecycle.token_owned_escrow` | `lifecycle` | `TokenOwnedEscrow` |
|
||||
| `lifecycle.historical_reservation` | `lifecycle` | `ReservationListV1/V2` |
|
||||
| `risk.royalty_configuration` | `risk_compliance` | `seller_fee_basis_points fact reference` |
|
||||
| `risk.unverified_creators` | `risk_compliance` | `creators verification fact reference` |
|
||||
| `risk.mutable_metadata` | `risk_compliance` | `is_mutable fact reference` |
|
||||
| `risk.rule_set` | `risk_compliance` | `programmable_config rule_set fact reference` |
|
||||
| `risk.sensitive_delegation` | `risk_compliance` | `delegate records and TokenRecord fact reference` |
|
||||
## Validations exécutées par l’opérateur pour `pre.002`
|
||||
|
||||
Les observations d’instruction décrivent uniquement les mutations. Les snapshots de comptes possédés restent autoritatifs pour l’état final et ne doivent jamais être dupliqués par une projection concurrente.
|
||||
```text
|
||||
cargo fmt --all OK
|
||||
cargo check --workspace OK
|
||||
cargo clippy --all-targets OK
|
||||
python3 scripts/audit_rust_workspace_rules.py clean
|
||||
cargo test -p kb-lib 627 + 4 tests OK
|
||||
```
|
||||
|
||||
## Non-fusion Token-2022
|
||||
|
||||
- domaine Metaplex : `metaplex_token_metadata` ;
|
||||
- domaine Token-2022 : `spl_token_2022_embedded_metadata` ;
|
||||
- aucune identité, URI ou autorité n’est fusionnée implicitement entre les deux domaines ;
|
||||
- une corrélation ultérieure doit conserver les deux provenances et signaler les conflits.
|
||||
|
||||
## Validation disponible dans cet environnement
|
||||
|
||||
- parsing JSON : effectué ;
|
||||
- inventaire `0..57` : effectué ;
|
||||
- unicité des faits : effectuée ;
|
||||
- tests Cargo : non exécutés, car `cargo` est absent de l’environnement de préparation.
|
||||
La classification corrigée et le code de `pre.003` doivent être revalidés avant acceptation.
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<!-- file: docs/validation/V0_4_7_PRE_003_METAPLEX_EXECUTOR_FOUNDATIONS.md -->
|
||||
<!-- version: 3 -->
|
||||
|
||||
# `0.4.7-pre.003` — fondations de l’exécuteur Metaplex
|
||||
|
||||
## Portée
|
||||
|
||||
Cette prerelease remplace la frontière réservée de l’exécuteur Metaplex Token Metadata par une surface typée initiale comprenant :
|
||||
|
||||
- `Create`, discriminant `42`, avec `CreateArgs::V1` ;
|
||||
- `Update`, discriminant `50`, avec `UpdateArgs::AsUpdateAuthorityV2` ;
|
||||
- `PuffMetadata`, discriminant `14`, exécutable dépréciée ;
|
||||
- `SetTokenStandard`, discriminant `35`, exécutable dépréciée.
|
||||
|
||||
Les autres variantes du wrapper `Update` restent planifiées selon leurs domaines d’autorité. Les anciennes instructions create/update remplacées restent decode-only et pointent vers leur wrapper canonique final.
|
||||
|
||||
## Contrats introduits
|
||||
|
||||
- intents typés et codes d’opération stables ;
|
||||
- `#[deprecated]` sur les variantes Rust obsolètes ;
|
||||
- approbation opérateur runtime obligatoire pour toute opération dépréciée ;
|
||||
- builders officiels `mpl-token-metadata 5.1.x` ;
|
||||
- validation des PDA metadata et edition ;
|
||||
- comptes et signers exacts dérivés de l’instruction construite ;
|
||||
- simulation obligatoire et dry-run imposé pour cette première tranche ;
|
||||
- plafonds positifs de frais et de dépense ;
|
||||
- paire complète obligatoire pour les comptes Token Authorization Rules ;
|
||||
- autorisation explicite de chaque signer requis ;
|
||||
- erreurs fail-closed avant production du plan préparé.
|
||||
|
||||
## Classification corrigée
|
||||
|
||||
- 20 instructions courantes et constructibles ;
|
||||
- 15 instructions obsolètes mais constructibles, classées `executable_deprecated` ;
|
||||
- 22 instructions remplacées par leur version canonique finale ;
|
||||
- 1 frontière Bubblegum.
|
||||
|
||||
`pre.003` implémente les deux opérations courantes de sa tranche et les deux opérations obsolètes qui lui sont affectées. Les autres opérations dépréciées sont réparties jusqu’à `pre.006`.
|
||||
|
||||
## Validation attendue
|
||||
|
||||
```bash
|
||||
cargo fmt --all
|
||||
cargo check --workspace
|
||||
cargo clippy --all-targets
|
||||
python3 scripts/audit_rust_workspace_rules.py
|
||||
cargo test -p kb-lib
|
||||
```
|
||||
|
||||
Cargo n’est pas disponible dans l’environnement de préparation. Les validations Rust doivent être exécutées par l’opérateur avant acceptation.
|
||||
|
||||
## Correctif de conformité `fix-002`
|
||||
|
||||
- la dépendance directe `solana-program` introduite par `pre.003` est retirée ;
|
||||
- les contrats internes reposent sur `solana_instruction::Instruction` et `solana_pubkey::Pubkey` ;
|
||||
- les types historiques internes de `mpl-token-metadata 5.1.1` sont laissés à l’inférence puis convertis immédiatement, sans fuite dans l’API de `kb-lib` ;
|
||||
- tous les usages de l’opérateur `?` introduits dans le builder et le dispatcher Metaplex sont remplacés par une propagation explicite des erreurs ;
|
||||
- les TODO de réaudit des exécuteurs Solana Core/SPL et de l’orchestration pipeline sont enregistrés pour une version ultérieure à déterminer.
|
||||
@@ -1,8 +1,29 @@
|
||||
<!-- file: kb-lib/CHANGELOG.md -->
|
||||
<!-- version: 7 -->
|
||||
<!-- version: 10 -->
|
||||
|
||||
# CHANGELOG — kb-lib
|
||||
|
||||
## 0.4.7-pre.003 — fondations de l’exécuteur Metaplex
|
||||
|
||||
### Correctif `0.4.7-pre.003-delta-fix-003`
|
||||
|
||||
- ajout de tests unitaires dans `intent.rs`, `builder.rs` et `executor.rs` pour la surface Metaplex introduite en `pre.003` ;
|
||||
- vérification des codes d’opération, de la partition des opérations dépréciées, des politiques fail-closed, des PDA metadata/edition, du builder `PuffMetadata`, des capacités exactes et de la parité entre dispatch typé et générique.
|
||||
|
||||
### Correctif `0.4.7-pre.003-delta-fix-002`
|
||||
|
||||
- suppression de la dépendance directe inutile à `solana-program` dans le workspace et `kb-lib` ;
|
||||
- utilisation des types modulaires `solana_instruction::Instruction` et `solana_pubkey::Pubkey` pour le contrat interne ;
|
||||
- conversion locale des types de compatibilité retournés par `mpl-token-metadata 5.1.1`, sans exposer ni dépendre directement de `solana-program` ;
|
||||
- suppression de tous les opérateurs `?` introduits par la surface Metaplex `pre.003` ;
|
||||
- ajout des TODO de réaudit des exécuteurs Solana Core/SPL et de leur orchestration pipeline selon la nouvelle règle des opérations remplacées et obsolètes.
|
||||
|
||||
- remplacement de l’exécuteur réservé par une surface typée initiale ;
|
||||
- builders courants `Create` et `UpdateAsUpdateAuthorityV2` fondés sur `mpl-token-metadata` ;
|
||||
- validation des PDA metadata/edition, des signers autorisés, des comptes de rule set et des plafonds de coût ;
|
||||
- simulation et dry-run obligatoires pour cette première tranche ;
|
||||
- correction de la classification des instructions remplacées et obsolètes.
|
||||
|
||||
## 0.4.7-pre.002 — contrat Metaplex fermé
|
||||
|
||||
- classification exhaustive des 58 discriminants Metaplex Token Metadata ;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# file: kb-lib/Cargo.toml
|
||||
# version: 14
|
||||
# version: 15
|
||||
|
||||
[package]
|
||||
name = "kb-lib"
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<!-- file: kb-lib/README.md -->
|
||||
<!-- version: 15 -->
|
||||
<!-- version: 16 -->
|
||||
|
||||
# kb-lib
|
||||
|
||||
`kb-lib` est la bibliothèque fonctionnelle consolidée de `khadhroony-bot3`. Elle expose les modèles partagés, les contrats et implémentations de décodage, de matérialisation, d’exécution et de sécurité nécessaires au pipeline Solana.
|
||||
|
||||
- Metaplex Token Metadata expose les wrappers canoniques courants et conserve les opérations obsolètes encore constructibles sous une API Rust `#[deprecated]` avec approbation opérateur explicite ; les versions remplacées ne reçoivent qu’un renvoi vers leur remplacement canonique final.
|
||||
|
||||
## Périmètre
|
||||
|
||||
La crate regroupe quatre familles internes, exposées par la façade `kb_lib` :
|
||||
@@ -56,7 +58,7 @@ Les matrices actives sont maintenues sous [`../test-fixtures/contract-matrices/`
|
||||
|
||||
## Statut et limites
|
||||
|
||||
La migration des surfaces historiques jusqu’à un périmètre proche de bot2 `0.4.6` est largement réalisée. Le décodeur Metaplex Token Metadata couvre les 58 discriminants et les matérialisations possèdent désormais un contrat fermé de propriété des faits. L’exécuteur et la clôture fonctionnelle de cette surface restent à réaliser entre `0.4.7-pre.003` et `pre.006`. Le registre ElGamal n’est pas déclaré validé sur Devnet ou Mainnet.
|
||||
La migration des surfaces historiques jusqu’à un périmètre proche de bot2 `0.4.6` est largement réalisée. Le décodeur Metaplex Token Metadata couvre les 58 discriminants et les matérialisations possèdent désormais un contrat fermé de propriété des faits. L’exécuteur Metaplex possède désormais ses fondations typées et les wrappers courants `Create`/`Update`; les autres opérations courantes restent à achever jusqu’à `pre.006`. Le registre ElGamal n’est pas déclaré validé sur Devnet ou Mainnet.
|
||||
|
||||
## Documents
|
||||
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
<!-- file: kb-lib/TODO.md -->
|
||||
<!-- version: 4 -->
|
||||
<!-- version: 7 -->
|
||||
|
||||
# TODO — kb-lib
|
||||
|
||||
## `0.4.7` — Metaplex Token Metadata
|
||||
|
||||
- [x] établir le contrat fermé de couverture et de propriété des faits ;
|
||||
- [x] introduire les intents et builders courants `Create`/`Update` ;
|
||||
- [ ] achever Verify/Unverify, délégations, pNFT, éditions, use, escrow et opérations courantes restantes avant `pre.006` ;
|
||||
|
||||
## Entre `0.4.6` et `0.4.7` — Metaplex Token Metadata
|
||||
|
||||
- [x] Matérialisation - vérifier la couverture exacte migrée depuis bot2.
|
||||
@@ -18,6 +24,14 @@
|
||||
- [ ] Fixture - préparer une preuve `PubkeyValidity` et un compte `Proof Context State` valide si un réseau exploitable est confirmé.
|
||||
- [ ] Validation - exécuter une validation réseau réelle avant de déclarer la surface validée.
|
||||
|
||||
## Réaudit ultérieur des exécuteurs déjà livrés
|
||||
|
||||
- [ ] Version à déterminer - réauditer les exécuteurs Solana Core et SPL déjà implémentés selon la règle distinguant les opérations courantes, remplacées et obsolètes constructibles.
|
||||
- [ ] Solana Core - vérifier chaque génération historique ou obsolète officiellement constructible et ajouter les opérations dépréciées manquantes sans réexposer les versions remplacées intermédiaires.
|
||||
- [ ] SPL Memo - réexaminer notamment Memo v1 et Memo v3 : conserver uniquement le remplacement canonique pour les versions remplacées et implémenter comme dépréciée toute génération obsolète encore légitimement constructible.
|
||||
- [ ] SPL Token, ATA, Token-2022 et registre ElGamal - vérifier la même règle opération par opération, documenter les remplacements canoniques et compléter les builders dépréciés manquants.
|
||||
- [ ] Matrices - aligner les statuts, annotations `#[deprecated]`, approbations opérateur et tests de parité de toutes les surfaces concernées.
|
||||
|
||||
## Versions ultérieures
|
||||
|
||||
- [ ] Surfaces - remplacer les décodeurs et exécuteurs réservés selon le ROADMAP.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: kb-lib/USAGE.md -->
|
||||
<!-- version: 3 -->
|
||||
<!-- version: 5 -->
|
||||
|
||||
# Utilisation de kb-lib
|
||||
|
||||
@@ -117,6 +117,7 @@ println!("edition parent={:?}", edition.parent);
|
||||
|
||||
Les signatures exactes doivent être vérifiées lors de l’utilisation : certaines familles exigent un contexte ou une identité canonique supplémentaire.
|
||||
|
||||
|
||||
## Exécution typée
|
||||
|
||||
`ExApiTypedInstructionExecutor` produit un `ExApiPreparedExecutionPlan` à partir d’une intention et d’une politique explicites. Les exécuteurs concrets incluent notamment :
|
||||
|
||||
@@ -3067,9 +3067,7 @@ mod tests {
|
||||
assert_eq!(entries.len(), 58);
|
||||
let mut discriminators = std::collections::BTreeSet::new();
|
||||
for entry in entries {
|
||||
let discriminator = match entry
|
||||
.get("discriminator")
|
||||
.and_then(serde_json::Value::as_u64)
|
||||
let discriminator = match entry.get("discriminator").and_then(serde_json::Value::as_u64)
|
||||
{
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => panic!("instruction discriminator missing"),
|
||||
@@ -3077,14 +3075,18 @@ mod tests {
|
||||
assert!(discriminators.insert(discriminator));
|
||||
assert!(entry.get("accounts").is_some_and(serde_json::Value::is_array));
|
||||
assert!(entry.get("signers").is_some_and(serde_json::Value::is_array));
|
||||
assert!(entry
|
||||
assert!(
|
||||
entry
|
||||
.get("coverage_status")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.is_some_and(|value| return value != "pending" && !value.is_empty()));
|
||||
assert!(entry
|
||||
.is_some_and(|value| return value != "pending" && !value.is_empty())
|
||||
);
|
||||
assert!(
|
||||
entry
|
||||
.get("execute")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.is_some_and(|value| return value != "pending" && !value.is_empty()));
|
||||
.is_some_and(|value| return value != "pending" && !value.is_empty())
|
||||
);
|
||||
}
|
||||
assert_eq!(discriminators, (0u64..58u64).collect());
|
||||
}
|
||||
@@ -3095,10 +3097,8 @@ mod tests {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => panic!("matrix parse failed: {error}"),
|
||||
};
|
||||
let facts = match matrix
|
||||
.get("fact_ownership_contract")
|
||||
.and_then(serde_json::Value::as_array)
|
||||
{
|
||||
let facts =
|
||||
match matrix.get("fact_ownership_contract").and_then(serde_json::Value::as_array) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => panic!("fact_ownership_contract missing"),
|
||||
};
|
||||
@@ -3109,10 +3109,12 @@ mod tests {
|
||||
std::option::Option::None => panic!("fact name missing"),
|
||||
};
|
||||
assert!(names.insert(fact));
|
||||
assert!(entry
|
||||
assert!(
|
||||
entry
|
||||
.get("owner")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.is_some_and(|value| return !value.is_empty()));
|
||||
.is_some_and(|value| return !value.is_empty())
|
||||
);
|
||||
}
|
||||
let forbidden = match matrix
|
||||
.pointer("/fact_ownership_proof/cross_domain_merge_forbidden")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-lib/src/executor.rs
|
||||
// version: 9
|
||||
// version: 11
|
||||
|
||||
//! Consolidated executor modules.
|
||||
|
||||
@@ -208,12 +208,28 @@ pub use self::lending::ExLendingKaminoExecutor;
|
||||
pub use self::lending::ExLendingMarginfiV2Executor;
|
||||
/// Exposes the reserved `lock/raydium_lp` executor.
|
||||
pub use self::lock::ExLockRaydiumLpExecutor;
|
||||
/// Exposes the reserved `metadata/metaplex_token_metadata` executor.
|
||||
/// Stable operation code for the current Metaplex Create wrapper.
|
||||
pub use self::metadata::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION;
|
||||
/// Deprecated Metaplex operation codes requiring explicit approval.
|
||||
pub use self::metadata::EX_METAPLEX_TOKEN_METADATA_DEPRECATED_OPERATION_CODES;
|
||||
/// Stable deprecated Metaplex PuffMetadata operation code.
|
||||
pub use self::metadata::EX_METAPLEX_TOKEN_METADATA_PUFF_METADATA_OPERATION;
|
||||
/// Stable deprecated Metaplex SetTokenStandard operation code.
|
||||
pub use self::metadata::EX_METAPLEX_TOKEN_METADATA_SET_TOKEN_STANDARD_OPERATION;
|
||||
/// Current and deprecated Metaplex Token Metadata operations.
|
||||
pub use self::metadata::EX_METAPLEX_TOKEN_METADATA_SUPPORTED_OPERATION_CODES;
|
||||
/// Stable operation code for the current Metaplex Update wrapper.
|
||||
pub use self::metadata::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION;
|
||||
/// Exposes the Metaplex Token Metadata executor.
|
||||
pub use self::metadata::ExMetadataMetaplexTokenMetadataExecutor;
|
||||
/// Reserved `kb-lib.executor.metadata.solana_program_metadata` executor.
|
||||
pub use self::metadata::ExMetadataSolanaProgramMetadataExecutor;
|
||||
/// Exposes the reserved `metadata/spl_name_service` executor.
|
||||
pub use self::metadata::ExMetadataSplNameServiceExecutor;
|
||||
/// Exposes the complete Metaplex Token Metadata execution intent.
|
||||
pub use self::metadata::ExMetaplexTokenMetadataExecutionIntent;
|
||||
/// Exposes one current Metaplex Token Metadata operation.
|
||||
pub use self::metadata::ExMetaplexTokenMetadataOperation;
|
||||
/// Exposes the reserved `nft/metaplex_bubblegum` executor.
|
||||
pub use self::nft::ExNftMetaplexBubblegumExecutor;
|
||||
/// Reserved `kb-lib.executor.nft.tensor_cnft` executor.
|
||||
@@ -915,6 +931,8 @@ pub use self::wallet::ExWalletJupiterApeproSmartWalletExecutor;
|
||||
/// Exposes the reserved `weighted/swap_stabble` executor.
|
||||
pub use self::weighted::ExWeightedSwapStabbleExecutor;
|
||||
|
||||
/// Internal Metaplex Token Metadata prepared-plan builder.
|
||||
pub(crate) use self::metadata::executor_metaplex_token_metadata_build_prepared_plan;
|
||||
/// Stable operation code for the nonce advance injected by durable transaction assembly.
|
||||
pub(crate) use self::solana::EX_SOLANA_TRANSACTION_DURABLE_NONCE_ADVANCE_OPERATION;
|
||||
/// Maximum over-the-wire size of a Solana transaction packet.
|
||||
@@ -998,7 +1016,7 @@ pub(crate) use self::spl::executor_spl_token_build_prepared_plan;
|
||||
mod reserved_executor_tests {
|
||||
#[test]
|
||||
fn every_reserved_executor_is_registered_and_builds_only_an_empty_plan() {
|
||||
let executors: [&dyn crate::ExApiInstructionExecutor; 105] = [
|
||||
let executors: [&dyn crate::ExApiInstructionExecutor; 104] = [
|
||||
&crate::ExAdapterSaberDecimalWrapperExecutor,
|
||||
&crate::ExAdapterSplTokenWrapExecutor,
|
||||
&crate::ExGovernanceSquadsMultisigExecutor,
|
||||
@@ -1067,7 +1085,6 @@ mod reserved_executor_tests {
|
||||
&crate::ExLendingKaminoExecutor,
|
||||
&crate::ExLendingMarginfiV2Executor,
|
||||
&crate::ExLockRaydiumLpExecutor,
|
||||
&crate::ExMetadataMetaplexTokenMetadataExecutor,
|
||||
&crate::ExMetadataSplNameServiceExecutor,
|
||||
&crate::ExNftMetaplexBubblegumExecutor,
|
||||
&crate::ExOrderbookJupiterLimitOrderExecutor,
|
||||
@@ -1105,7 +1122,7 @@ mod reserved_executor_tests {
|
||||
&crate::ExWalletJupiterApeproSmartWalletExecutor,
|
||||
&crate::ExWeightedSwapStabbleExecutor,
|
||||
];
|
||||
assert_eq!(executors.len(), 105);
|
||||
assert_eq!(executors.len(), 104);
|
||||
for executor in executors {
|
||||
assert!(!executor.program_ids().is_empty());
|
||||
for program_id in executor.program_ids() {
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
// file: kb-lib/src/executor/metadata.rs
|
||||
// version: 2
|
||||
// version: 5
|
||||
|
||||
//! `metadata` executor family.
|
||||
|
||||
mod metaplex_token_metadata;
|
||||
mod solana_program_metadata;
|
||||
mod spl_name_service;
|
||||
/// Exposes the reserved `metadata/metaplex_token_metadata` executor.
|
||||
/// Exposes the Metaplex Token Metadata execution surface.
|
||||
pub use self::metaplex_token_metadata::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION;
|
||||
pub use self::metaplex_token_metadata::EX_METAPLEX_TOKEN_METADATA_DEPRECATED_OPERATION_CODES;
|
||||
pub use self::metaplex_token_metadata::EX_METAPLEX_TOKEN_METADATA_PUFF_METADATA_OPERATION;
|
||||
pub use self::metaplex_token_metadata::EX_METAPLEX_TOKEN_METADATA_SET_TOKEN_STANDARD_OPERATION;
|
||||
pub use self::metaplex_token_metadata::EX_METAPLEX_TOKEN_METADATA_SUPPORTED_OPERATION_CODES;
|
||||
pub use self::metaplex_token_metadata::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION;
|
||||
pub use self::metaplex_token_metadata::ExMetadataMetaplexTokenMetadataExecutor;
|
||||
pub use self::metaplex_token_metadata::ExMetaplexTokenMetadataExecutionIntent;
|
||||
pub use self::metaplex_token_metadata::ExMetaplexTokenMetadataOperation;
|
||||
/// Exposes the reserved `metadata/solana_program_metadata` executor.
|
||||
pub use self::solana_program_metadata::ExMetadataSolanaProgramMetadataExecutor;
|
||||
/// Exposes the reserved `metadata/spl_name_service` executor.
|
||||
pub use self::spl_name_service::ExMetadataSplNameServiceExecutor;
|
||||
|
||||
pub(crate) use self::metaplex_token_metadata::executor_metaplex_token_metadata_build_prepared_plan;
|
||||
|
||||
@@ -1,49 +1,29 @@
|
||||
// file: kb-lib/src/executor/metadata/metaplex_token_metadata.rs
|
||||
// version: 4
|
||||
// version: 7
|
||||
|
||||
//! Reserved executor for `metadata_metaplex_token_metadata`.
|
||||
//! Metaplex Token Metadata typed execution surface.
|
||||
|
||||
/// Reserved executor for the `metadata_metaplex_token_metadata` program surface.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ExMetadataMetaplexTokenMetadataExecutor;
|
||||
mod builder;
|
||||
mod executor;
|
||||
mod intent;
|
||||
|
||||
impl crate::ExApiInstructionExecutor for crate::ExMetadataMetaplexTokenMetadataExecutor {
|
||||
fn executor_name(&self) -> &'static str {
|
||||
return "kb-lib.executor.metadata.metaplex_token_metadata";
|
||||
}
|
||||
/// Exposes the Metaplex Token Metadata executor.
|
||||
pub use self::executor::ExMetadataMetaplexTokenMetadataExecutor;
|
||||
/// Stable operation code for the current Create wrapper.
|
||||
pub use self::intent::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION;
|
||||
/// Deprecated operation codes requiring explicit operator approval.
|
||||
pub use self::intent::EX_METAPLEX_TOKEN_METADATA_DEPRECATED_OPERATION_CODES;
|
||||
/// Stable deprecated operation code for PuffMetadata.
|
||||
pub use self::intent::EX_METAPLEX_TOKEN_METADATA_PUFF_METADATA_OPERATION;
|
||||
/// Stable deprecated operation code for SetTokenStandard.
|
||||
pub use self::intent::EX_METAPLEX_TOKEN_METADATA_SET_TOKEN_STANDARD_OPERATION;
|
||||
/// Current and deprecated operations implemented by this prerelease.
|
||||
pub use self::intent::EX_METAPLEX_TOKEN_METADATA_SUPPORTED_OPERATION_CODES;
|
||||
/// Stable operation code for the current Update wrapper.
|
||||
pub use self::intent::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION;
|
||||
/// Exposes the complete Metaplex Token Metadata execution intent.
|
||||
pub use self::intent::ExMetaplexTokenMetadataExecutionIntent;
|
||||
/// Exposes the current and deprecated Metaplex Token Metadata operation intent.
|
||||
pub use self::intent::ExMetaplexTokenMetadataOperation;
|
||||
|
||||
fn executor_version(&self) -> &'static str {
|
||||
return env!("CARGO_PKG_VERSION");
|
||||
}
|
||||
|
||||
fn program_ids(&self) -> &'static [&'static str] {
|
||||
return &[kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID];
|
||||
}
|
||||
|
||||
fn supports_request(
|
||||
&self,
|
||||
request: &crate::ExApiExecutionRequest,
|
||||
) -> crate::ExApiExecutionSupport {
|
||||
if crate::ExApiInstructionExecutor::handles_program_id(self, &request.program_id) {
|
||||
return crate::ExApiExecutionSupport::Maybe;
|
||||
}
|
||||
return crate::ExApiExecutionSupport::No;
|
||||
}
|
||||
|
||||
fn build_plan(
|
||||
&self,
|
||||
_request: &crate::ExApiExecutionRequest,
|
||||
) -> kb_core::Result<crate::ExApiExecutionPlan> {
|
||||
let payload_json = match crate::executor_api_serialize_payload_json(
|
||||
&serde_json::json!({"status":"reserved_executor","surface":"metadata.metaplex_token_metadata"}),
|
||||
) {
|
||||
std::result::Result::Ok(serialized) => serialized,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return std::result::Result::Ok(crate::ExApiExecutionPlan {
|
||||
executor_name: "kb-lib.executor.metadata.metaplex_token_metadata".to_string(),
|
||||
instruction_count: 0,
|
||||
payload_json,
|
||||
});
|
||||
}
|
||||
}
|
||||
pub(crate) use self::builder::executor_metaplex_token_metadata_build_prepared_plan;
|
||||
|
||||
637
kb-lib/src/executor/metadata/metaplex_token_metadata/builder.rs
Normal file
637
kb-lib/src/executor/metadata/metaplex_token_metadata/builder.rs
Normal file
@@ -0,0 +1,637 @@
|
||||
// file: kb-lib/src/executor/metadata/metaplex_token_metadata/builder.rs
|
||||
// version: 4
|
||||
|
||||
//! Official Metaplex builders and conservative plan validation.
|
||||
|
||||
use std::str::FromStr; // rust-rules: trait-import
|
||||
|
||||
macro_rules! parse_official_pubkey {
|
||||
($value:expr, $field:expr) => {{
|
||||
match std::str::FromStr::from_str($value) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"execution_metaplex_pubkey_invalid",
|
||||
format!("invalid {} public key: {}", $field, error),
|
||||
));
|
||||
},
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
macro_rules! convert_official_instruction {
|
||||
($instruction:expr) => {{
|
||||
let instruction = $instruction;
|
||||
let program_id = match parse_str_pubkey(&instruction.program_id.to_string(), "program_id") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let mut accounts = std::vec::Vec::with_capacity(instruction.accounts.len());
|
||||
for account in instruction.accounts {
|
||||
let pubkey = match parse_str_pubkey(&account.pubkey.to_string(), "instruction_account")
|
||||
{
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
accounts.push(solana_instruction::AccountMeta {
|
||||
pubkey,
|
||||
is_signer: account.is_signer,
|
||||
is_writable: account.is_writable,
|
||||
});
|
||||
}
|
||||
solana_instruction::Instruction {
|
||||
program_id,
|
||||
accounts,
|
||||
data: instruction.data,
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
pub(crate) fn executor_metaplex_token_metadata_build_prepared_plan(
|
||||
intent: &crate::ExMetaplexTokenMetadataExecutionIntent,
|
||||
) -> kb_core::Result<crate::ExApiPreparedExecutionPlan> {
|
||||
if let std::result::Result::Err(error) = validate_intent(intent) {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let instruction_result = match &intent.operation {
|
||||
crate::ExMetaplexTokenMetadataOperation::Create {
|
||||
metadata,
|
||||
master_edition,
|
||||
mint,
|
||||
mint_as_signer,
|
||||
authority,
|
||||
update_authority,
|
||||
update_authority_as_signer,
|
||||
spl_token_program,
|
||||
create_args,
|
||||
} => build_create(
|
||||
intent,
|
||||
metadata,
|
||||
master_edition.as_ref(),
|
||||
mint,
|
||||
*mint_as_signer,
|
||||
authority,
|
||||
update_authority,
|
||||
*update_authority_as_signer,
|
||||
spl_token_program.as_ref(),
|
||||
create_args,
|
||||
),
|
||||
crate::ExMetaplexTokenMetadataOperation::UpdateAsUpdateAuthorityV2 {
|
||||
authority,
|
||||
mint,
|
||||
metadata,
|
||||
edition,
|
||||
token,
|
||||
authorization_rules_program,
|
||||
authorization_rules,
|
||||
update_args,
|
||||
} => build_update(
|
||||
intent,
|
||||
authority,
|
||||
mint,
|
||||
metadata,
|
||||
edition.as_ref(),
|
||||
token.as_ref(),
|
||||
authorization_rules_program.as_ref(),
|
||||
authorization_rules.as_ref(),
|
||||
update_args,
|
||||
),
|
||||
crate::ExMetaplexTokenMetadataOperation::PuffMetadata { metadata } => {
|
||||
build_puff_metadata(metadata)
|
||||
},
|
||||
crate::ExMetaplexTokenMetadataOperation::SetTokenStandard {
|
||||
metadata,
|
||||
update_authority,
|
||||
mint,
|
||||
edition,
|
||||
} => build_set_token_standard(metadata, update_authority, mint, edition.as_ref()),
|
||||
};
|
||||
let instruction = match instruction_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let required_signers = required_signers(&intent.fee_payer, &instruction.accounts);
|
||||
if let std::result::Result::Err(error) = validate_authorized_signers(intent, &required_signers)
|
||||
{
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
return std::result::Result::Ok(crate::ExApiPreparedExecutionPlan {
|
||||
executor_name: "kb-lib.executor.metadata.metaplex_token_metadata".to_string(),
|
||||
executor_version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
intent_id: intent.intent_id.clone(),
|
||||
operation_code: intent.operation.operation_code().to_string(),
|
||||
fee_payer: intent.fee_payer.clone(),
|
||||
instructions: vec![planned_instruction(intent.operation.operation_code(), &instruction)],
|
||||
required_signers,
|
||||
policy: intent.policy.clone(),
|
||||
requested_spend_lamports: intent.max_rent_lamports,
|
||||
requested_compute_unit_price_micro_lamports: std::option::Option::None,
|
||||
});
|
||||
}
|
||||
|
||||
fn validate_intent(intent: &crate::ExMetaplexTokenMetadataExecutionIntent) -> kb_core::Result<()> {
|
||||
if intent.intent_id.trim().is_empty() {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"execution_metaplex_intent_id_empty",
|
||||
"Metaplex Token Metadata execution intent id must not be empty",
|
||||
));
|
||||
}
|
||||
if intent.operation.is_deprecated() && !intent.allow_deprecated_operation {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"execution_metaplex_deprecated_operation_not_approved",
|
||||
"deprecated Metaplex Token Metadata operation requires explicit operator approval",
|
||||
));
|
||||
}
|
||||
if intent.policy.simulation != crate::ExApiExecutionSimulationPolicy::Required {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"execution_metaplex_simulation_required",
|
||||
"Metaplex Token Metadata execution requires simulation before signing or sending",
|
||||
));
|
||||
}
|
||||
if !intent.policy.dry_run {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"execution_metaplex_dry_run_required",
|
||||
"Metaplex Token Metadata pre.003 intents are simulation-only",
|
||||
));
|
||||
}
|
||||
match intent.policy.cost_limit.max_fee_lamports {
|
||||
std::option::Option::Some(value) if value > 0 => {},
|
||||
std::option::Option::Some(_) | std::option::Option::None => {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"execution_metaplex_fee_limit_missing",
|
||||
"Metaplex Token Metadata execution requires a positive transaction fee ceiling",
|
||||
));
|
||||
},
|
||||
}
|
||||
match intent.policy.cost_limit.max_spend_lamports {
|
||||
std::option::Option::Some(value) if value >= intent.max_rent_lamports => {},
|
||||
std::option::Option::Some(_) | std::option::Option::None => {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"execution_metaplex_spend_limit_insufficient",
|
||||
"Metaplex Token Metadata execution spend ceiling must cover max_rent_lamports",
|
||||
));
|
||||
},
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn build_create(
|
||||
intent: &crate::ExMetaplexTokenMetadataExecutionIntent,
|
||||
metadata: &crate::MdPubkey,
|
||||
master_edition: std::option::Option<&crate::MdPubkey>,
|
||||
mint: &crate::MdPubkey,
|
||||
mint_as_signer: bool,
|
||||
authority: &crate::MdPubkey,
|
||||
update_authority: &crate::MdPubkey,
|
||||
update_authority_as_signer: bool,
|
||||
spl_token_program: std::option::Option<&crate::MdProgramId>,
|
||||
create_args: &mpl_token_metadata::types::CreateArgs,
|
||||
) -> kb_core::Result<solana_instruction::Instruction> {
|
||||
let metadata = match parse_pubkey(metadata, "metadata") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let mint = match parse_pubkey(mint, "mint") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if let std::result::Result::Err(error) = validate_metadata_pda(&metadata, &mint) {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let master_edition = match parse_optional_pubkey(master_edition, "master_edition") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if let std::option::Option::Some(value) = master_edition {
|
||||
if let std::result::Result::Err(error) = validate_edition_pda(&value, &mint) {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
}
|
||||
if !matches!(create_args, mpl_token_metadata::types::CreateArgs::V1 { .. }) {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"execution_metaplex_create_args_unsupported",
|
||||
"only CreateArgs::V1 is current and supported",
|
||||
));
|
||||
}
|
||||
let official = mpl_token_metadata::instructions::Create {
|
||||
metadata: parse_official_pubkey!(&metadata.to_string(), "metadata"),
|
||||
master_edition: match master_edition.as_ref() {
|
||||
std::option::Option::Some(value) => std::option::Option::Some(parse_official_pubkey!(
|
||||
&value.to_string(),
|
||||
"master_edition"
|
||||
)),
|
||||
std::option::Option::None => std::option::Option::None,
|
||||
},
|
||||
mint: (parse_official_pubkey!(&mint.to_string(), "mint"), mint_as_signer),
|
||||
authority: parse_official_pubkey!(&authority.0, "authority"),
|
||||
payer: parse_official_pubkey!(&intent.fee_payer.0, "fee_payer"),
|
||||
update_authority: (
|
||||
parse_official_pubkey!(&update_authority.0, "update_authority"),
|
||||
update_authority_as_signer,
|
||||
),
|
||||
system_program: parse_official_pubkey!(kb_program_ids::SYSTEM_PROGRAM_ID, "system_program"),
|
||||
sysvar_instructions: parse_official_pubkey!(
|
||||
kb_program_ids::SYSVAR_INSTRUCTIONS_PROGRAM_ID,
|
||||
"sysvar_instructions"
|
||||
),
|
||||
spl_token_program: match spl_token_program {
|
||||
std::option::Option::Some(value) => {
|
||||
std::option::Option::Some(parse_official_pubkey!(&value.0, "spl_token_program"))
|
||||
},
|
||||
std::option::Option::None => std::option::Option::None,
|
||||
},
|
||||
}
|
||||
.instruction(mpl_token_metadata::instructions::CreateInstructionArgs {
|
||||
create_args: create_args.clone(),
|
||||
});
|
||||
return std::result::Result::Ok(convert_official_instruction!(official));
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn build_update(
|
||||
intent: &crate::ExMetaplexTokenMetadataExecutionIntent,
|
||||
authority: &crate::MdPubkey,
|
||||
mint: &crate::MdPubkey,
|
||||
metadata: &crate::MdPubkey,
|
||||
edition: std::option::Option<&crate::MdPubkey>,
|
||||
token: std::option::Option<&crate::MdPubkey>,
|
||||
authorization_rules_program: std::option::Option<&crate::MdProgramId>,
|
||||
authorization_rules: std::option::Option<&crate::MdPubkey>,
|
||||
update_args: &mpl_token_metadata::types::UpdateArgs,
|
||||
) -> kb_core::Result<solana_instruction::Instruction> {
|
||||
if !matches!(update_args, mpl_token_metadata::types::UpdateArgs::AsUpdateAuthorityV2 { .. }) {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"execution_metaplex_update_variant_not_yet_enabled",
|
||||
"pre.003 enables only UpdateArgs::AsUpdateAuthorityV2; delegate variants are scheduled later",
|
||||
));
|
||||
}
|
||||
let mint = match parse_pubkey(mint, "mint") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let metadata = match parse_pubkey(metadata, "metadata") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if let std::result::Result::Err(error) = validate_metadata_pda(&metadata, &mint) {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let edition = match parse_optional_pubkey(edition, "edition") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if let std::option::Option::Some(value) = edition {
|
||||
if let std::result::Result::Err(error) = validate_edition_pda(&value, &mint) {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
}
|
||||
if authorization_rules_program.is_some() != authorization_rules.is_some() {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"execution_metaplex_rule_set_accounts_incomplete",
|
||||
"authorization rules program and account must be supplied together",
|
||||
));
|
||||
}
|
||||
let official = mpl_token_metadata::instructions::Update {
|
||||
authority: parse_official_pubkey!(&authority.0, "authority"),
|
||||
delegate_record: std::option::Option::None,
|
||||
token: match token {
|
||||
std::option::Option::Some(value) => {
|
||||
std::option::Option::Some(parse_official_pubkey!(&value.0, "token"))
|
||||
},
|
||||
std::option::Option::None => std::option::Option::None,
|
||||
},
|
||||
mint: parse_official_pubkey!(&mint.to_string(), "mint"),
|
||||
metadata: parse_official_pubkey!(&metadata.to_string(), "metadata"),
|
||||
edition: match edition.as_ref() {
|
||||
std::option::Option::Some(value) => {
|
||||
std::option::Option::Some(parse_official_pubkey!(&value.to_string(), "edition"))
|
||||
},
|
||||
std::option::Option::None => std::option::Option::None,
|
||||
},
|
||||
payer: parse_official_pubkey!(&intent.fee_payer.0, "fee_payer"),
|
||||
system_program: parse_official_pubkey!(kb_program_ids::SYSTEM_PROGRAM_ID, "system_program"),
|
||||
sysvar_instructions: parse_official_pubkey!(
|
||||
kb_program_ids::SYSVAR_INSTRUCTIONS_PROGRAM_ID,
|
||||
"sysvar_instructions"
|
||||
),
|
||||
authorization_rules_program: match authorization_rules_program {
|
||||
std::option::Option::Some(value) => std::option::Option::Some(parse_official_pubkey!(
|
||||
&value.0,
|
||||
"authorization_rules_program"
|
||||
)),
|
||||
std::option::Option::None => std::option::Option::None,
|
||||
},
|
||||
authorization_rules: match authorization_rules {
|
||||
std::option::Option::Some(value) => {
|
||||
std::option::Option::Some(parse_official_pubkey!(&value.0, "authorization_rules"))
|
||||
},
|
||||
std::option::Option::None => std::option::Option::None,
|
||||
},
|
||||
}
|
||||
.instruction(mpl_token_metadata::instructions::UpdateInstructionArgs {
|
||||
update_args: update_args.clone(),
|
||||
});
|
||||
return std::result::Result::Ok(convert_official_instruction!(official));
|
||||
}
|
||||
|
||||
fn build_puff_metadata(
|
||||
metadata: &crate::MdPubkey,
|
||||
) -> kb_core::Result<solana_instruction::Instruction> {
|
||||
let metadata = match parse_pubkey(metadata, "metadata") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let official = mpl_token_metadata::instructions::PuffMetadata {
|
||||
metadata: parse_official_pubkey!(&metadata.to_string(), "metadata"),
|
||||
}
|
||||
.instruction();
|
||||
return std::result::Result::Ok(convert_official_instruction!(official));
|
||||
}
|
||||
|
||||
fn build_set_token_standard(
|
||||
metadata: &crate::MdPubkey,
|
||||
update_authority: &crate::MdPubkey,
|
||||
mint: &crate::MdPubkey,
|
||||
edition: std::option::Option<&crate::MdPubkey>,
|
||||
) -> kb_core::Result<solana_instruction::Instruction> {
|
||||
let metadata = match parse_pubkey(metadata, "metadata") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let mint = match parse_pubkey(mint, "mint") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if let std::result::Result::Err(error) = validate_metadata_pda(&metadata, &mint) {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let edition = match parse_optional_pubkey(edition, "edition") {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
if let std::option::Option::Some(value) = edition {
|
||||
if let std::result::Result::Err(error) = validate_edition_pda(&value, &mint) {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
}
|
||||
let official = mpl_token_metadata::instructions::SetTokenStandard {
|
||||
metadata: parse_official_pubkey!(&metadata.to_string(), "metadata"),
|
||||
update_authority: parse_official_pubkey!(&update_authority.0, "update_authority"),
|
||||
mint: parse_official_pubkey!(&mint.to_string(), "mint"),
|
||||
edition: match edition.as_ref() {
|
||||
std::option::Option::Some(value) => {
|
||||
std::option::Option::Some(parse_official_pubkey!(&value.to_string(), "edition"))
|
||||
},
|
||||
std::option::Option::None => std::option::Option::None,
|
||||
},
|
||||
}
|
||||
.instruction();
|
||||
return std::result::Result::Ok(convert_official_instruction!(official));
|
||||
}
|
||||
|
||||
fn validate_metadata_pda(
|
||||
metadata: &solana_pubkey::Pubkey,
|
||||
mint: &solana_pubkey::Pubkey,
|
||||
) -> kb_core::Result<()> {
|
||||
let program_id = match parse_str_pubkey(
|
||||
kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID,
|
||||
"program_id",
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let (expected, _) = solana_pubkey::Pubkey::find_program_address(
|
||||
&[b"metadata", program_id.as_ref(), mint.as_ref()],
|
||||
&program_id,
|
||||
);
|
||||
if *metadata != expected {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"execution_metaplex_metadata_pda_mismatch",
|
||||
format!("metadata PDA {} does not match expected {}", metadata, expected),
|
||||
));
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn validate_edition_pda(
|
||||
edition: &solana_pubkey::Pubkey,
|
||||
mint: &solana_pubkey::Pubkey,
|
||||
) -> kb_core::Result<()> {
|
||||
let program_id = match parse_str_pubkey(
|
||||
kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID,
|
||||
"program_id",
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let (expected, _) = solana_pubkey::Pubkey::find_program_address(
|
||||
&[b"metadata", program_id.as_ref(), mint.as_ref(), b"edition"],
|
||||
&program_id,
|
||||
);
|
||||
if *edition != expected {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"execution_metaplex_edition_pda_mismatch",
|
||||
format!("edition PDA {} does not match expected {}", edition, expected),
|
||||
));
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn validate_authorized_signers(
|
||||
intent: &crate::ExMetaplexTokenMetadataExecutionIntent,
|
||||
required: &[crate::ExApiRequiredSigner],
|
||||
) -> kb_core::Result<()> {
|
||||
for signer in required {
|
||||
if !intent.policy.authorized_signers.contains(&signer.pubkey) {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"execution_metaplex_signer_not_authorized",
|
||||
format!("required signer {} is not authorized", signer.pubkey.0),
|
||||
));
|
||||
}
|
||||
}
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn parse_pubkey(value: &crate::MdPubkey, field: &str) -> kb_core::Result<solana_pubkey::Pubkey> {
|
||||
return parse_str_pubkey(&value.0, field);
|
||||
}
|
||||
|
||||
fn parse_str_pubkey(value: &str, field: &str) -> kb_core::Result<solana_pubkey::Pubkey> {
|
||||
return solana_pubkey::Pubkey::from_str(value).map_err(|error| {
|
||||
return kb_core::Error::new(
|
||||
"execution_metaplex_pubkey_invalid",
|
||||
format!("invalid {field} public key: {error}"),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn parse_optional_pubkey(
|
||||
value: std::option::Option<&crate::MdPubkey>,
|
||||
field: &str,
|
||||
) -> kb_core::Result<std::option::Option<solana_pubkey::Pubkey>> {
|
||||
return match value {
|
||||
std::option::Option::Some(value) => {
|
||||
parse_pubkey(value, field).map(std::option::Option::Some)
|
||||
},
|
||||
std::option::Option::None => std::result::Result::Ok(std::option::Option::None),
|
||||
};
|
||||
}
|
||||
|
||||
fn planned_instruction(
|
||||
operation_code: &str,
|
||||
instruction: &solana_instruction::Instruction,
|
||||
) -> crate::ExApiPlannedInstruction {
|
||||
return crate::ExApiPlannedInstruction {
|
||||
program_id: crate::MdProgramId(instruction.program_id.to_string()),
|
||||
operation_code: operation_code.to_string(),
|
||||
accounts: instruction
|
||||
.accounts
|
||||
.iter()
|
||||
.map(|account| {
|
||||
return crate::ExApiPlannedAccount {
|
||||
pubkey: crate::MdPubkey(account.pubkey.to_string()),
|
||||
is_signer: account.is_signer,
|
||||
is_writable: account.is_writable,
|
||||
};
|
||||
})
|
||||
.collect(),
|
||||
data: instruction.data.clone(),
|
||||
};
|
||||
}
|
||||
|
||||
fn required_signers(
|
||||
fee_payer: &crate::MdPubkey,
|
||||
accounts: &[solana_instruction::AccountMeta],
|
||||
) -> std::vec::Vec<crate::ExApiRequiredSigner> {
|
||||
let mut required = vec![crate::ExApiRequiredSigner {
|
||||
pubkey: fee_payer.clone(),
|
||||
role: "fee_payer".to_string(),
|
||||
}];
|
||||
for account in accounts {
|
||||
if !account.is_signer {
|
||||
continue;
|
||||
}
|
||||
let pubkey = crate::MdPubkey(account.pubkey.to_string());
|
||||
if required.iter().any(|value| return value.pubkey == pubkey) {
|
||||
continue;
|
||||
}
|
||||
required.push(crate::ExApiRequiredSigner {
|
||||
pubkey,
|
||||
role: "metaplex_authority".to_string(),
|
||||
});
|
||||
}
|
||||
return required;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
fn approved_puff_intent() -> crate::ExMetaplexTokenMetadataExecutionIntent {
|
||||
let fee_payer = crate::MdPubkey("11111111111111111111111111111111".to_string());
|
||||
let mut policy = crate::ExApiExecutionPolicy::default();
|
||||
policy.cost_limit.max_fee_lamports = std::option::Option::Some(5_000);
|
||||
policy.cost_limit.max_spend_lamports = std::option::Option::Some(10_000);
|
||||
policy.authorized_signers = vec![fee_payer.clone()];
|
||||
return crate::ExMetaplexTokenMetadataExecutionIntent {
|
||||
intent_id: "metaplex-puff-test".to_string(),
|
||||
fee_payer,
|
||||
max_rent_lamports: 10_000,
|
||||
policy,
|
||||
allow_deprecated_operation: true,
|
||||
operation: crate::ExMetaplexTokenMetadataOperation::PuffMetadata {
|
||||
metadata: crate::MdPubkey(
|
||||
"SysvarRent111111111111111111111111111111111".to_string(),
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn deprecated_operation_requires_explicit_approval() {
|
||||
let mut intent = approved_puff_intent();
|
||||
intent.allow_deprecated_operation = false;
|
||||
let result = super::validate_intent(&intent);
|
||||
assert_eq!(
|
||||
result.as_ref().err().map(kb_core::Error::code),
|
||||
std::option::Option::Some("execution_metaplex_deprecated_operation_not_approved"),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn policy_requires_dry_run_and_sufficient_limits() {
|
||||
let mut intent = approved_puff_intent();
|
||||
intent.policy.dry_run = false;
|
||||
let dry_run_result = super::validate_intent(&intent);
|
||||
assert_eq!(
|
||||
dry_run_result.as_ref().err().map(kb_core::Error::code),
|
||||
std::option::Option::Some("execution_metaplex_dry_run_required"),
|
||||
);
|
||||
intent.policy.dry_run = true;
|
||||
intent.policy.cost_limit.max_spend_lamports = std::option::Option::Some(9_999);
|
||||
let spend_result = super::validate_intent(&intent);
|
||||
assert_eq!(
|
||||
spend_result.as_ref().err().map(kb_core::Error::code),
|
||||
std::option::Option::Some("execution_metaplex_spend_limit_insufficient"),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn metadata_and_edition_pda_validation_is_exact() {
|
||||
let program_id_result = super::parse_str_pubkey(
|
||||
kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID,
|
||||
"program_id",
|
||||
);
|
||||
assert!(program_id_result.is_ok());
|
||||
let program_id = match program_id_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let mint = solana_pubkey::Pubkey::new_unique();
|
||||
let (metadata, _) = solana_pubkey::Pubkey::find_program_address(
|
||||
&[b"metadata", program_id.as_ref(), mint.as_ref()],
|
||||
&program_id,
|
||||
);
|
||||
let (edition, _) = solana_pubkey::Pubkey::find_program_address(
|
||||
&[b"metadata", program_id.as_ref(), mint.as_ref(), b"edition"],
|
||||
&program_id,
|
||||
);
|
||||
assert!(super::validate_metadata_pda(&metadata, &mint).is_ok());
|
||||
assert!(super::validate_edition_pda(&edition, &mint).is_ok());
|
||||
let wrong = solana_pubkey::Pubkey::new_unique();
|
||||
let metadata_result = super::validate_metadata_pda(&wrong, &mint);
|
||||
assert_eq!(
|
||||
metadata_result.as_ref().err().map(kb_core::Error::code),
|
||||
std::option::Option::Some("execution_metaplex_metadata_pda_mismatch"),
|
||||
);
|
||||
let edition_result = super::validate_edition_pda(&wrong, &mint);
|
||||
assert_eq!(
|
||||
edition_result.as_ref().err().map(kb_core::Error::code),
|
||||
std::option::Option::Some("execution_metaplex_edition_pda_mismatch"),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn puff_metadata_builds_one_exact_instruction_and_deduplicates_fee_payer() {
|
||||
let intent = approved_puff_intent();
|
||||
let result = super::executor_metaplex_token_metadata_build_prepared_plan(&intent);
|
||||
assert!(result.is_ok());
|
||||
let plan = match result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
assert_eq!(plan.operation_code, crate::EX_METAPLEX_TOKEN_METADATA_PUFF_METADATA_OPERATION,);
|
||||
assert_eq!(plan.instructions.len(), 1);
|
||||
assert_eq!(
|
||||
plan.instructions[0].program_id.0,
|
||||
kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID,
|
||||
);
|
||||
assert_eq!(plan.instructions[0].data.first().copied(), std::option::Option::Some(14),);
|
||||
assert_eq!(plan.required_signers.len(), 1);
|
||||
assert_eq!(plan.required_signers[0].pubkey, intent.fee_payer);
|
||||
assert_eq!(plan.required_signers[0].role, "fee_payer");
|
||||
}
|
||||
}
|
||||
251
kb-lib/src/executor/metadata/metaplex_token_metadata/executor.rs
Normal file
251
kb-lib/src/executor/metadata/metaplex_token_metadata/executor.rs
Normal file
@@ -0,0 +1,251 @@
|
||||
// file: kb-lib/src/executor/metadata/metaplex_token_metadata/executor.rs
|
||||
// version: 3
|
||||
|
||||
//! Exact Metaplex Token Metadata capability dispatch.
|
||||
|
||||
/// Metaplex Token Metadata executor implementation.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ExMetadataMetaplexTokenMetadataExecutor;
|
||||
|
||||
impl crate::ExMetadataMetaplexTokenMetadataExecutor {
|
||||
fn exact_capability(
|
||||
&self,
|
||||
program_id: &crate::MdProgramId,
|
||||
operation_code: &str,
|
||||
) -> crate::ExApiExecutionCapability {
|
||||
if program_id.0 != kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID {
|
||||
return crate::ExApiExecutionCapability::unsupported(
|
||||
"execution_metaplex_program_not_owned",
|
||||
format!("program {} is not Metaplex Token Metadata", program_id.0),
|
||||
);
|
||||
}
|
||||
if crate::EX_METAPLEX_TOKEN_METADATA_SUPPORTED_OPERATION_CODES.contains(&operation_code) {
|
||||
return crate::ExApiExecutionCapability::supported(operation_code);
|
||||
}
|
||||
return crate::ExApiExecutionCapability::unsupported(
|
||||
"execution_metaplex_operation_unsupported",
|
||||
format!("Metaplex Token Metadata operation {operation_code} is not enabled"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::ExApiTypedInstructionExecutor for crate::ExMetadataMetaplexTokenMetadataExecutor {
|
||||
type Intent = crate::ExMetaplexTokenMetadataExecutionIntent;
|
||||
|
||||
fn capability(
|
||||
&self,
|
||||
program_id: &crate::MdProgramId,
|
||||
operation_code: &str,
|
||||
) -> crate::ExApiExecutionCapability {
|
||||
return self.exact_capability(program_id, operation_code);
|
||||
}
|
||||
|
||||
fn build_prepared_plan(
|
||||
&self,
|
||||
intent: &Self::Intent,
|
||||
) -> kb_core::Result<crate::ExApiPreparedExecutionPlan> {
|
||||
return crate::executor_metaplex_token_metadata_build_prepared_plan(intent);
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::ExApiInstructionExecutor for crate::ExMetadataMetaplexTokenMetadataExecutor {
|
||||
fn executor_name(&self) -> &'static str {
|
||||
return "kb-lib.executor.metadata.metaplex_token_metadata";
|
||||
}
|
||||
|
||||
fn executor_version(&self) -> &'static str {
|
||||
return env!("CARGO_PKG_VERSION");
|
||||
}
|
||||
|
||||
fn program_ids(&self) -> &'static [&'static str] {
|
||||
return &[kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID];
|
||||
}
|
||||
|
||||
fn supports_request(
|
||||
&self,
|
||||
request: &crate::ExApiExecutionRequest,
|
||||
) -> crate::ExApiExecutionSupport {
|
||||
return match self.exact_capability(&request.program_id, &request.operation_code) {
|
||||
crate::ExApiExecutionCapability::Supported { operation_code: _ } => {
|
||||
crate::ExApiExecutionSupport::Yes
|
||||
},
|
||||
crate::ExApiExecutionCapability::Unsupported { reason_code: _, reason: _ } => {
|
||||
crate::ExApiExecutionSupport::No
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn build_plan(
|
||||
&self,
|
||||
request: &crate::ExApiExecutionRequest,
|
||||
) -> kb_core::Result<crate::ExApiExecutionPlan> {
|
||||
match self.exact_capability(&request.program_id, &request.operation_code) {
|
||||
crate::ExApiExecutionCapability::Supported { operation_code: _ } => {},
|
||||
crate::ExApiExecutionCapability::Unsupported { reason_code, reason } => {
|
||||
return std::result::Result::Err(kb_core::Error::new(reason_code, reason));
|
||||
},
|
||||
}
|
||||
let intent = match serde_json::from_str::<crate::ExMetaplexTokenMetadataExecutionIntent>(
|
||||
&request.payload_json,
|
||||
) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"execution_metaplex_intent_deserialize_failed",
|
||||
error.to_string(),
|
||||
));
|
||||
},
|
||||
};
|
||||
if intent.operation.operation_code() != request.operation_code {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"execution_operation_code_mismatch",
|
||||
"request operation code does not match the typed Metaplex intent",
|
||||
));
|
||||
}
|
||||
let prepared = match crate::executor_metaplex_token_metadata_build_prepared_plan(&intent) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let payload_value = match serde_json::to_value(&prepared) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
return std::result::Result::Err(kb_core::Error::new(
|
||||
"execution_metaplex_plan_serialize_failed",
|
||||
error.to_string(),
|
||||
));
|
||||
},
|
||||
};
|
||||
let payload_json = match crate::executor_api_serialize_payload_json(&payload_value) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return std::result::Result::Ok(crate::ExApiExecutionPlan {
|
||||
executor_name: self.executor_name().to_string(),
|
||||
instruction_count: prepared.instructions.len(),
|
||||
payload_json,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
fn approved_puff_intent() -> crate::ExMetaplexTokenMetadataExecutionIntent {
|
||||
let fee_payer = crate::MdPubkey("11111111111111111111111111111111".to_string());
|
||||
let mut policy = crate::ExApiExecutionPolicy::default();
|
||||
policy.cost_limit.max_fee_lamports = std::option::Option::Some(5_000);
|
||||
policy.cost_limit.max_spend_lamports = std::option::Option::Some(10_000);
|
||||
policy.authorized_signers = vec![fee_payer.clone()];
|
||||
return crate::ExMetaplexTokenMetadataExecutionIntent {
|
||||
intent_id: "metaplex-executor-test".to_string(),
|
||||
fee_payer,
|
||||
max_rent_lamports: 10_000,
|
||||
policy,
|
||||
allow_deprecated_operation: true,
|
||||
operation: crate::ExMetaplexTokenMetadataOperation::PuffMetadata {
|
||||
metadata: crate::MdPubkey(
|
||||
"SysvarRent111111111111111111111111111111111".to_string(),
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn capability_is_exact_for_program_and_operation() {
|
||||
let executor = crate::ExMetadataMetaplexTokenMetadataExecutor;
|
||||
let program_id = crate::MdProgramId(
|
||||
kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID.to_string(),
|
||||
);
|
||||
let supported = <crate::ExMetadataMetaplexTokenMetadataExecutor as crate::ExApiTypedInstructionExecutor>::capability(
|
||||
&executor,
|
||||
&program_id,
|
||||
crate::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION,
|
||||
);
|
||||
assert!(matches!(supported, crate::ExApiExecutionCapability::Supported { .. }));
|
||||
let unknown = <crate::ExMetadataMetaplexTokenMetadataExecutor as crate::ExApiTypedInstructionExecutor>::capability(
|
||||
&executor,
|
||||
&program_id,
|
||||
"metadata.metaplex_token_metadata.unknown",
|
||||
);
|
||||
assert!(matches!(unknown, crate::ExApiExecutionCapability::Unsupported { .. }));
|
||||
let foreign = <crate::ExMetadataMetaplexTokenMetadataExecutor as crate::ExApiTypedInstructionExecutor>::capability(
|
||||
&executor,
|
||||
&crate::MdProgramId(kb_program_ids::SYSTEM_PROGRAM_ID.to_string()),
|
||||
crate::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION,
|
||||
);
|
||||
assert!(matches!(foreign, crate::ExApiExecutionCapability::Unsupported { .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn generic_dispatch_rejects_operation_code_mismatch() {
|
||||
let executor = crate::ExMetadataMetaplexTokenMetadataExecutor;
|
||||
let intent = approved_puff_intent();
|
||||
let payload_result = serde_json::to_string(&intent);
|
||||
assert!(payload_result.is_ok());
|
||||
let payload_json = match payload_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let request = crate::ExApiExecutionRequest {
|
||||
program_id: crate::MdProgramId(
|
||||
kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID.to_string(),
|
||||
),
|
||||
operation_code: crate::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION.to_string(),
|
||||
payload_json,
|
||||
};
|
||||
let result = <crate::ExMetadataMetaplexTokenMetadataExecutor as crate::ExApiInstructionExecutor>::build_plan(
|
||||
&executor,
|
||||
&request,
|
||||
);
|
||||
assert_eq!(
|
||||
result.as_ref().err().map(kb_core::Error::code),
|
||||
std::option::Option::Some("execution_operation_code_mismatch"),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn typed_and_generic_dispatch_build_the_same_single_instruction_plan() {
|
||||
let executor = crate::ExMetadataMetaplexTokenMetadataExecutor;
|
||||
let intent = approved_puff_intent();
|
||||
let typed_result = <crate::ExMetadataMetaplexTokenMetadataExecutor as crate::ExApiTypedInstructionExecutor>::build_prepared_plan(
|
||||
&executor,
|
||||
&intent,
|
||||
);
|
||||
assert!(typed_result.is_ok());
|
||||
let typed = match typed_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let payload_result = serde_json::to_string(&intent);
|
||||
assert!(payload_result.is_ok());
|
||||
let payload_json = match payload_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
let request = crate::ExApiExecutionRequest {
|
||||
program_id: crate::MdProgramId(
|
||||
kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID.to_string(),
|
||||
),
|
||||
operation_code: crate::EX_METAPLEX_TOKEN_METADATA_PUFF_METADATA_OPERATION.to_string(),
|
||||
payload_json,
|
||||
};
|
||||
let generic_result = <crate::ExMetadataMetaplexTokenMetadataExecutor as crate::ExApiInstructionExecutor>::build_plan(
|
||||
&executor,
|
||||
&request,
|
||||
);
|
||||
assert!(generic_result.is_ok());
|
||||
let generic = match generic_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(_) => return,
|
||||
};
|
||||
assert_eq!(typed.instructions.len(), 1);
|
||||
assert_eq!(generic.instruction_count, typed.instructions.len());
|
||||
assert_eq!(generic.executor_name, "kb-lib.executor.metadata.metaplex_token_metadata",);
|
||||
assert!(
|
||||
generic
|
||||
.payload_json
|
||||
.contains(crate::EX_METAPLEX_TOKEN_METADATA_PUFF_METADATA_OPERATION)
|
||||
);
|
||||
}
|
||||
}
|
||||
204
kb-lib/src/executor/metadata/metaplex_token_metadata/intent.rs
Normal file
204
kb-lib/src/executor/metadata/metaplex_token_metadata/intent.rs
Normal file
@@ -0,0 +1,204 @@
|
||||
// file: kb-lib/src/executor/metadata/metaplex_token_metadata/intent.rs
|
||||
// version: 3
|
||||
|
||||
//! Typed current and deprecated Metaplex Token Metadata execution intents.
|
||||
|
||||
use ts_rs::TS; // rust-rules: derive-import
|
||||
|
||||
/// Stable operation code for the current Create wrapper, discriminator 42.
|
||||
pub const EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION: &str =
|
||||
"metadata.metaplex_token_metadata.create";
|
||||
/// Stable operation code for the current Update wrapper, discriminator 50.
|
||||
pub const EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION: &str =
|
||||
"metadata.metaplex_token_metadata.update";
|
||||
/// Stable deprecated operation code for PuffMetadata, discriminator 14.
|
||||
pub const EX_METAPLEX_TOKEN_METADATA_PUFF_METADATA_OPERATION: &str =
|
||||
"metadata.metaplex_token_metadata.puff_metadata";
|
||||
/// Stable deprecated operation code for SetTokenStandard, discriminator 35.
|
||||
pub const EX_METAPLEX_TOKEN_METADATA_SET_TOKEN_STANDARD_OPERATION: &str =
|
||||
"metadata.metaplex_token_metadata.set_token_standard";
|
||||
/// Deprecated operations that require explicit operator approval at runtime.
|
||||
pub const EX_METAPLEX_TOKEN_METADATA_DEPRECATED_OPERATION_CODES: &[&str] = &[
|
||||
EX_METAPLEX_TOKEN_METADATA_PUFF_METADATA_OPERATION,
|
||||
EX_METAPLEX_TOKEN_METADATA_SET_TOKEN_STANDARD_OPERATION,
|
||||
];
|
||||
/// Current and deprecated operations implemented by `0.4.7-pre.003`.
|
||||
pub const EX_METAPLEX_TOKEN_METADATA_SUPPORTED_OPERATION_CODES: &[&str] = &[
|
||||
EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION,
|
||||
EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION,
|
||||
EX_METAPLEX_TOKEN_METADATA_PUFF_METADATA_OPERATION,
|
||||
EX_METAPLEX_TOKEN_METADATA_SET_TOKEN_STANDARD_OPERATION,
|
||||
];
|
||||
|
||||
/// One current or explicitly deprecated Metaplex Token Metadata operation.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)]
|
||||
#[serde(tag = "operation", rename_all = "snake_case")]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_lib/executor/metadata/metaplex_token_metadata/intent/ExMetaplexTokenMetadataOperation.ts"
|
||||
)]
|
||||
pub enum ExMetaplexTokenMetadataOperation {
|
||||
/// Build the current Create wrapper with `CreateArgs::V1`.
|
||||
Create {
|
||||
/// Canonical metadata PDA.
|
||||
metadata: crate::MdPubkey,
|
||||
/// Optional canonical master edition PDA.
|
||||
master_edition: std::option::Option<crate::MdPubkey>,
|
||||
/// Asset mint.
|
||||
mint: crate::MdPubkey,
|
||||
/// Whether the mint account signs this create operation.
|
||||
mint_as_signer: bool,
|
||||
/// Mint authority signer.
|
||||
authority: crate::MdPubkey,
|
||||
/// Update authority.
|
||||
update_authority: crate::MdPubkey,
|
||||
/// Whether the update authority signs this create operation.
|
||||
update_authority_as_signer: bool,
|
||||
/// Optional SPL Token or Token-2022 program account.
|
||||
spl_token_program: std::option::Option<crate::MdProgramId>,
|
||||
/// Official Metaplex create arguments.
|
||||
#[ts(type = "unknown")]
|
||||
create_args: mpl_token_metadata::types::CreateArgs,
|
||||
},
|
||||
/// Build the current Update wrapper as the update authority.
|
||||
UpdateAsUpdateAuthorityV2 {
|
||||
/// Update authority signer.
|
||||
authority: crate::MdPubkey,
|
||||
/// Asset mint.
|
||||
mint: crate::MdPubkey,
|
||||
/// Canonical metadata PDA.
|
||||
metadata: crate::MdPubkey,
|
||||
/// Optional canonical edition PDA.
|
||||
edition: std::option::Option<crate::MdPubkey>,
|
||||
/// Optional token account needed by programmable assets.
|
||||
token: std::option::Option<crate::MdPubkey>,
|
||||
/// Optional Token Authorization Rules program.
|
||||
authorization_rules_program: std::option::Option<crate::MdProgramId>,
|
||||
/// Optional Token Authorization Rules account.
|
||||
authorization_rules: std::option::Option<crate::MdPubkey>,
|
||||
/// Official Metaplex update arguments. Only `AsUpdateAuthorityV2` is accepted in this prerelease.
|
||||
#[ts(type = "unknown")]
|
||||
update_args: mpl_token_metadata::types::UpdateArgs,
|
||||
},
|
||||
/// Build the obsolete PuffMetadata instruction retained for compatibility workflows.
|
||||
#[deprecated(
|
||||
since = "0.4.7",
|
||||
note = "PuffMetadata is obsolete; use only for explicit compatibility workflows"
|
||||
)]
|
||||
PuffMetadata {
|
||||
/// Metadata account to pad to the historical fixed-length representation.
|
||||
metadata: crate::MdPubkey,
|
||||
},
|
||||
/// Build the obsolete SetTokenStandard instruction retained for compatibility workflows.
|
||||
#[deprecated(
|
||||
since = "0.4.7",
|
||||
note = "SetTokenStandard is obsolete; prefer canonical Create or Update flows"
|
||||
)]
|
||||
SetTokenStandard {
|
||||
/// Canonical metadata PDA.
|
||||
metadata: crate::MdPubkey,
|
||||
/// Metadata update authority signer.
|
||||
update_authority: crate::MdPubkey,
|
||||
/// Asset mint.
|
||||
mint: crate::MdPubkey,
|
||||
/// Optional canonical edition PDA.
|
||||
edition: std::option::Option<crate::MdPubkey>,
|
||||
},
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl crate::ExMetaplexTokenMetadataOperation {
|
||||
/// Returns the stable generic dispatch operation code.
|
||||
pub fn operation_code(&self) -> &'static str {
|
||||
return match self {
|
||||
Self::Create { .. } => crate::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION,
|
||||
Self::UpdateAsUpdateAuthorityV2 { .. } => {
|
||||
crate::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION
|
||||
},
|
||||
Self::PuffMetadata { .. } => crate::EX_METAPLEX_TOKEN_METADATA_PUFF_METADATA_OPERATION,
|
||||
Self::SetTokenStandard { .. } => {
|
||||
crate::EX_METAPLEX_TOKEN_METADATA_SET_TOKEN_STANDARD_OPERATION
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/// Returns whether the operation is obsolete and requires explicit operator approval.
|
||||
pub fn is_deprecated(&self) -> bool {
|
||||
return matches!(self, Self::PuffMetadata { .. } | Self::SetTokenStandard { .. });
|
||||
}
|
||||
}
|
||||
|
||||
/// Complete typed intent accepted by the Metaplex Token Metadata executor.
|
||||
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)]
|
||||
#[ts(
|
||||
export,
|
||||
export_to = "../frontend/ts/bindings/kb_lib/executor/metadata/metaplex_token_metadata/intent/ExMetaplexTokenMetadataExecutionIntent.ts"
|
||||
)]
|
||||
pub struct ExMetaplexTokenMetadataExecutionIntent {
|
||||
/// Stable caller-provided identifier used for logs and replay correlation.
|
||||
pub intent_id: std::string::String,
|
||||
/// Transaction fee payer.
|
||||
pub fee_payer: crate::MdPubkey,
|
||||
/// Maximum rent or account-growth spend allowed for the operation.
|
||||
pub max_rent_lamports: u64,
|
||||
/// Conservative common execution policy.
|
||||
pub policy: crate::ExApiExecutionPolicy,
|
||||
/// Explicit operator approval required for every deprecated operation.
|
||||
pub allow_deprecated_operation: bool,
|
||||
/// Typed Metaplex operation.
|
||||
pub operation: crate::ExMetaplexTokenMetadataOperation,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn operation_codes_are_exact_unique_and_partition_deprecated_operations() {
|
||||
assert_eq!(crate::EX_METAPLEX_TOKEN_METADATA_SUPPORTED_OPERATION_CODES.len(), 4);
|
||||
let mut unique = std::collections::BTreeSet::new();
|
||||
for operation_code in crate::EX_METAPLEX_TOKEN_METADATA_SUPPORTED_OPERATION_CODES {
|
||||
assert!(unique.insert(*operation_code));
|
||||
}
|
||||
assert_eq!(crate::EX_METAPLEX_TOKEN_METADATA_DEPRECATED_OPERATION_CODES.len(), 2);
|
||||
assert!(
|
||||
crate::EX_METAPLEX_TOKEN_METADATA_DEPRECATED_OPERATION_CODES
|
||||
.contains(&crate::EX_METAPLEX_TOKEN_METADATA_PUFF_METADATA_OPERATION,)
|
||||
);
|
||||
assert!(
|
||||
crate::EX_METAPLEX_TOKEN_METADATA_DEPRECATED_OPERATION_CODES
|
||||
.contains(&crate::EX_METAPLEX_TOKEN_METADATA_SET_TOKEN_STANDARD_OPERATION,)
|
||||
);
|
||||
assert!(
|
||||
!crate::EX_METAPLEX_TOKEN_METADATA_DEPRECATED_OPERATION_CODES
|
||||
.contains(&crate::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION,)
|
||||
);
|
||||
assert!(
|
||||
!crate::EX_METAPLEX_TOKEN_METADATA_DEPRECATED_OPERATION_CODES
|
||||
.contains(&crate::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION,)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn deprecated_operations_report_their_exact_codes_and_status() {
|
||||
let puff = crate::ExMetaplexTokenMetadataOperation::PuffMetadata {
|
||||
metadata: crate::MdPubkey("11111111111111111111111111111111".to_string()),
|
||||
};
|
||||
let set_token_standard = crate::ExMetaplexTokenMetadataOperation::SetTokenStandard {
|
||||
metadata: crate::MdPubkey("11111111111111111111111111111111".to_string()),
|
||||
update_authority: crate::MdPubkey("11111111111111111111111111111111".to_string()),
|
||||
mint: crate::MdPubkey("11111111111111111111111111111111".to_string()),
|
||||
edition: std::option::Option::None,
|
||||
};
|
||||
assert_eq!(
|
||||
puff.operation_code(),
|
||||
crate::EX_METAPLEX_TOKEN_METADATA_PUFF_METADATA_OPERATION,
|
||||
);
|
||||
assert_eq!(
|
||||
set_token_standard.operation_code(),
|
||||
crate::EX_METAPLEX_TOKEN_METADATA_SET_TOKEN_STANDARD_OPERATION,
|
||||
);
|
||||
assert!(puff.is_deprecated());
|
||||
assert!(set_token_standard.is_deprecated());
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: kb-lib/src/lib.rs
|
||||
// version: 26
|
||||
// version: 28
|
||||
|
||||
//! Consolidated decoder, executor, materializer and shared model library.
|
||||
#![warn(missing_docs)]
|
||||
@@ -358,6 +358,18 @@ pub use self::decoder::decoder_metadata_metaplex_token_metadata_decode_use_autho
|
||||
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;
|
||||
/// Stable operation code for the current Metaplex Create wrapper.
|
||||
pub use self::executor::EX_METAPLEX_TOKEN_METADATA_CREATE_OPERATION;
|
||||
/// Deprecated Metaplex operation codes requiring explicit approval.
|
||||
pub use self::executor::EX_METAPLEX_TOKEN_METADATA_DEPRECATED_OPERATION_CODES;
|
||||
/// Stable deprecated Metaplex PuffMetadata operation code.
|
||||
pub use self::executor::EX_METAPLEX_TOKEN_METADATA_PUFF_METADATA_OPERATION;
|
||||
/// Stable deprecated Metaplex SetTokenStandard operation code.
|
||||
pub use self::executor::EX_METAPLEX_TOKEN_METADATA_SET_TOKEN_STANDARD_OPERATION;
|
||||
/// Current and deprecated Metaplex Token Metadata operations.
|
||||
pub use self::executor::EX_METAPLEX_TOKEN_METADATA_SUPPORTED_OPERATION_CODES;
|
||||
/// Stable operation code for the current Metaplex Update wrapper.
|
||||
pub use self::executor::EX_METAPLEX_TOKEN_METADATA_UPDATE_OPERATION;
|
||||
/// Exposes the Address Lookup Table close operation code.
|
||||
pub use self::executor::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_CLOSE_OPERATION;
|
||||
/// Exposes the Address Lookup Table create operation code.
|
||||
@@ -1016,12 +1028,16 @@ pub use self::executor::ExLendingKaminoExecutor;
|
||||
pub use self::executor::ExLendingMarginfiV2Executor;
|
||||
/// Exposes the reserved `ExLockRaydiumLpExecutor` implementation.
|
||||
pub use self::executor::ExLockRaydiumLpExecutor;
|
||||
/// Exposes the reserved `ExMetadataMetaplexTokenMetadataExecutor` implementation.
|
||||
/// Exposes the Metaplex Token Metadata executor.
|
||||
pub use self::executor::ExMetadataMetaplexTokenMetadataExecutor;
|
||||
/// Exposes the reserved `ExMetadataSolanaProgramMetadataExecutor` implementation.
|
||||
pub use self::executor::ExMetadataSolanaProgramMetadataExecutor;
|
||||
/// Exposes the reserved `ExMetadataSplNameServiceExecutor` implementation.
|
||||
pub use self::executor::ExMetadataSplNameServiceExecutor;
|
||||
/// Exposes the complete Metaplex Token Metadata execution intent.
|
||||
pub use self::executor::ExMetaplexTokenMetadataExecutionIntent;
|
||||
/// Exposes one current Metaplex Token Metadata operation.
|
||||
pub use self::executor::ExMetaplexTokenMetadataOperation;
|
||||
/// Exposes the reserved `ExNftMetaplexBubblegumExecutor` implementation.
|
||||
pub use self::executor::ExNftMetaplexBubblegumExecutor;
|
||||
/// Exposes the reserved `ExNftTensorCnftExecutor` implementation.
|
||||
@@ -1833,6 +1849,8 @@ pub(crate) use self::executor::TRACING_TARGET_EXECUTOR_SPL_MEMO;
|
||||
pub(crate) use self::executor::TRACING_TARGET_EXECUTOR_SPL_TOKEN;
|
||||
/// Canonical SPL Token-2022 tracing target.
|
||||
pub(crate) use self::executor::TRACING_TARGET_EXECUTOR_SPL_TOKEN_2022;
|
||||
/// Internal Metaplex Token Metadata prepared-plan builder.
|
||||
pub(crate) use self::executor::executor_metaplex_token_metadata_build_prepared_plan;
|
||||
/// Crate-root access to `build_prepared_plan` from `address_lookup_table`.
|
||||
pub(crate) use self::executor::executor_solana_core_address_lookup_table_build_prepared_plan;
|
||||
/// Crate-root access to `build_prepared_plan` from `builder`.
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
<!-- file: kb-pipeline/CHANGELOG.md -->
|
||||
<!-- version: 8 -->
|
||||
<!-- version: 9 -->
|
||||
|
||||
# CHANGELOG — kb-pipeline
|
||||
|
||||
## 0.4.7-pre.003-delta-fix-002 — dette de compatibilité des exécuteurs
|
||||
|
||||
- ajout d’un TODO de réaudit de l’orchestration après extension future des exécuteurs Solana Core/SPL aux opérations obsolètes encore constructibles ;
|
||||
- exigence de propager les avertissements de dépréciation et l’approbation opérateur sans réintroduire les versions remplacées intermédiaires.
|
||||
|
||||
## 0.4.6
|
||||
|
||||
- alignement de la crate sur la version fonctionnelle bot3 `0.4.6` ;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- file: kb-pipeline/TODO.md -->
|
||||
<!-- version: 6 -->
|
||||
<!-- version: 7 -->
|
||||
|
||||
# TODO — kb-pipeline
|
||||
|
||||
@@ -8,6 +8,12 @@
|
||||
- [ ] Metaplex Token Metadata - intégrer l’exécution et les étapes restantes au replay bot3.
|
||||
- [ ] Metaplex Token Metadata - ajouter l’orchestration nécessaire aux préflights, postconditions et validations.
|
||||
|
||||
## Réaudit ultérieur des surfaces historiques
|
||||
|
||||
- [ ] Version à déterminer - après le réaudit des exécuteurs Solana Core et SPL dans `kb-lib`, vérifier que `kb-pipeline` orchestre sans filtrage implicite les opérations courantes et les opérations obsolètes explicitement approuvées.
|
||||
- [ ] Préflight et diagnostics - propager les statuts de remplacement canonique, les avertissements de dépréciation et l’approbation opérateur exigée par les builders obsolètes.
|
||||
- [ ] Tests - ajouter les cas pipeline nécessaires pour les opérations dépréciées réintroduites, sans créer de chemins d’exécution pour les versions remplacées intermédiaires.
|
||||
|
||||
## Versions ultérieures
|
||||
|
||||
- [ ] Dette technique - auditer les duplications résiduelles entre orchestrations SPL Token classique et Token-2022.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
# file: scripts/audit_khadhroony_workspace_rules.py
|
||||
# version: 13
|
||||
# version: 14
|
||||
|
||||
"""Audit mechanically verifiable rules specific to khadhroony-bot3."""
|
||||
|
||||
@@ -440,13 +440,13 @@ def audit_reserved_executor_scaffolds(root: pathlib.Path) -> list[Violation]:
|
||||
"reserved executor must build only an explicit zero-instruction plan",
|
||||
)
|
||||
)
|
||||
if reserved_count != 105:
|
||||
if reserved_count != 104:
|
||||
violations.append(
|
||||
Violation(
|
||||
"KH_EX007",
|
||||
"kb-lib/src/executor",
|
||||
1,
|
||||
f"expected 105 reserved executor boundaries, found {reserved_count}",
|
||||
f"expected 104 reserved executor boundaries, found {reserved_count}",
|
||||
)
|
||||
)
|
||||
return violations
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user