v0.4.8-pre.003

This commit is contained in:
2026-08-05 15:04:27 +02:00
parent 0078956d48
commit de1062b2d3
13 changed files with 1060 additions and 108 deletions

View File

@@ -1,5 +1,5 @@
# file: Cargo.toml # file: Cargo.toml
# version: 24 # version: 25
[workspace] [workspace]
resolver = "3" resolver = "3"
@@ -18,7 +18,7 @@ members = [
] ]
[workspace.package] [workspace.package]
version = "0.4.8-pre.2" version = "0.4.8-pre.3"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-bot3" repository = "https://git.sasedev.com/Sasedev/khadhroony-bot3"

View File

@@ -1,5 +1,5 @@
<!-- file: docs/IDL_TO_KB_LIB_NOMENCLATURE.md --> <!-- file: docs/IDL_TO_KB_LIB_NOMENCLATURE.md -->
<!-- version: 2 --> <!-- version: 3 -->
# Correspondance IDL ↔ `kb-lib` # Correspondance IDL ↔ `kb-lib`
@@ -7,7 +7,7 @@ Ce tableau constitue la cible de classification pour les futurs décodeurs et ex
Il ne signifie pas que tous les modules proposés sont déjà implémentés. Il ne signifie pas que tous les modules proposés sont déjà implémentés.
| IDL v3 | Module actuel | Module cible | Action Rust | | IDL v3 | Module actuel | Module cible | Action Rust |
|-----------------------------------------------------------------------------------------------------------------------|------------------------------------------------|------------------------------------------------|---------------------------------------------------------------------------| |-----------------------------------------------------------------------------------------------------------------------|------------------------------------------------|------------------------------------------------|----------------------------------------------------------------------|
| `adapter.DecZY86MU5Gj7kppfUCEmd4LbXXuyZH1yHaP2NTqdiZB.saber_decimal_wrapper.V0_0_0.from_solscan.json` | `adapter/saber_decimal_wrapper` | `adapter/saber_decimal_wrapper` | Aucune | | `adapter.DecZY86MU5Gj7kppfUCEmd4LbXXuyZH1yHaP2NTqdiZB.saber_decimal_wrapper.V0_0_0.from_solscan.json` | `adapter/saber_decimal_wrapper` | `adapter/saber_decimal_wrapper` | Aucune |
| `adapter.TwRapQCDhWkZRrDaHfZGuHxkZ91gHDRkyuzNqeU5MgR.spl_token_wrap.V0_1_0.from_github_solana_program.json` | `—` | `adapter/spl_token_wrap` | Créer `adapter/spl_token_wrap` | | `adapter.TwRapQCDhWkZRrDaHfZGuHxkZ91gHDRkyuzNqeU5MgR.spl_token_wrap.V0_1_0.from_github_solana_program.json` | `—` | `adapter/spl_token_wrap` | Créer `adapter/spl_token_wrap` |
| `amm.2wT8Yq49kHgDzXuPxZSaeLaH1qbmGXtEyPy64bL7aD3c.lifinity_swap_v2.V0_1_1.from_solscan.json` | `amm/lifinity_swap_v2` | `amm/lifinity_swap_v2` | Aucune | | `amm.2wT8Yq49kHgDzXuPxZSaeLaH1qbmGXtEyPy64bL7aD3c.lifinity_swap_v2.V0_1_1.from_solscan.json` | `amm/lifinity_swap_v2` | `amm/lifinity_swap_v2` | Aucune |
@@ -51,7 +51,7 @@ Il ne signifie pas que tous les modules proposés sont déjà implémentés.
| `lending.KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD.kamino.V1_23_0.from_solscan.json` | `lending/kamino` | `lending/kamino` | Aucune | | `lending.KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD.kamino.V1_23_0.from_solscan.json` | `lending/kamino` | `lending/kamino` | Aucune |
| `lending.MFv2hWf31Z9kbCa1snEPYctwafyhdvnV7FZnsebVacA.marginfi_v2.V0_1_8.from_solscan.json` | `lending/marginfi_v2` | `lending/marginfi_v2` | Aucune | | `lending.MFv2hWf31Z9kbCa1snEPYctwafyhdvnV7FZnsebVacA.marginfi_v2.V0_1_8.from_solscan.json` | `lending/marginfi_v2` | `lending/marginfi_v2` | Aucune |
| `lock.LockrWmn6K5twhz3y9w1dQERbmgSaRkfnTeTKbpofwE.raydium_lp.V0_1_0.from_solscan.json` | `lock/raydium_lp` | `lock/raydium_lp` | Aucune | | `lock.LockrWmn6K5twhz3y9w1dQERbmgSaRkfnTeTKbpofwE.raydium_lp.V0_1_0.from_solscan.json` | `lock/raydium_lp` | `lock/raydium_lp` | Aucune |
| `metadata.ProgM6JCCvbYkfKqJYHePx4xxSUSqJp7rh8Lyv7nk7S.solana_program_metadata.V0_0_0.from_github_solana_program.json` | `metadata/solana_program_metadata` | `metadata/solana_program_metadata` | Remplacer le décodeur et lexécuteur réservés par limplémentation réelle | | `metadata.ProgM6JCCvbYkfKqJYHePx4xxSUSqJp7rh8Lyv7nk7S.solana_program_metadata.V0_0_0.from_github_solana_program.json` | `metadata/solana_program_metadata` | `metadata/solana_program_metadata` | Modèles wire présents ; implémenter le décodeur et lexécuteur réels |
| `metadata.metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s.metaplex_token_metadata.V1_14_0.from_solscan.json` | `metadata/metaplex_token_metadata` | `metadata/metaplex_token_metadata` | Aucune | | `metadata.metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s.metaplex_token_metadata.V1_14_0.from_solscan.json` | `metadata/metaplex_token_metadata` | `metadata/metaplex_token_metadata` | Aucune |
| `nft.BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY.metaplex_bubblegum.V0_12_0.from_solscan.json` | `nft/metaplex_bubblegum` | `nft/metaplex_bubblegum` | Aucune | | `nft.BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY.metaplex_bubblegum.V0_12_0.from_solscan.json` | `nft/metaplex_bubblegum` | `nft/metaplex_bubblegum` | Aucune |
| `nft.TCMPhJdwDryooaGtiocG1u3xcYbRpiJzb283XfCZsDp.tensor_cnft.V2_0_1.from_solscan.json` | `—` | `nft/tensor_cnft` | Créer `nft/tensor_cnft` | | `nft.TCMPhJdwDryooaGtiocG1u3xcYbRpiJzb283XfCZsDp.tensor_cnft.V2_0_1.from_solscan.json` | `—` | `nft/tensor_cnft` | Créer `nft/tensor_cnft` |

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/V0_4_8_SOLANA_PROGRAM_METADATA_AND_TOKEN_2022_COMPLETENESS_PLAN.md --> <!-- file: docs/plans/V0_4_8_SOLANA_PROGRAM_METADATA_AND_TOKEN_2022_COMPLETENESS_PLAN.md -->
<!-- version: 2 --> <!-- version: 3 -->
# Plan `0.4.8` — Solana Program Metadata et complétude Token-2022 # Plan `0.4.8` — Solana Program Metadata et complétude Token-2022
@@ -191,10 +191,16 @@ Résultat durable : le décodeur Token-2022 couvre déjà les cinq payloads wire
Critère : matrice fermée et sources de vérité citées avant implémentation. Critère : matrice fermée et sources de vérité citées avant implémentation.
### `0.4.8-pre.003` — modèles Solana Program Metadata ### `0.4.8-pre.003` — modèles Solana Program Metadata — terminé
Résultat durable : la façade `kb-lib` expose les tailles fixes auditées, les enums wire fermées, les cinq erreurs custom, le seed brut de 16 octets, la référence externe zeroable et les trois formes de données on-chain. Les conversions rejettent les valeurs inconnues, les tailles incorrectes, les seeds UTF-8 trop longs, les données directes ou URL vides et les longueurs non représentables par `u32`. Aucune URL ni donnée externe nest résolue.
- types, enums, erreurs, bornes et sérialisation ; - types, enums, erreurs, bornes et sérialisation ;
- tests contractuels des représentations publiques. - tests contractuels des représentations publiques ;
- test dAPI externe depuis la façade `kb_lib` ;
- documentation généraliste de la nouvelle surface publique.
Critère atteint côté delta : modèles et tests statiques livrés ; compilation, Clippy et tests Rust à exécuter dans lenvironnement du projet avant validation définitive.
### `0.4.8-pre.004` — décodeur de comptes Solana Program Metadata ### `0.4.8-pre.004` — décodeur de comptes Solana Program Metadata

View File

@@ -1,8 +1,16 @@
<!-- file: kb-lib/CHANGELOG.md --> <!-- file: kb-lib/CHANGELOG.md -->
<!-- version: 16 --> <!-- version: 17 -->
# CHANGELOG — kb-lib # CHANGELOG — kb-lib
## 0.4.8-pre.003 — modèles Solana Program Metadata
- ajoute les constantes publiques des layouts fixes `Buffer`, `Metadata`, seed et référence externe ;
- ajoute les enums wire fermées, les cinq erreurs custom officielles et les conversions fail-closed ;
- ajoute les modèles bornés du seed, des données directes, URL et externes sans résolution off-chain ;
- ajoute les sérialisations wire sûres, les représentations JSON stables et les tests de façade publique ;
- conserve le décodeur Solana Program Metadata au statut réservé jusquaux phases comptes et instructions.
## 0.4.7-pre.016 — clôture documentaire Metaplex ## 0.4.7-pre.016 — clôture documentaire Metaplex
- généralise la documentation dusage de la surface Metaplex ; - généralise la documentation dusage de la surface Metaplex ;

View File

@@ -1,5 +1,5 @@
<!-- file: kb-lib/README.md --> <!-- file: kb-lib/README.md -->
<!-- version: 19 --> <!-- version: 20 -->
# kb-lib # kb-lib
@@ -7,7 +7,15 @@
## Périmètre ## Périmètre
La crate couvre actuellement Solana Core, SPL Memo, SPL Token classique, SPL Associated Token Account, Token-2022, le registre SPL ElGamal et Metaplex Token Metadata. La crate couvre actuellement Solana Core, SPL Memo, SPL Token classique, SPL Associated Token Account, Token-2022, le registre SPL ElGamal et Metaplex Token Metadata. Elle expose également les modèles wire bornés de Solana Program Metadata ; son décodeur reste réservé tant que les comptes et instructions ne sont pas implémentés.
Pour Solana Program Metadata, la surface publique fondatrice comprend :
- les tailles fixes officielles des headers, seeds et références externes ;
- les enums fermées de discriminateur, encodage, compression, format et source ;
- le seed brut de 16 octets avec helpers UTF-8 bornés ;
- les données directes, URL et références vers un autre compte sans résolution off-chain ;
- les cinq erreurs custom officielles et les erreurs de conversion fail-closed.
Pour Metaplex Token Metadata, elle fournit : Pour Metaplex Token Metadata, elle fournit :
@@ -40,6 +48,7 @@ Pour Metaplex Token Metadata, elle fournit :
- contrats `MtApi*` et matérialisateurs concrets ; - contrats `MtApi*` et matérialisateurs concrets ;
- contrats `ExApi*`, politiques de sécurité et exécuteurs `Ex*Executor` ; - contrats `ExApi*`, politiques de sécurité et exécuteurs `Ex*Executor` ;
- `ExMetadataMetaplexTokenMetadataExecutor`, `ExMetaplexTokenMetadataExecutionIntent` et `ExMetaplexTokenMetadataOperation` ; - `ExMetadataMetaplexTokenMetadataExecutor`, `ExMetaplexTokenMetadataExecutionIntent` et `ExMetaplexTokenMetadataOperation` ;
- modèles `DcMetadataSpm*` et constantes `DC_METADATA_SPM_*` pour le contrat Solana Program Metadata ;
- modèles canoniques `Md*` réexportés par la façade. - modèles canoniques `Md*` réexportés par la façade.
Les exemples dappel et invariants sont documentés dans [USAGE.md](USAGE.md). Les exemples dappel et invariants sont documentés dans [USAGE.md](USAGE.md).
@@ -54,6 +63,8 @@ Les exemples dappel et invariants sont documentés dans [USAGE.md](USAGE.md).
La surface fonctionnelle Metaplex de la crate est achevée. Les campagnes Devnet restantes relèvent de `kb-pipeline-demo-scenarios` et de `kb-app-demo-desktop`, non dun manque de builder dans `kb-lib`. La surface fonctionnelle Metaplex de la crate est achevée. Les campagnes Devnet restantes relèvent de `kb-pipeline-demo-scenarios` et de `kb-app-demo-desktop`, non dun manque de builder dans `kb-lib`.
Pour Solana Program Metadata, seuls les modèles wire sont actuellement actifs. Le décodeur de protocole reste volontairement réservé jusquà limplémentation bornée des comptes et instructions.
Le registre ElGamal nest pas déclaré validé réellement sur réseau. Le registre ElGamal nest pas déclaré validé réellement sur réseau.
## Documentation ## Documentation

View File

@@ -1,5 +1,5 @@
<!-- file: kb-lib/USAGE.md --> <!-- file: kb-lib/USAGE.md -->
<!-- version: 8 --> <!-- version: 9 -->
# Utilisation de kb-lib # Utilisation de kb-lib
@@ -82,6 +82,34 @@ println!("parsed registry state: {registry:#?}");
Les parseurs vérifient les bornes et formats quils annoncent. Ils ne reconstruisent pas un état historique depuis un RPC courant. Les parseurs vérifient les bornes et formats quils annoncent. Ils ne reconstruisent pas un état historique depuis un RPC courant.
## Modèles Solana Program Metadata
Les modèles `DcMetadataSpm*` représentent uniquement le contrat on-chain du programme `ProgM6JCCvbYkfKqJYHePx4xxSUSqJp7rh8Lyv7nk7S`. Ils ne téléchargent jamais les URL et ne lisent pas automatiquement les comptes référencés par `External`.
```rust
let seed_result = kb_lib::DcMetadataSpmSeed::from_utf8("idl");
let seed = match seed_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
let external = kb_lib::DcMetadataSpmExternalData::from_zeroable_length(
solana_pubkey::Pubkey::new_from_array([7_u8; 32]),
128,
4096,
);
let data = kb_lib::DcMetadataSpmData::External(external);
let wire_bytes = match data.to_wire_bytes() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()),
};
assert_eq!(seed.as_bytes().len(), kb_lib::DC_METADATA_SPM_SEED_BYTES);
assert_eq!(wire_bytes.len(), kb_lib::DC_METADATA_SPM_EXTERNAL_DATA_BYTES);
```
Le champ `length` externe suit le contrat zeroable : la valeur wire `0` devient `None` et signifie que la référence porte sur tout le compte à partir de `offset`. Les contenus `Direct` et `Url` doivent être non vides, et toute longueur doit rester représentable par le `u32` du header.
Le décodeur de protocole `DcMetadataSolanaProgramMetadataDecoder` est encore réservé. Les modèles servent déjà de contrat public stable aux futurs décodeurs de comptes, décodeurs dinstructions, matérialisateurs et builders.
## Comptes Metaplex Token Metadata ## Comptes Metaplex Token Metadata
Les fonctions `decoder_metadata_metaplex_token_metadata_decode_*_account` décodent les familles de comptes publiques prises en charge. Le consommateur doit fournir les données et le contexte attendus par la signature exacte de la fonction concernée. Les parseurs conservent les variantes historiques et refusent les longueurs, propriétaires ou suffixes incompatibles. Les fonctions `decoder_metadata_metaplex_token_metadata_decode_*_account` décodent les familles de comptes publiques prises en charge. Le consommateur doit fournir les données et le contexte attendus par la signature exacte de la fonction concernée. Les parseurs conservent les variantes historiques et refusent les longueurs, propriétaires ou suffixes incompatibles.

View File

@@ -1,5 +1,5 @@
// file: kb-lib/src/decoder.rs // file: kb-lib/src/decoder.rs
// version: 12 // version: 13
//! Consolidated decoder modules. //! Consolidated decoder modules.
@@ -332,6 +332,14 @@ pub(crate) use self::metadata::DC_METADATA_MTM_VERIFY_COLLECTION_DISCRIMINATOR;
pub(crate) use self::metadata::DC_METADATA_MTM_VERIFY_DISCRIMINATOR; pub(crate) use self::metadata::DC_METADATA_MTM_VERIFY_DISCRIMINATOR;
/// Verify Sized Collection Item discriminator. /// Verify Sized Collection Item discriminator.
pub(crate) use self::metadata::DC_METADATA_MTM_VERIFY_SIZED_COLLECTION_ITEM_DISCRIMINATOR; pub(crate) use self::metadata::DC_METADATA_MTM_VERIFY_SIZED_COLLECTION_ITEM_DISCRIMINATOR;
/// Exact fixed byte length of one Buffer account header.
pub use self::metadata::DC_METADATA_SPM_BUFFER_HEADER_BYTES;
/// Exact byte length of one external-account data reference.
pub use self::metadata::DC_METADATA_SPM_EXTERNAL_DATA_BYTES;
/// Exact fixed byte length of one Metadata account header.
pub use self::metadata::DC_METADATA_SPM_METADATA_HEADER_BYTES;
/// Exact byte length of one Solana Program Metadata seed.
pub use self::metadata::DC_METADATA_SPM_SEED_BYTES;
/// Exact decoder for the Metaplex Token Metadata program. /// Exact decoder for the Metaplex Token Metadata program.
pub use self::metadata::DcMetadataMetaplexTokenMetadataDecoder; pub use self::metadata::DcMetadataMetaplexTokenMetadataDecoder;
/// Canonical projection shared by Collection and Use Authority records. /// Canonical projection shared by Collection and Use Authority records.
@@ -384,6 +392,26 @@ pub use self::metadata::DcMetadataMtmTokenRecordAccountSnapshot;
pub use self::metadata::DcMetadataSolanaProgramMetadataDecoder; pub use self::metadata::DcMetadataSolanaProgramMetadataDecoder;
/// Reserved `kb-lib.decoder.metadata.spl_name_service` decoder. /// Reserved `kb-lib.decoder.metadata.spl_name_service` decoder.
pub use self::metadata::DcMetadataSplNameServiceDecoder; pub use self::metadata::DcMetadataSplNameServiceDecoder;
/// Solana Program Metadata account discriminator.
pub use self::metadata::DcMetadataSpmAccountDiscriminator;
/// Compression declared for Solana Program Metadata content.
pub use self::metadata::DcMetadataSpmCompression;
/// Typed Solana Program Metadata content without off-chain resolution.
pub use self::metadata::DcMetadataSpmData;
/// On-chain source used for Solana Program Metadata content.
pub use self::metadata::DcMetadataSpmDataSource;
/// Encoding declared for Solana Program Metadata content.
pub use self::metadata::DcMetadataSpmEncoding;
/// Fixed external-account data reference.
pub use self::metadata::DcMetadataSpmExternalData;
/// Format declared for Solana Program Metadata content.
pub use self::metadata::DcMetadataSpmFormat;
/// Bounded Solana Program Metadata model conversion error.
pub use self::metadata::DcMetadataSpmModelError;
/// Official Solana Program Metadata custom error.
pub use self::metadata::DcMetadataSpmProgramError;
/// Exact fixed-size Solana Program Metadata seed.
pub use self::metadata::DcMetadataSpmSeed;
/// Canonical tracing target for this crate. /// Canonical tracing target for this crate.
pub(crate) use self::metadata::TRACING_TARGET_DECODER_METADATA_METAPLEX_TOKEN_METADATA; pub(crate) use self::metadata::TRACING_TARGET_DECODER_METADATA_METAPLEX_TOKEN_METADATA;
/// Crate-root access to `decode` from `instruction`. /// Crate-root access to `decode` from `instruction`.

View File

@@ -1,5 +1,5 @@
// file: kb-lib/src/decoder/metadata.rs // file: kb-lib/src/decoder/metadata.rs
// version: 7 // version: 8
//! `metadata` decoder family. //! `metadata` decoder family.
@@ -77,8 +77,36 @@ pub use self::metaplex_token_metadata::decoder_metadata_metaplex_token_metadata_
pub use self::metaplex_token_metadata::decoder_metadata_metaplex_token_metadata_decode_token_record_account; pub use self::metaplex_token_metadata::decoder_metadata_metaplex_token_metadata_decode_token_record_account;
/// Decodes one Use Authority Record account. /// Decodes one Use Authority Record account.
pub use self::metaplex_token_metadata::decoder_metadata_metaplex_token_metadata_decode_use_authority_record_account; pub use self::metaplex_token_metadata::decoder_metadata_metaplex_token_metadata_decode_use_authority_record_account;
/// Exact fixed byte length of one Buffer account header.
pub use self::solana_program_metadata::DC_METADATA_SPM_BUFFER_HEADER_BYTES;
/// Exact byte length of one external-account data reference.
pub use self::solana_program_metadata::DC_METADATA_SPM_EXTERNAL_DATA_BYTES;
/// Exact fixed byte length of one Metadata account header.
pub use self::solana_program_metadata::DC_METADATA_SPM_METADATA_HEADER_BYTES;
/// Exact byte length of one Solana Program Metadata seed.
pub use self::solana_program_metadata::DC_METADATA_SPM_SEED_BYTES;
/// Reserved `kb-lib.decoder.metadata.solana_program_metadata` decoder. /// Reserved `kb-lib.decoder.metadata.solana_program_metadata` decoder.
pub use self::solana_program_metadata::DcMetadataSolanaProgramMetadataDecoder; pub use self::solana_program_metadata::DcMetadataSolanaProgramMetadataDecoder;
/// Solana Program Metadata account discriminator.
pub use self::solana_program_metadata::DcMetadataSpmAccountDiscriminator;
/// Compression declared for Solana Program Metadata content.
pub use self::solana_program_metadata::DcMetadataSpmCompression;
/// Typed Solana Program Metadata content without off-chain resolution.
pub use self::solana_program_metadata::DcMetadataSpmData;
/// On-chain source used for Solana Program Metadata content.
pub use self::solana_program_metadata::DcMetadataSpmDataSource;
/// Encoding declared for Solana Program Metadata content.
pub use self::solana_program_metadata::DcMetadataSpmEncoding;
/// Fixed external-account data reference.
pub use self::solana_program_metadata::DcMetadataSpmExternalData;
/// Format declared for Solana Program Metadata content.
pub use self::solana_program_metadata::DcMetadataSpmFormat;
/// Bounded Solana Program Metadata model conversion error.
pub use self::solana_program_metadata::DcMetadataSpmModelError;
/// Official Solana Program Metadata custom error.
pub use self::solana_program_metadata::DcMetadataSpmProgramError;
/// Exact fixed-size Solana Program Metadata seed.
pub use self::solana_program_metadata::DcMetadataSpmSeed;
/// Reserved `kb-lib.decoder.metadata.spl_name_service` decoder. /// Reserved `kb-lib.decoder.metadata.spl_name_service` decoder.
pub use self::spl_name_service::DcMetadataSplNameServiceDecoder; pub use self::spl_name_service::DcMetadataSplNameServiceDecoder;

View File

@@ -1,7 +1,39 @@
// file: kb-lib/src/decoder/metadata/solana_program_metadata.rs // file: kb-lib/src/decoder/metadata/solana_program_metadata.rs
// version: 1 // version: 2
//! Reserved protocol decoder for `metadata_solana_program_metadata`. //! Public Solana Program Metadata models and reserved protocol decoder.
mod constants;
mod model;
/// Exact fixed byte length of one Buffer account header.
pub use self::constants::DC_METADATA_SPM_BUFFER_HEADER_BYTES;
/// Exact byte length of one external-account data reference.
pub use self::constants::DC_METADATA_SPM_EXTERNAL_DATA_BYTES;
/// Exact fixed byte length of one Metadata account header.
pub use self::constants::DC_METADATA_SPM_METADATA_HEADER_BYTES;
/// Exact byte length of one Solana Program Metadata seed.
pub use self::constants::DC_METADATA_SPM_SEED_BYTES;
/// Solana Program Metadata account discriminator.
pub use self::model::DcMetadataSpmAccountDiscriminator;
/// Compression declared for metadata content.
pub use self::model::DcMetadataSpmCompression;
/// Typed on-chain metadata content without off-chain resolution.
pub use self::model::DcMetadataSpmData;
/// On-chain source used for metadata content.
pub use self::model::DcMetadataSpmDataSource;
/// Encoding declared for metadata content.
pub use self::model::DcMetadataSpmEncoding;
/// Fixed external-account data reference.
pub use self::model::DcMetadataSpmExternalData;
/// Format declared for metadata content.
pub use self::model::DcMetadataSpmFormat;
/// Bounded model conversion error.
pub use self::model::DcMetadataSpmModelError;
/// Official Solana Program Metadata custom error.
pub use self::model::DcMetadataSpmProgramError;
/// Exact fixed-size metadata seed.
pub use self::model::DcMetadataSpmSeed;
/// Reserved decoder for the `metadata_solana_program_metadata` program surface. /// Reserved decoder for the `metadata_solana_program_metadata` program surface.
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]

View File

@@ -0,0 +1,13 @@
// file: kb-lib/src/decoder/metadata/solana_program_metadata/constants.rs
// version: 1
//! Bounded Solana Program Metadata wire constants.
/// Exact byte length of one Solana Program Metadata seed.
pub const DC_METADATA_SPM_SEED_BYTES: usize = 16;
/// Exact fixed byte length of one Buffer account header.
pub const DC_METADATA_SPM_BUFFER_HEADER_BYTES: usize = 96;
/// Exact fixed byte length of one Metadata account header.
pub const DC_METADATA_SPM_METADATA_HEADER_BYTES: usize = 96;
/// Exact byte length of one external-account data reference.
pub const DC_METADATA_SPM_EXTERNAL_DATA_BYTES: usize = 40;

View File

@@ -0,0 +1,740 @@
// file: kb-lib/src/decoder/metadata/solana_program_metadata/model.rs
// version: 1
//! Public bounded models for the Solana Program Metadata wire contract.
/// Solana Program Metadata account discriminator.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
#[repr(u8)]
#[serde(rename_all = "snake_case")]
pub enum DcMetadataSpmAccountDiscriminator {
/// Uninitialized account data.
Empty = 0,
/// Writable staging buffer.
Buffer = 1,
/// Initialized program metadata account.
Metadata = 2,
}
impl DcMetadataSpmAccountDiscriminator {
/// Returns the exact wire discriminant.
pub const fn wire_value(self) -> u8 {
return self as u8;
}
/// Resolves an exact wire discriminant.
pub fn from_wire_value(value: u8) -> std::result::Result<Self, crate::DcMetadataSpmModelError> {
match value {
0 => return std::result::Result::Ok(Self::Empty),
1 => return std::result::Result::Ok(Self::Buffer),
2 => return std::result::Result::Ok(Self::Metadata),
_ => {
return std::result::Result::Err(
crate::DcMetadataSpmModelError::UnknownAccountDiscriminator { value },
);
},
}
}
}
/// Encoding declared for metadata content.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
#[repr(u8)]
#[serde(rename_all = "snake_case")]
pub enum DcMetadataSpmEncoding {
/// No content encoding is declared.
None = 0,
/// UTF-8 content encoding.
Utf8 = 1,
/// Base58 content encoding.
Base58 = 2,
/// Base64 content encoding.
Base64 = 3,
}
impl DcMetadataSpmEncoding {
/// Returns the exact wire discriminant.
pub const fn wire_value(self) -> u8 {
return self as u8;
}
/// Resolves an exact wire discriminant.
pub fn from_wire_value(value: u8) -> std::result::Result<Self, crate::DcMetadataSpmModelError> {
match value {
0 => return std::result::Result::Ok(Self::None),
1 => return std::result::Result::Ok(Self::Utf8),
2 => return std::result::Result::Ok(Self::Base58),
3 => return std::result::Result::Ok(Self::Base64),
_ => {
return std::result::Result::Err(crate::DcMetadataSpmModelError::UnknownEncoding {
value,
});
},
}
}
}
/// Compression declared for metadata content.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
#[repr(u8)]
#[serde(rename_all = "snake_case")]
pub enum DcMetadataSpmCompression {
/// No content compression is declared.
None = 0,
/// Gzip content compression.
Gzip = 1,
/// Zlib content compression.
Zlib = 2,
}
impl DcMetadataSpmCompression {
/// Returns the exact wire discriminant.
pub const fn wire_value(self) -> u8 {
return self as u8;
}
/// Resolves an exact wire discriminant.
pub fn from_wire_value(value: u8) -> std::result::Result<Self, crate::DcMetadataSpmModelError> {
match value {
0 => return std::result::Result::Ok(Self::None),
1 => return std::result::Result::Ok(Self::Gzip),
2 => return std::result::Result::Ok(Self::Zlib),
_ => {
return std::result::Result::Err(
crate::DcMetadataSpmModelError::UnknownCompression { value },
);
},
}
}
}
/// Format declared for metadata content.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
#[repr(u8)]
#[serde(rename_all = "snake_case")]
pub enum DcMetadataSpmFormat {
/// No structured content format is declared.
None = 0,
/// JSON content format.
Json = 1,
/// YAML content format.
Yaml = 2,
/// TOML content format.
Toml = 3,
}
impl DcMetadataSpmFormat {
/// Returns the exact wire discriminant.
pub const fn wire_value(self) -> u8 {
return self as u8;
}
/// Resolves an exact wire discriminant.
pub fn from_wire_value(value: u8) -> std::result::Result<Self, crate::DcMetadataSpmModelError> {
match value {
0 => return std::result::Result::Ok(Self::None),
1 => return std::result::Result::Ok(Self::Json),
2 => return std::result::Result::Ok(Self::Yaml),
3 => return std::result::Result::Ok(Self::Toml),
_ => {
return std::result::Result::Err(crate::DcMetadataSpmModelError::UnknownFormat {
value,
});
},
}
}
}
/// On-chain source used for metadata content.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
#[repr(u8)]
#[serde(rename_all = "snake_case")]
pub enum DcMetadataSpmDataSource {
/// Content bytes are stored directly after the account header.
Direct = 0,
/// A UTF-8 URL is stored after the account header.
Url = 1,
/// A fixed external-account reference is stored after the account header.
External = 2,
}
impl DcMetadataSpmDataSource {
/// Returns the exact wire discriminant.
pub const fn wire_value(self) -> u8 {
return self as u8;
}
/// Resolves an exact wire discriminant.
pub fn from_wire_value(value: u8) -> std::result::Result<Self, crate::DcMetadataSpmModelError> {
match value {
0 => return std::result::Result::Ok(Self::Direct),
1 => return std::result::Result::Ok(Self::Url),
2 => return std::result::Result::Ok(Self::External),
_ => {
return std::result::Result::Err(
crate::DcMetadataSpmModelError::UnknownDataSource { value },
);
},
}
}
}
/// Official custom error returned by the Solana Program Metadata program.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize)]
#[repr(u32)]
#[serde(rename_all = "snake_case")]
pub enum DcMetadataSpmProgramError {
/// The referenced program account is not executable.
NotExecutableAccount = 0,
/// The referenced upgradeable-program state is invalid.
InvalidProgramState = 1,
/// The referenced program-data account is invalid.
InvalidProgramDataAccount = 2,
/// The metadata account is immutable.
ImmutableMetadataAccount = 3,
/// The metadata data length is invalid for its source.
InvalidDataLength = 4,
}
impl DcMetadataSpmProgramError {
/// Returns the exact custom program error code.
pub const fn code(self) -> u32 {
return self as u32;
}
/// Resolves an exact custom program error code.
pub fn from_code(value: u32) -> std::result::Result<Self, crate::DcMetadataSpmModelError> {
match value {
0 => return std::result::Result::Ok(Self::NotExecutableAccount),
1 => return std::result::Result::Ok(Self::InvalidProgramState),
2 => return std::result::Result::Ok(Self::InvalidProgramDataAccount),
3 => return std::result::Result::Ok(Self::ImmutableMetadataAccount),
4 => return std::result::Result::Ok(Self::InvalidDataLength),
_ => {
return std::result::Result::Err(
crate::DcMetadataSpmModelError::UnknownProgramError { value },
);
},
}
}
}
/// Exact fixed-size seed carried by PDA derivations and account headers.
#[derive(
Clone, Copy, Debug, Default, Eq, Hash, PartialEq, serde::Deserialize, serde::Serialize,
)]
#[serde(transparent)]
pub struct DcMetadataSpmSeed([u8; crate::DC_METADATA_SPM_SEED_BYTES]);
impl DcMetadataSpmSeed {
/// Creates a seed from its exact fixed-size wire representation.
pub const fn from_bytes(bytes: [u8; crate::DC_METADATA_SPM_SEED_BYTES]) -> Self {
return Self(bytes);
}
/// Creates a zero-padded fixed-size seed from one UTF-8 value.
pub fn from_utf8(value: &str) -> std::result::Result<Self, crate::DcMetadataSpmModelError> {
let source = value.as_bytes();
if source.len() > crate::DC_METADATA_SPM_SEED_BYTES {
return std::result::Result::Err(crate::DcMetadataSpmModelError::SeedUtf8TooLong {
actual_bytes: source.len(),
max_bytes: crate::DC_METADATA_SPM_SEED_BYTES,
});
}
let mut bytes = [0_u8; crate::DC_METADATA_SPM_SEED_BYTES];
bytes[..source.len()].copy_from_slice(source);
return std::result::Result::Ok(Self(bytes));
}
/// Parses an exact fixed-size wire slice.
pub fn from_wire_bytes(
bytes: &[u8],
) -> std::result::Result<Self, crate::DcMetadataSpmModelError> {
if bytes.len() != crate::DC_METADATA_SPM_SEED_BYTES {
return std::result::Result::Err(crate::DcMetadataSpmModelError::InvalidSeedLength {
actual_bytes: bytes.len(),
expected_bytes: crate::DC_METADATA_SPM_SEED_BYTES,
});
}
let mut seed = [0_u8; crate::DC_METADATA_SPM_SEED_BYTES];
seed.copy_from_slice(bytes);
return std::result::Result::Ok(Self(seed));
}
/// Returns the exact fixed-size wire representation.
pub const fn as_bytes(&self) -> &[u8; crate::DC_METADATA_SPM_SEED_BYTES] {
return &self.0;
}
/// Returns the UTF-8 value after removing only terminal zero padding.
pub fn trimmed_utf8(&self) -> std::result::Result<&str, crate::DcMetadataSpmModelError> {
let mut length = self.0.len();
while length > 0 && self.0[length - 1] == 0 {
length -= 1;
}
match std::str::from_utf8(&self.0[..length]) {
std::result::Result::Ok(value) => return std::result::Result::Ok(value),
std::result::Result::Err(_) => {
return std::result::Result::Err(crate::DcMetadataSpmModelError::InvalidSeedUtf8);
},
}
}
}
/// Fixed external-account data reference used by the `External` data source.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct DcMetadataSpmExternalData {
/// Account that stores the referenced data.
pub address: solana_pubkey::Pubkey,
/// Byte offset inside the external account.
pub offset: u32,
/// Optional non-zero byte length; `None` is encoded as zero and means the whole account.
pub length: std::option::Option<std::num::NonZeroU32>,
}
impl DcMetadataSpmExternalData {
/// Creates an exact external-account reference.
pub const fn new(
address: solana_pubkey::Pubkey,
offset: u32,
length: std::option::Option<std::num::NonZeroU32>,
) -> Self {
return Self { address, offset, length };
}
/// Creates an external-account reference from a zeroable optional length.
pub fn from_zeroable_length(address: solana_pubkey::Pubkey, offset: u32, length: u32) -> Self {
return Self::new(address, offset, std::num::NonZeroU32::new(length));
}
/// Serializes the exact 40-byte little-endian wire representation.
pub fn to_wire_bytes(&self) -> [u8; crate::DC_METADATA_SPM_EXTERNAL_DATA_BYTES] {
let mut bytes = [0_u8; crate::DC_METADATA_SPM_EXTERNAL_DATA_BYTES];
bytes[..32].copy_from_slice(&self.address.to_bytes());
bytes[32..36].copy_from_slice(&self.offset.to_le_bytes());
let length = match self.length {
std::option::Option::Some(value) => value.get(),
std::option::Option::None => 0,
};
bytes[36..40].copy_from_slice(&length.to_le_bytes());
return bytes;
}
/// Parses the exact 40-byte little-endian wire representation.
pub fn from_wire_bytes(
bytes: &[u8],
) -> std::result::Result<Self, crate::DcMetadataSpmModelError> {
if bytes.len() != crate::DC_METADATA_SPM_EXTERNAL_DATA_BYTES {
return std::result::Result::Err(
crate::DcMetadataSpmModelError::InvalidExternalDataLength {
actual_bytes: bytes.len(),
expected_bytes: crate::DC_METADATA_SPM_EXTERNAL_DATA_BYTES,
},
);
}
let mut address = [0_u8; 32];
address.copy_from_slice(&bytes[..32]);
let mut offset = [0_u8; 4];
offset.copy_from_slice(&bytes[32..36]);
let mut length = [0_u8; 4];
length.copy_from_slice(&bytes[36..40]);
return std::result::Result::Ok(Self::from_zeroable_length(
solana_pubkey::Pubkey::new_from_array(address),
u32::from_le_bytes(offset),
u32::from_le_bytes(length),
));
}
}
/// Typed on-chain metadata content without off-chain resolution.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(tag = "source", content = "value", rename_all = "snake_case")]
pub enum DcMetadataSpmData {
/// Raw content stored directly in the metadata account.
Direct(std::vec::Vec<u8>),
/// UTF-8 URL stored directly in the metadata account.
Url(String),
/// Fixed reference to data stored in another account.
External(crate::DcMetadataSpmExternalData),
}
impl DcMetadataSpmData {
/// Returns the source discriminator associated with this content.
pub const fn source(&self) -> crate::DcMetadataSpmDataSource {
match self {
Self::Direct(_) => return crate::DcMetadataSpmDataSource::Direct,
Self::Url(_) => return crate::DcMetadataSpmDataSource::Url,
Self::External(_) => return crate::DcMetadataSpmDataSource::External,
}
}
/// Validates source-specific length requirements and the `u32` header bound.
pub fn validate(&self) -> std::result::Result<(), crate::DcMetadataSpmModelError> {
let actual_bytes = self.wire_byte_len();
match self {
Self::Direct(_) | Self::Url(_) if actual_bytes == 0 => {
return std::result::Result::Err(crate::DcMetadataSpmModelError::EmptyData {
source: self.source(),
});
},
Self::Direct(_) | Self::Url(_) | Self::External(_) => {},
}
if u32::try_from(actual_bytes).is_err() {
return std::result::Result::Err(
crate::DcMetadataSpmModelError::DataLengthExceedsU32 { actual_bytes },
);
}
return std::result::Result::Ok(());
}
/// Returns the exact number of bytes represented on wire.
pub fn wire_byte_len(&self) -> usize {
match self {
Self::Direct(value) => return value.len(),
Self::Url(value) => return value.len(),
Self::External(_) => return crate::DC_METADATA_SPM_EXTERNAL_DATA_BYTES,
}
}
/// Returns the exact little-endian header length after validation.
pub fn wire_length_u32(&self) -> std::result::Result<u32, crate::DcMetadataSpmModelError> {
match self.validate() {
std::result::Result::Ok(()) => {},
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
let actual_bytes = self.wire_byte_len();
match u32::try_from(actual_bytes) {
std::result::Result::Ok(value) => return std::result::Result::Ok(value),
std::result::Result::Err(_) => {
return std::result::Result::Err(
crate::DcMetadataSpmModelError::DataLengthExceedsU32 { actual_bytes },
);
},
}
}
/// Serializes the exact content bytes without fetching URL or external-account data.
pub fn to_wire_bytes(
&self,
) -> std::result::Result<std::vec::Vec<u8>, crate::DcMetadataSpmModelError> {
match self.validate() {
std::result::Result::Ok(()) => {},
std::result::Result::Err(error) => return std::result::Result::Err(error),
}
match self {
Self::Direct(value) => return std::result::Result::Ok(value.clone()),
Self::Url(value) => {
return std::result::Result::Ok(value.as_bytes().to_vec());
},
Self::External(value) => {
return std::result::Result::Ok(value.to_wire_bytes().to_vec());
},
}
}
}
/// Bounded error for Solana Program Metadata public model conversions.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum DcMetadataSpmModelError {
/// An account discriminator is outside the published closed enum.
UnknownAccountDiscriminator {
/// Rejected wire value.
value: u8,
},
/// An encoding discriminator is outside the published closed enum.
UnknownEncoding {
/// Rejected wire value.
value: u8,
},
/// A compression discriminator is outside the published closed enum.
UnknownCompression {
/// Rejected wire value.
value: u8,
},
/// A format discriminator is outside the published closed enum.
UnknownFormat {
/// Rejected wire value.
value: u8,
},
/// A data-source discriminator is outside the published closed enum.
UnknownDataSource {
/// Rejected wire value.
value: u8,
},
/// A custom program error code is outside the published closed enum.
UnknownProgramError {
/// Rejected custom error code.
value: u32,
},
/// One UTF-8 seed exceeds the exact fixed-size wire bound.
SeedUtf8TooLong {
/// Actual UTF-8 byte length.
actual_bytes: usize,
/// Maximum accepted UTF-8 byte length.
max_bytes: usize,
},
/// One seed slice does not have the exact fixed-size wire length.
InvalidSeedLength {
/// Actual slice length.
actual_bytes: usize,
/// Required fixed length.
expected_bytes: usize,
},
/// The non-padding seed bytes are not valid UTF-8.
InvalidSeedUtf8,
/// One external-data slice does not have the exact fixed-size wire length.
InvalidExternalDataLength {
/// Actual slice length.
actual_bytes: usize,
/// Required fixed length.
expected_bytes: usize,
},
/// Direct or URL content is empty.
EmptyData {
/// Source whose data is empty.
source: crate::DcMetadataSpmDataSource,
},
/// Content cannot be represented by the `u32` length stored in the account header.
DataLengthExceedsU32 {
/// Actual content byte length.
actual_bytes: usize,
},
}
impl std::fmt::Display for DcMetadataSpmModelError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UnknownAccountDiscriminator { value } => {
return write!(formatter, "unknown account discriminator {value}");
},
Self::UnknownEncoding { value } => {
return write!(formatter, "unknown encoding discriminator {value}");
},
Self::UnknownCompression { value } => {
return write!(formatter, "unknown compression discriminator {value}");
},
Self::UnknownFormat { value } => {
return write!(formatter, "unknown format discriminator {value}");
},
Self::UnknownDataSource { value } => {
return write!(formatter, "unknown data-source discriminator {value}");
},
Self::UnknownProgramError { value } => {
return write!(formatter, "unknown program metadata error code {value}");
},
Self::SeedUtf8TooLong { actual_bytes, max_bytes } => {
return write!(
formatter,
"seed UTF-8 length {actual_bytes} exceeds {max_bytes} bytes",
);
},
Self::InvalidSeedLength { actual_bytes, expected_bytes } => {
return write!(
formatter,
"seed length {actual_bytes} does not equal {expected_bytes} bytes",
);
},
Self::InvalidSeedUtf8 => {
return formatter.write_str("seed contains invalid UTF-8 before terminal padding");
},
Self::InvalidExternalDataLength { actual_bytes, expected_bytes } => {
return write!(
formatter,
"external data length {actual_bytes} does not equal {expected_bytes} bytes",
);
},
Self::EmptyData { source } => {
return write!(formatter, "metadata data source {source:?} cannot be empty");
},
Self::DataLengthExceedsU32 { actual_bytes } => {
return write!(
formatter,
"metadata data length {actual_bytes} exceeds the u32 wire bound",
);
},
}
}
}
impl std::error::Error for DcMetadataSpmModelError {}
#[cfg(test)]
mod tests {
#[test]
fn published_wire_values_are_exact_and_closed() {
assert_eq!(crate::DcMetadataSpmAccountDiscriminator::Empty.wire_value(), 0);
assert_eq!(crate::DcMetadataSpmAccountDiscriminator::Buffer.wire_value(), 1);
assert_eq!(crate::DcMetadataSpmAccountDiscriminator::Metadata.wire_value(), 2);
assert_eq!(crate::DcMetadataSpmEncoding::None.wire_value(), 0);
assert_eq!(crate::DcMetadataSpmEncoding::Utf8.wire_value(), 1);
assert_eq!(crate::DcMetadataSpmEncoding::Base58.wire_value(), 2);
assert_eq!(crate::DcMetadataSpmEncoding::Base64.wire_value(), 3);
assert_eq!(crate::DcMetadataSpmCompression::None.wire_value(), 0);
assert_eq!(crate::DcMetadataSpmCompression::Gzip.wire_value(), 1);
assert_eq!(crate::DcMetadataSpmCompression::Zlib.wire_value(), 2);
assert_eq!(crate::DcMetadataSpmFormat::None.wire_value(), 0);
assert_eq!(crate::DcMetadataSpmFormat::Json.wire_value(), 1);
assert_eq!(crate::DcMetadataSpmFormat::Yaml.wire_value(), 2);
assert_eq!(crate::DcMetadataSpmFormat::Toml.wire_value(), 3);
assert_eq!(crate::DcMetadataSpmDataSource::Direct.wire_value(), 0);
assert_eq!(crate::DcMetadataSpmDataSource::Url.wire_value(), 1);
assert_eq!(crate::DcMetadataSpmDataSource::External.wire_value(), 2);
assert_eq!(crate::DcMetadataSpmProgramError::NotExecutableAccount.code(), 0);
assert_eq!(crate::DcMetadataSpmProgramError::InvalidProgramState.code(), 1);
assert_eq!(crate::DcMetadataSpmProgramError::InvalidProgramDataAccount.code(), 2);
assert_eq!(crate::DcMetadataSpmProgramError::ImmutableMetadataAccount.code(), 3);
assert_eq!(crate::DcMetadataSpmProgramError::InvalidDataLength.code(), 4);
assert!(crate::DcMetadataSpmAccountDiscriminator::from_wire_value(3).is_err());
assert!(crate::DcMetadataSpmEncoding::from_wire_value(4).is_err());
assert!(crate::DcMetadataSpmCompression::from_wire_value(3).is_err());
assert!(crate::DcMetadataSpmFormat::from_wire_value(4).is_err());
assert!(crate::DcMetadataSpmDataSource::from_wire_value(3).is_err());
assert!(crate::DcMetadataSpmProgramError::from_code(5).is_err());
}
#[test]
fn seed_preserves_exact_bytes_and_applies_the_utf8_byte_bound() {
let seed_result = crate::DcMetadataSpmSeed::from_utf8("sécurité");
assert!(seed_result.is_ok());
let seed;
if let std::result::Result::Ok(value) = seed_result {
seed = value;
} else {
return;
}
assert_eq!(seed.as_bytes().len(), crate::DC_METADATA_SPM_SEED_BYTES);
assert_eq!(seed.trimmed_utf8(), std::result::Result::Ok("sécurité"));
assert!(crate::DcMetadataSpmSeed::from_utf8("éééééééé").is_ok());
assert_eq!(
crate::DcMetadataSpmSeed::from_utf8("ééééééééé"),
std::result::Result::Err(crate::DcMetadataSpmModelError::SeedUtf8TooLong {
actual_bytes: 18,
max_bytes: crate::DC_METADATA_SPM_SEED_BYTES,
}),
);
assert!(crate::DcMetadataSpmSeed::from_wire_bytes(&[0_u8; 15]).is_err());
let invalid = crate::DcMetadataSpmSeed::from_bytes([
0xff, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8,
0_u8, 0_u8,
]);
assert_eq!(
invalid.trimmed_utf8(),
std::result::Result::Err(crate::DcMetadataSpmModelError::InvalidSeedUtf8),
);
}
#[test]
fn external_data_round_trips_the_exact_zeroable_layout() {
let address = solana_pubkey::Pubkey::new_from_array([7_u8; 32]);
let length_option = std::num::NonZeroU32::new(9);
assert!(length_option.is_some());
let length;
if let std::option::Option::Some(value) = length_option {
length = value;
} else {
return;
}
let model = crate::DcMetadataSpmExternalData::new(
address,
0x1122_3344,
std::option::Option::Some(length),
);
let bytes = model.to_wire_bytes();
assert_eq!(bytes.len(), crate::DC_METADATA_SPM_EXTERNAL_DATA_BYTES);
assert_eq!(&bytes[..32], &[7_u8; 32]);
assert_eq!(&bytes[32..36], &0x1122_3344_u32.to_le_bytes());
assert_eq!(&bytes[36..40], &9_u32.to_le_bytes());
assert_eq!(
crate::DcMetadataSpmExternalData::from_wire_bytes(&bytes),
std::result::Result::Ok(model),
);
let zeroable = crate::DcMetadataSpmExternalData::from_zeroable_length(address, 3, 0);
assert_eq!(zeroable.length, std::option::Option::None);
assert_eq!(&zeroable.to_wire_bytes()[36..40], &0_u32.to_le_bytes());
assert!(crate::DcMetadataSpmExternalData::from_wire_bytes(&bytes[..39]).is_err());
}
#[test]
fn typed_data_keeps_url_and_external_content_on_chain_only() {
let direct = crate::DcMetadataSpmData::Direct(vec![1_u8, 2_u8, 3_u8]);
assert_eq!(direct.source(), crate::DcMetadataSpmDataSource::Direct);
assert_eq!(direct.wire_length_u32(), std::result::Result::Ok(3));
assert_eq!(direct.to_wire_bytes(), std::result::Result::Ok(vec![1_u8, 2_u8, 3_u8]));
let url = crate::DcMetadataSpmData::Url("https://example.invalid/idl.json".to_string());
assert_eq!(url.source(), crate::DcMetadataSpmDataSource::Url);
assert_eq!(
url.to_wire_bytes(),
std::result::Result::Ok(b"https://example.invalid/idl.json".to_vec()),
);
let external = crate::DcMetadataSpmData::External(
crate::DcMetadataSpmExternalData::from_zeroable_length(
solana_pubkey::Pubkey::new_from_array([8_u8; 32]),
12,
20,
),
);
assert_eq!(external.source(), crate::DcMetadataSpmDataSource::External);
assert_eq!(external.wire_length_u32(), std::result::Result::Ok(40),);
assert_eq!(
external.to_wire_bytes().map(|value| return value.len()),
std::result::Result::Ok(crate::DC_METADATA_SPM_EXTERNAL_DATA_BYTES),
);
assert!(crate::DcMetadataSpmData::Direct(std::vec::Vec::new()).validate().is_err());
assert!(crate::DcMetadataSpmData::Url(String::new()).validate().is_err());
}
#[test]
fn public_json_representation_is_stable_and_typed() {
let encoding_result = serde_json::to_value(crate::DcMetadataSpmEncoding::Base64);
assert!(encoding_result.is_ok());
let encoding;
if let std::result::Result::Ok(value) = encoding_result {
encoding = value;
} else {
return;
}
assert_eq!(encoding, serde_json::json!("base64"));
let url_result = serde_json::to_value(crate::DcMetadataSpmData::Url(
"https://example.invalid/metadata.json".to_string(),
));
assert!(url_result.is_ok());
let url;
if let std::result::Result::Ok(value) = url_result {
url = value;
} else {
return;
}
assert_eq!(
url,
serde_json::json!({
"source": "url",
"value": "https://example.invalid/metadata.json",
}),
);
let seed_result = crate::DcMetadataSpmSeed::from_utf8("idl");
assert!(seed_result.is_ok());
let seed;
if let std::result::Result::Ok(value) = seed_result {
seed = value;
} else {
return;
}
let json_result = serde_json::to_value(seed);
assert!(json_result.is_ok());
let json;
if let std::result::Result::Ok(value) = json_result {
json = value;
} else {
return;
}
assert_eq!(json.as_array().map(std::vec::Vec::len), std::option::Option::Some(16));
}
#[test]
fn published_fixed_sizes_match_the_audited_contract() {
assert_eq!(crate::DC_METADATA_SPM_SEED_BYTES, 16);
assert_eq!(crate::DC_METADATA_SPM_BUFFER_HEADER_BYTES, 96);
assert_eq!(crate::DC_METADATA_SPM_METADATA_HEADER_BYTES, 96);
assert_eq!(crate::DC_METADATA_SPM_EXTERNAL_DATA_BYTES, 40);
}
}

View File

@@ -1,5 +1,5 @@
// file: kb-lib/src/lib.rs // file: kb-lib/src/lib.rs
// version: 29 // version: 30
//! Consolidated decoder, executor, materializer and shared model library. //! Consolidated decoder, executor, materializer and shared model library.
#![warn(missing_docs)] #![warn(missing_docs)]
@@ -15,6 +15,14 @@ mod model;
pub use self::decoder::DC_API_CORE_INSTRUCTION_INPUT_CONTRACT_VERSION; pub use self::decoder::DC_API_CORE_INSTRUCTION_INPUT_CONTRACT_VERSION;
/// Exhaustive published Metaplex account-key inventory. /// Exhaustive published Metaplex account-key inventory.
pub use self::decoder::DC_METADATA_MTM_METADATA_ACCOUNT_LAYOUT_AUDIT; pub use self::decoder::DC_METADATA_MTM_METADATA_ACCOUNT_LAYOUT_AUDIT;
/// Exposes the exact fixed byte length of one Buffer account header.
pub use self::decoder::DC_METADATA_SPM_BUFFER_HEADER_BYTES;
/// Exposes the exact byte length of one external-account data reference.
pub use self::decoder::DC_METADATA_SPM_EXTERNAL_DATA_BYTES;
/// Exposes the exact fixed byte length of one Metadata account header.
pub use self::decoder::DC_METADATA_SPM_METADATA_HEADER_BYTES;
/// Exposes the exact byte length of one Solana Program Metadata seed.
pub use self::decoder::DC_METADATA_SPM_SEED_BYTES;
/// Exact byte length of one SPL ElGamal registry account. /// Exact byte length of one SPL ElGamal registry account.
pub use self::decoder::DC_SPL_ELGAMAL_REGISTRY_ACCOUNT_LEN; pub use self::decoder::DC_SPL_ELGAMAL_REGISTRY_ACCOUNT_LEN;
/// Reserved `kb-lib.decoder.adapter.saber_decimal_wrapper` decoder. /// Reserved `kb-lib.decoder.adapter.saber_decimal_wrapper` decoder.
@@ -226,6 +234,26 @@ pub use self::decoder::DcMetadataMtmTokenRecordAccountSnapshot;
pub use self::decoder::DcMetadataSolanaProgramMetadataDecoder; pub use self::decoder::DcMetadataSolanaProgramMetadataDecoder;
/// Reserved `kb-lib.decoder.metadata.spl_name_service` decoder. /// Reserved `kb-lib.decoder.metadata.spl_name_service` decoder.
pub use self::decoder::DcMetadataSplNameServiceDecoder; pub use self::decoder::DcMetadataSplNameServiceDecoder;
/// Solana Program Metadata account discriminator.
pub use self::decoder::DcMetadataSpmAccountDiscriminator;
/// Compression declared for Solana Program Metadata content.
pub use self::decoder::DcMetadataSpmCompression;
/// Typed Solana Program Metadata content without off-chain resolution.
pub use self::decoder::DcMetadataSpmData;
/// On-chain source used for Solana Program Metadata content.
pub use self::decoder::DcMetadataSpmDataSource;
/// Encoding declared for Solana Program Metadata content.
pub use self::decoder::DcMetadataSpmEncoding;
/// Fixed external-account data reference.
pub use self::decoder::DcMetadataSpmExternalData;
/// Format declared for Solana Program Metadata content.
pub use self::decoder::DcMetadataSpmFormat;
/// Bounded Solana Program Metadata model conversion error.
pub use self::decoder::DcMetadataSpmModelError;
/// Official Solana Program Metadata custom error.
pub use self::decoder::DcMetadataSpmProgramError;
/// Exact fixed-size Solana Program Metadata seed.
pub use self::decoder::DcMetadataSpmSeed;
/// Reserved `kb-lib.decoder.nft.metaplex_bubblegum` decoder. /// Reserved `kb-lib.decoder.nft.metaplex_bubblegum` decoder.
pub use self::decoder::DcNftMetaplexBubblegumDecoder; pub use self::decoder::DcNftMetaplexBubblegumDecoder;
/// Exposes the reserved `DcNftTensorCnftDecoder` implementation. /// Exposes the reserved `DcNftTensorCnftDecoder` implementation.

View File

@@ -0,0 +1,30 @@
// file: kb-lib/tests/external_solana_program_metadata_model_api.rs
// version: 1
//! Downstream-style compilation contract for Solana Program Metadata models.
#[test]
fn external_consumers_use_only_crate_root_model_exports() {
let seed_result = kb_lib::DcMetadataSpmSeed::from_utf8("idl");
assert!(seed_result.is_ok());
let seed;
if let std::result::Result::Ok(value) = seed_result {
seed = value;
} else {
return;
}
assert_eq!(seed.trimmed_utf8(), std::result::Result::Ok("idl"));
assert_eq!(seed.as_bytes().len(), kb_lib::DC_METADATA_SPM_SEED_BYTES);
let external = kb_lib::DcMetadataSpmExternalData::from_zeroable_length(
solana_pubkey::Pubkey::new_from_array([9_u8; 32]),
4,
8,
);
let data = kb_lib::DcMetadataSpmData::External(external);
assert_eq!(data.source(), kb_lib::DcMetadataSpmDataSource::External);
assert_eq!(data.wire_length_u32(), std::result::Result::Ok(40),);
assert_eq!(kb_lib::DcMetadataSpmProgramError::InvalidDataLength.code(), 4);
assert_eq!(kb_lib::DC_METADATA_SPM_BUFFER_HEADER_BYTES, 96);
assert_eq!(kb_lib::DC_METADATA_SPM_METADATA_HEADER_BYTES, 96);
assert_eq!(kb_lib::DC_METADATA_SPM_EXTERNAL_DATA_BYTES, 40);
}