From 4c13d7ac544e9cd9956d177562e3d756f0eece23 Mon Sep 17 00:00:00 2001 From: SinuS Von SifriduS Date: Wed, 5 Aug 2026 17:46:56 +0200 Subject: [PATCH] v0.4.8-pre.004 --- Cargo.toml | 4 +- docs/IDL_TO_KB_LIB_NOMENCLATURE.md | 188 +-- ...TADATA_AND_TOKEN_2022_COMPLETENESS_PLAN.md | 38 +- kb-lib/CHANGELOG.md | 13 +- kb-lib/README.md | 13 +- kb-lib/USAGE.md | 36 +- kb-lib/src/decoder.rs | 20 +- kb-lib/src/decoder/metadata.rs | 20 +- .../metadata/solana_program_metadata.rs | 23 +- .../solana_program_metadata/account.rs | 1121 +++++++++++++++++ .../solana_program_metadata/constants.rs | 4 +- .../metadata/solana_program_metadata/model.rs | 2 +- kb-lib/src/lib.rs | 20 +- ...nal_solana_program_metadata_account_api.rs | 35 + ...OLANA_PROGRAM_METADATA_ACCOUNT_MATRIX.json | 96 ++ 15 files changed, 1505 insertions(+), 128 deletions(-) create mode 100644 kb-lib/src/decoder/metadata/solana_program_metadata/account.rs create mode 100644 kb-lib/tests/external_solana_program_metadata_account_api.rs create mode 100644 test-fixtures/contract-matrices/SOLANA_PROGRAM_METADATA_ACCOUNT_MATRIX.json diff --git a/Cargo.toml b/Cargo.toml index 1277e88..ef5a83d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ # file: Cargo.toml -# version: 25 +# version: 26 [workspace] resolver = "3" @@ -18,7 +18,7 @@ members = [ ] [workspace.package] -version = "0.4.8-pre.3" +version = "0.4.8-pre.4" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/khadhroony-bot3" diff --git a/docs/IDL_TO_KB_LIB_NOMENCLATURE.md b/docs/IDL_TO_KB_LIB_NOMENCLATURE.md index 2b539b4..691d1f3 100644 --- a/docs/IDL_TO_KB_LIB_NOMENCLATURE.md +++ b/docs/IDL_TO_KB_LIB_NOMENCLATURE.md @@ -1,101 +1,101 @@ - + # Correspondance IDL ↔ `kb-lib` Ce tableau constitue la cible de classification pour les futurs décodeurs et exécuteurs. Il ne signifie pas que tous les modules proposés sont déjà implémentés. -| 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.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.AMMJdEiCCa8mdugg6JPF7gFirmmxisTfDJoSNSUi5zDJ.metadao_v0_5.V0_5_0.from_solscan.json` | `amm/metadao_v0_5` | `amm/metadao_v0_5` | Aucune | -| `amm.BSwp6bEBihVLdqJRKGgzjcGLHkcTuzmSo1TQkHepzH8p.bonk_swap.V0_1_1.from_solscan.json` | `amm/bonk_swap` | `amm/bonk_swap` | Aucune | -| `amm.Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB.meteora_damm_v1.V0_5_2.from_solscan.json` | `amm/meteora_damm_v1` | `amm/meteora_damm_v1` | Aucune | -| `amm.GAMMA7meSFWaBXF25oSUgmGRwaW6sCMFLmBNiMSdbHVT.goosefx_gamma.V0_2_0.from_solscan.json` | `amm/goosefx_gamma` | `amm/goosefx_gamma` | Aucune | -| `amm.GFXsSL5sSaDfNFQUYsHekbWBW1TsFdjDYzACh62tEHxn.goosefx_v2.V0_3_0.from_solscan.json` | `amm/goosefx_v2` | `amm/goosefx_v2` | Aucune | -| `amm.Gswppe6ERWKpUTXvRPfXdzHhiCyJvLadVvXGfdpBqcE1.guac_swap.V0_1_1.from_solscan.json` | `amm/guac_swap` | `amm/guac_swap` | Aucune | -| `amm.WooFif76YGRNjk1pA8wCsN67aQsD9f9iLsz4NcJ1AVb.woofi.V0_1_0.from_solscan.json` | `amm/woofi` | `amm/woofi` | Aucune | -| `amm.cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG.meteora_damm_v2.V0_2_0.from_solscan.json` | `amm/meteora_damm_v2` | `amm/meteora_damm_v2` | Aucune | -| `amm.pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA.pump_swap.V0_1_0.from_solscan.json` | `amm/pump_swap` | `amm/pump_swap` | Aucune | -| `amm.vrTGoBuy5rYSxAfV3jaRJWHH6nN9WK4NRExGxsk1bCJ.vertigo.V0_1_0.from_solscan.json` | `amm/vertigo` | `amm/vertigo` | Aucune | -| `bridge.6doghB248px58JSSwG4qejQ46kFMW4AMj7vzJnWZHNZn.layer_zero_executor.V0_1_0.from_solscan.json` | `bridge/layer_zero_executor` | `bridge/layer_zero_executor` | Aucune | -| `bridge.76y77prsiCMvXMjuoZ5VRrhG5qYBrUMYTE5WgHqgjEn6.layer_zero_endpoint.V0_1_0.from_solscan.json` | `bridge/layer_zero_endpoint` | `bridge/layer_zero_endpoint` | Aucune | -| `bridge.CCTPV2vPZJS2u2BBsUoscuikbYjnpFmbFsvVuJdgUMQe.circle_cctp_token_messenger_minter_v2.V0_2_0.from_solscan.json` | `bridge/circle_cctp_token_messenger_minter_v2` | `bridge/circle_cctp_token_messenger_minter_v2` | Aucune | -| `bridge.CCTPiPYPc6AsJuwueEnWgSgucamXDZwBd53dQ11YiKX3.circle_cctp_token_messenger_minter.V0_1_0.from_solscan.json` | `bridge/circle_cctp_token_messenger_minter` | `bridge/circle_cctp_token_messenger_minter` | Aucune | -| `clmm.6dMXqGZ3ga2dikrYS9ovDXgHGh5RUsb2RTUj6hrQXhk6.stabble.V0_1_0.from_solscan.json` | `clmm/stabble` | `clmm/stabble` | Aucune | -| `clmm.CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK.raydium.V0_1_0.from_solscan.json` | `clmm/raydium` | `clmm/raydium` | Aucune | -| `clmm.HpNfyc2Saw7RKkQd8nEL4khUcuPhQ7WwY1B2qjx8jxFq.pancake_swap.V0_1_0.from_solscan.json` | `clmm/pancake_swap` | `clmm/pancake_swap` | Aucune | -| `clmm.REALQqNEomY6cQGZJUGwywTBD2UmDT32rZcNnfxQ5N2.byreal.V0_1_0.from_solscan.json` | `clmm/byreal` | `clmm/byreal` | Aucune | -| `clmm.fUSioN9YKKSa3CUC2YUc4tPkHJ5Y6XW1yz8y6F7qWz9.fusion.V1_1_4.from_solscan.json` | `clmm/fusion` | `clmm/fusion` | Aucune | -| `clmm.whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc.orca_whirlpool.V0_9_0.from_solscan.json` | `clmm/orca_whirlpool` | `clmm/orca_whirlpool` | Aucune | -| `cpmm.CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C.raydium.V0_2_0.from_solscan.json` | `cpmm/raydium` | `cpmm/raydium` | Aucune | -| `dlmm.LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo.meteora.V0_12_0.from_solscan.json` | `dlmm/meteora` | `dlmm/meteora` | Aucune | -| `fees.pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ.pump_fees.V0_1_0.from_solscan.json` | `admin/pump_fees` | `fees/pump_fees` | Déplacer `admin/pump_fees` → `fees/pump_fees` | -| `governance.FUTARELBfJfQ8RDGhg1wdhddq1odMAJUePHFuBYfUxKq.metadao_futarchy.V0_6_1.from_solscan.json` | `amm/metadao_futarchy_amm` | `governance/metadao_futarchy` | Déplacer `amm/metadao_futarchy_amm` → `governance/metadao_futarchy` | -| `governance.SQDS4ep65T869zMMBKyuUq6aD6EgTu8psMjkvj52pCf.squads_multisig.V2_0_0.from_solscan.json` | `—` | `governance/squads_multisig` | Créer `governance/squads_multisig` | -| `governance.WALL8ucBuUyL46QYxwYJjidaFYhdvxUFrgvBxPshERx.metadao_bid_wall.V0_7_0.from_solscan.json` | `governance/metadao_bid_wall` | `governance/metadao_bid_wall` | Aucune | -| `launchpad.5U3EU2ubXtK84QcRjWVmYt9RaDyA8gKxdUrPFXmZyaki.virtuals.V0_1_0.from_solscan.json` | `amm/virtuals` | `launchpad/virtuals` | Déplacer `amm/virtuals` → `launchpad/virtuals` | -| `launchpad.6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P.pump_fun.V0_1_0.from_solscan.json` | `launchpad/pump_fun` | `launchpad/pump_fun` | Aucune | -| `launchpad.LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj.raydium_launchlab.V0_2_0.from_solscan.json` | `launchpad/raydium_launchlab` | `launchpad/raydium_launchlab` | Aucune | -| `launchpad.MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG.moonit.V0_1_0.from_solscan.json` | `launchpad/moonit` | `launchpad/moonit` | Aucune | -| `launchpad.PdMDrKEMaX8q7CCJb7NvUCxerBCcsFUa4LjBEynTtEd.pump_pumpup_ai.V0_1_0.from_solscan.json` | `launchpad/pump_pumpup_ai` | `launchpad/pump_pumpup_ai` | Aucune | -| `launchpad.T8HsGYv7sMk3kTnyaRqZrbRPuntYzdh12evXBkprint.printr.V1_0_0.from_solscan.json` | `launchpad/printr` | `launchpad/printr` | Aucune | -| `launchpad.boop8hVGQGqehUK2iVEMEnMrL5RbjywRzHKBmBE7ry4.boop_fun.V0_3_0.from_solscan.json` | `launchpad/boop_fun` | `launchpad/boop_fun` | Aucune | -| `launchpad.dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN.meteora_dbc.V0_1_10.from_solscan.json` | `launchpad/meteora_dbc` | `launchpad/meteora_dbc` | Aucune | -| `launchpad.moontUzsdepotRGe5xsfip7vLPTJnVuafqdUWexVnPM.metadao_ico.V0_7_0.from_solscan.json` | `launchpad/metadao_ico` | `launchpad/metadao_ico` | Aucune | -| `launchpad.waveQX2yP3H1pVU8djGvEHmYg8uamQ84AuyGtpsrXTF.orca_wavebreak.V1_1_5.from_solscan.json` | `launchpad/orca_wavebreak` | `launchpad/orca_wavebreak` | Aucune | -| `lending.C1onEW2kPetmHmwe74YC1ESx3LnFEpVau6g2pg4fHycr.clone.V0_1_0.from_solscan.json` | `lending/clone` | `lending/clone` | 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 | -| `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` | Modèles wire présents ; implémenter le décodeur et l’exécuteur réels | -| `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.TCMPhJdwDryooaGtiocG1u3xcYbRpiJzb283XfCZsDp.tensor_cnft.V2_0_1.from_solscan.json` | `—` | `nft/tensor_cnft` | Créer `nft/tensor_cnft` | -| `orderbook.j1o2qRpjcyUwEvwtcfhEQefh773ZgjxcVRry7LDqg5X.jupiter_limit_order_v2.V0_1_0.from_solscan.json` | `orderbook/jupiter_limit_order_v2` | `orderbook/jupiter_limit_order_v2` | Aucune | -| `orderbook.jupoNjAxXgZ4rjzxzPMP4oxduvQsQtZzyknqvzYNrNu.jupiter_limit_order.V0_1_0.from_solscan.json` | `orderbook/jupiter_limit_order` | `orderbook/jupiter_limit_order` | Aucune | -| `orderbook.opnb2LAfJYbRMAHHvqjCwQxanZn7ReEHp1k81EohpZb.openbook_v2.V0_1_0.from_solscan.json` | `orderbook/openbook_v2` | `orderbook/openbook_v2` | Aucune | -| `perpetuals.EtrnLzgbS7nMMy5fbD42kXiUzGg8XQzJ972Xtk1cjWih.phoenix_eternal.V0_2_0.from_solscan.json` | `—` | `perpetuals/phoenix_eternal` | Créer `perpetuals/phoenix_eternal` | -| `perpetuals.PERPHjGBqRHArX4DySjwM6UJHiR3sWAatqfdBS2qQJu.jupiter.V0_1_0.from_solscan.json` | `perpetuals/jupiter` | `perpetuals/jupiter` | Aucune | -| `perpetuals.ZETAxsqBRek56DhiGXrn75yj2NHU3aYUnxvHXpkf3aD.zeta.V0_1_0.from_solscan.json` | `perpetuals/zeta` | `perpetuals/zeta` | Aucune | -| `perpetuals.dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH.drift_v2.V2_150_0.from_solscan.json` | `perpetuals/drift_v2` | `perpetuals/drift_v2` | Aucune | -| `router.6m2CDdhRgxpH4WjvdzxAYbGxwdGUz5MziiL5jek2kBma.okx_labs_v1.V0_1_0.from_solscan.json` | `router/okx_labs_v1` | `router/okx_labs_v1` | Aucune | -| `router.DF1ow4tspfHX9JwWJsAb9epbkA8hmpSEAtxXy1V27QBH.dflow_aggregator_v4.V0_1_0.from_solscan.json` | `router/dflow_aggregator_v4` | `router/dflow_aggregator_v4` | Aucune | -| `router.JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB.jupiter_aggregator_v4.V0_1_0.from_solscan.json` | `router/jupiter_aggregator_v4` | `router/jupiter_aggregator_v4` | Aucune | -| `router.JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4.jupiter_aggregator_v6.V0_1_0.from_solscan.json` | `router/jupiter_aggregator_v6` | `router/jupiter_aggregator_v6` | Aucune | -| `router.proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u.okx_labs_v2.V0_1_0.from_solscan.json` | `router/okx_labs_v2` | `router/okx_labs_v2` | Aucune | -| `rwa.XzTT4XB8m7sLD2xi6snefSasaswsKCxx5Tifjondogm.ondo_global_markets.V0_1_0.from_solscan.json` | `rwa/ondo_global_markets` | `rwa/ondo_global_markets` | Aucune | -| `solana.11111111111111111111111111111111.core.system.V0_0_1.from_github_solana_program.json` | `solana/core/system` | `solana/core/system` | Aucune | -| `solana.AddressLookupTab1e1111111111111111111111111.core.address_lookup_table.V0_0_1.from_github_solana_program.json` | `solana/core/address_lookup_table` | `solana/core/address_lookup_table` | Aucune | -| `solana.BPFLoaderUpgradeab1e11111111111111111111111.core.loader_v3.V0_0_0.from_github_solana_program.json` | `solana/core/loader_v3` | `solana/core/loader_v3` | Aucune | -| `solana.ComputeBudget111111111111111111111111111111.core.compute_budget.V0_0_1.from_github_solana_program.json` | `solana/core/compute_budget` | `solana/core/compute_budget` | Aucune | -| `solana.Config1111111111111111111111111111111111111.core.config.V0_0_1.from_github_solana_program.json` | `solana/core/config` | `solana/core/config` | Aucune | -| `solana.Feature111111111111111111111111111111111111.core.feature.V0_0_1.from_github_solana_program.json` | `solana/core/feature` | `solana/core/feature` | Aucune | -| `solana.LoaderV411111111111111111111111111111111111.core.loader_v4.V0_0_0.from_github_solana_program.json` | `solana/core/loader_v4` | `solana/core/loader_v4` | Aucune | -| `solana.Stake11111111111111111111111111111111111111.core.stake.V4_3_1.from_github_solana_program.json` | `solana/core/stake` | `solana/core/stake` | Aucune | -| `spl.MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr.memo.v3.V3_0_1.from_github_solana_program.json` | `spl/memo/v3` | `spl/memo/v3` | Aucune | -| `spl.TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA.token.V3_3_0.from_github_solana_program.json` | `spl/token` | `spl/token` | Aucune | -| `spl.TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb.token_2022.V3_0_1.from_github_solana_program.json` | `spl/token_2022` | `spl/token_2022` | Aucune | -| `stable.HYEXCHtHkBagdStcJCp3xbbb9B7sdMdWXFNj6mdsG4hn.swap_hylo_exchange.V0_1_4.from_solscan.json` | `stable/swap_hylo_exchange` | `stable/swap_hylo_exchange` | Aucune | -| `stable.JUPUSDecMzAVgztLe6eGhwUBj1Pn3j9WAXwmtHmfbRr.swap_jupiter_stable.V0_1_0.from_solscan.json` | `stable/swap_jupiter_stable` | `stable/swap_jupiter_stable` | Aucune | -| `stable.NUMERUNsFCP3kuNmWZuXtm1AaQCPj9uw6Guv2Ekoi5P.swap_numeraire.V0_1_0.from_solscan.json` | `stable/swap_numeraire` | `stable/swap_numeraire` | Aucune | -| `stable.swapNyd8XiQwJ6ianp9snpu4brUqFxadzvHebnAXjJZ.swap_stabble.V1_5_0.from_solscan.json` | `stable/swap_stabble` | `stable/swap_stabble` | Aucune | -| `staking.4R3gSG8BpU4t19KYj8CfnbtRpnT8gtk4dvTHxVRwc2r7.jito_tip_distribution.V0_1_5.from_solscan.json` | `—` | `staking/jito_tip_distribution` | Créer `staking/jito_tip_distribution` | -| `staking.FarmsPZpWu9i7Kky8tPN37rs2TpmMrAZrC7S7vJa91Hr.kamino_farm.V1_6_5.from_solscan.json` | `staking/kamino_farm` | `staking/kamino_farm` | Aucune | -| `staking.MarBmsSgKXdrN1egZf5sqe1TMai9K1rChYNDJgjq7aD.marinade_finance.V0_1_0.from_solscan.json` | `staking/marinade_finance` | `staking/marinade_finance` | Aucune | -| `storage.recr1L3PCGKLbckBqMNcJhuuyU1zgo8nBhfLVsJNwr5.solana_record.V0_4_0.from_github_solana_program.json` | `—` | `storage/solana_record` | Créer `storage/solana_record` | -| `strategy.DCA265Vj8a9CEuX1eb1LWRnDT7uK6q1xMipnNyatn23M.jupiter_dca.V0_1_0.from_solscan.json` | `strategy/jupiter_dca` | `strategy/jupiter_dca` | Déplacer `strategy/jupiter_dca` → `strategy/jupiter_dca` | -| `treasury.treaf4wWBBty3fHdyBpo35Mz84M8k3heKXmjmi9vFt5.helium_treasury_management.V0_2_3.from_solscan.json` | `treasury/helium_treasury_management` | `treasury/helium_treasury_management` | Aucune | -| `vault.24Uqj9JCLxUeoC3hGfh5W3s9FM9uCHDS2SG3LYwBpyTi.meteora.V0_7_2.from_solscan.json` | `vault/meteora` | `vault/meteora` | Aucune | -| `vault.6LtLpnUFNByNXLyCoK9wA2MykKAmQNZKBdY8s47dehDc.kamino_yvaults.V0_1_0.from_solscan.json` | `vault/kamino_yvaults` | `vault/kamino_yvaults` | Aucune | -| `vault.CarrotwivhMpDnm27EHmRLeQ683Z1PufuqEmBZvD282s.carrot_defi.V0_1_0.from_solscan.json` | `vault/carrot_defi` | `vault/carrot_defi` | Aucune | -| `vault.HysTabVUfmQBFcmzu1ctRd1Y1fxd66RBpboy1bmtDSQQ.hylo_stability_pool.V0_1_4.from_solscan.json` | `vault/hylo_stability_pool` | `vault/hylo_stability_pool` | Aucune | -| `vault.KvauGMspG5k6rtzrqqn7WNn3oZdyKqLKwK2XWQ8FLjd.kamino_v2.V2_1_1.from_solscan.json` | `vault/kamino_v2` | `vault/kamino_v2` | Aucune | -| `vault.kvauTFR8qm1dhniz6pYuBZkuene3Hfrs1VQhVRgCNrr.kamino.V0_1_0.from_solscan.json` | `vault/kamino` | `vault/kamino` | Aucune | -| `vesting.LocpQgucEQHbqNABEYvBvwoxCPsSbG91A1QaQhQQqjn.jupiter_lock.V0_4_0.from_solscan.json` | `admin/jupiter_lock` | `vesting/jupiter_lock` | Déplacer `admin/jupiter_lock` → `vesting/jupiter_lock` | -| `vesting.strmRqUCoQUgGUan5YhzUZa6KqdzwX5L6FpUxfmKg5m.streamflow.V0_1_0.from_solscan.json` | `vesting/streamflow` | `vesting/streamflow` | Aucune | -| `wallet.JSW99DKmxNyREQM14SQLDykeBvEUG63TeohrvmofEiw.jupiter_apepro_smart_wallet.V0_1_0.from_solscan.json` | `wallet/jupiter_apepro_smart_wallet` | `wallet/jupiter_apepro_smart_wallet` | Aucune | -| `weighted.swapFpHZwjELNnjvThjajtiVmkz3yPQEHjLtka2fwHW.swap_stabble.V1_5_0.from_solscan.json` | `weighted/swap_stabble` | `weighted/swap_stabble` | Aucune | +| 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.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.AMMJdEiCCa8mdugg6JPF7gFirmmxisTfDJoSNSUi5zDJ.metadao_v0_5.V0_5_0.from_solscan.json` | `amm/metadao_v0_5` | `amm/metadao_v0_5` | Aucune | +| `amm.BSwp6bEBihVLdqJRKGgzjcGLHkcTuzmSo1TQkHepzH8p.bonk_swap.V0_1_1.from_solscan.json` | `amm/bonk_swap` | `amm/bonk_swap` | Aucune | +| `amm.Eo7WjKq67rjJQSZxS6z3YkapzY3eMj6Xy8X5EQVn5UaB.meteora_damm_v1.V0_5_2.from_solscan.json` | `amm/meteora_damm_v1` | `amm/meteora_damm_v1` | Aucune | +| `amm.GAMMA7meSFWaBXF25oSUgmGRwaW6sCMFLmBNiMSdbHVT.goosefx_gamma.V0_2_0.from_solscan.json` | `amm/goosefx_gamma` | `amm/goosefx_gamma` | Aucune | +| `amm.GFXsSL5sSaDfNFQUYsHekbWBW1TsFdjDYzACh62tEHxn.goosefx_v2.V0_3_0.from_solscan.json` | `amm/goosefx_v2` | `amm/goosefx_v2` | Aucune | +| `amm.Gswppe6ERWKpUTXvRPfXdzHhiCyJvLadVvXGfdpBqcE1.guac_swap.V0_1_1.from_solscan.json` | `amm/guac_swap` | `amm/guac_swap` | Aucune | +| `amm.WooFif76YGRNjk1pA8wCsN67aQsD9f9iLsz4NcJ1AVb.woofi.V0_1_0.from_solscan.json` | `amm/woofi` | `amm/woofi` | Aucune | +| `amm.cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG.meteora_damm_v2.V0_2_0.from_solscan.json` | `amm/meteora_damm_v2` | `amm/meteora_damm_v2` | Aucune | +| `amm.pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA.pump_swap.V0_1_0.from_solscan.json` | `amm/pump_swap` | `amm/pump_swap` | Aucune | +| `amm.vrTGoBuy5rYSxAfV3jaRJWHH6nN9WK4NRExGxsk1bCJ.vertigo.V0_1_0.from_solscan.json` | `amm/vertigo` | `amm/vertigo` | Aucune | +| `bridge.6doghB248px58JSSwG4qejQ46kFMW4AMj7vzJnWZHNZn.layer_zero_executor.V0_1_0.from_solscan.json` | `bridge/layer_zero_executor` | `bridge/layer_zero_executor` | Aucune | +| `bridge.76y77prsiCMvXMjuoZ5VRrhG5qYBrUMYTE5WgHqgjEn6.layer_zero_endpoint.V0_1_0.from_solscan.json` | `bridge/layer_zero_endpoint` | `bridge/layer_zero_endpoint` | Aucune | +| `bridge.CCTPV2vPZJS2u2BBsUoscuikbYjnpFmbFsvVuJdgUMQe.circle_cctp_token_messenger_minter_v2.V0_2_0.from_solscan.json` | `bridge/circle_cctp_token_messenger_minter_v2` | `bridge/circle_cctp_token_messenger_minter_v2` | Aucune | +| `bridge.CCTPiPYPc6AsJuwueEnWgSgucamXDZwBd53dQ11YiKX3.circle_cctp_token_messenger_minter.V0_1_0.from_solscan.json` | `bridge/circle_cctp_token_messenger_minter` | `bridge/circle_cctp_token_messenger_minter` | Aucune | +| `clmm.6dMXqGZ3ga2dikrYS9ovDXgHGh5RUsb2RTUj6hrQXhk6.stabble.V0_1_0.from_solscan.json` | `clmm/stabble` | `clmm/stabble` | Aucune | +| `clmm.CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK.raydium.V0_1_0.from_solscan.json` | `clmm/raydium` | `clmm/raydium` | Aucune | +| `clmm.HpNfyc2Saw7RKkQd8nEL4khUcuPhQ7WwY1B2qjx8jxFq.pancake_swap.V0_1_0.from_solscan.json` | `clmm/pancake_swap` | `clmm/pancake_swap` | Aucune | +| `clmm.REALQqNEomY6cQGZJUGwywTBD2UmDT32rZcNnfxQ5N2.byreal.V0_1_0.from_solscan.json` | `clmm/byreal` | `clmm/byreal` | Aucune | +| `clmm.fUSioN9YKKSa3CUC2YUc4tPkHJ5Y6XW1yz8y6F7qWz9.fusion.V1_1_4.from_solscan.json` | `clmm/fusion` | `clmm/fusion` | Aucune | +| `clmm.whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc.orca_whirlpool.V0_9_0.from_solscan.json` | `clmm/orca_whirlpool` | `clmm/orca_whirlpool` | Aucune | +| `cpmm.CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C.raydium.V0_2_0.from_solscan.json` | `cpmm/raydium` | `cpmm/raydium` | Aucune | +| `dlmm.LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo.meteora.V0_12_0.from_solscan.json` | `dlmm/meteora` | `dlmm/meteora` | Aucune | +| `fees.pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ.pump_fees.V0_1_0.from_solscan.json` | `admin/pump_fees` | `fees/pump_fees` | Déplacer `admin/pump_fees` → `fees/pump_fees` | +| `governance.FUTARELBfJfQ8RDGhg1wdhddq1odMAJUePHFuBYfUxKq.metadao_futarchy.V0_6_1.from_solscan.json` | `amm/metadao_futarchy_amm` | `governance/metadao_futarchy` | Déplacer `amm/metadao_futarchy_amm` → `governance/metadao_futarchy` | +| `governance.SQDS4ep65T869zMMBKyuUq6aD6EgTu8psMjkvj52pCf.squads_multisig.V2_0_0.from_solscan.json` | `—` | `governance/squads_multisig` | Créer `governance/squads_multisig` | +| `governance.WALL8ucBuUyL46QYxwYJjidaFYhdvxUFrgvBxPshERx.metadao_bid_wall.V0_7_0.from_solscan.json` | `governance/metadao_bid_wall` | `governance/metadao_bid_wall` | Aucune | +| `launchpad.5U3EU2ubXtK84QcRjWVmYt9RaDyA8gKxdUrPFXmZyaki.virtuals.V0_1_0.from_solscan.json` | `amm/virtuals` | `launchpad/virtuals` | Déplacer `amm/virtuals` → `launchpad/virtuals` | +| `launchpad.6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P.pump_fun.V0_1_0.from_solscan.json` | `launchpad/pump_fun` | `launchpad/pump_fun` | Aucune | +| `launchpad.LanMV9sAd7wArD4vJFi2qDdfnVhFxYSUg6eADduJ3uj.raydium_launchlab.V0_2_0.from_solscan.json` | `launchpad/raydium_launchlab` | `launchpad/raydium_launchlab` | Aucune | +| `launchpad.MoonCVVNZFSYkqNXP6bxHLPL6QQJiMagDL3qcqUQTrG.moonit.V0_1_0.from_solscan.json` | `launchpad/moonit` | `launchpad/moonit` | Aucune | +| `launchpad.PdMDrKEMaX8q7CCJb7NvUCxerBCcsFUa4LjBEynTtEd.pump_pumpup_ai.V0_1_0.from_solscan.json` | `launchpad/pump_pumpup_ai` | `launchpad/pump_pumpup_ai` | Aucune | +| `launchpad.T8HsGYv7sMk3kTnyaRqZrbRPuntYzdh12evXBkprint.printr.V1_0_0.from_solscan.json` | `launchpad/printr` | `launchpad/printr` | Aucune | +| `launchpad.boop8hVGQGqehUK2iVEMEnMrL5RbjywRzHKBmBE7ry4.boop_fun.V0_3_0.from_solscan.json` | `launchpad/boop_fun` | `launchpad/boop_fun` | Aucune | +| `launchpad.dbcij3LWUppWqq96dh6gJWwBifmcGfLSB5D4DuSMaqN.meteora_dbc.V0_1_10.from_solscan.json` | `launchpad/meteora_dbc` | `launchpad/meteora_dbc` | Aucune | +| `launchpad.moontUzsdepotRGe5xsfip7vLPTJnVuafqdUWexVnPM.metadao_ico.V0_7_0.from_solscan.json` | `launchpad/metadao_ico` | `launchpad/metadao_ico` | Aucune | +| `launchpad.waveQX2yP3H1pVU8djGvEHmYg8uamQ84AuyGtpsrXTF.orca_wavebreak.V1_1_5.from_solscan.json` | `launchpad/orca_wavebreak` | `launchpad/orca_wavebreak` | Aucune | +| `lending.C1onEW2kPetmHmwe74YC1ESx3LnFEpVau6g2pg4fHycr.clone.V0_1_0.from_solscan.json` | `lending/clone` | `lending/clone` | 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 | +| `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` | Modèles wire et décodeurs de comptes présents ; implémenter les instructions, la matérialisation et l’exécuteur | +| `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.TCMPhJdwDryooaGtiocG1u3xcYbRpiJzb283XfCZsDp.tensor_cnft.V2_0_1.from_solscan.json` | `—` | `nft/tensor_cnft` | Créer `nft/tensor_cnft` | +| `orderbook.j1o2qRpjcyUwEvwtcfhEQefh773ZgjxcVRry7LDqg5X.jupiter_limit_order_v2.V0_1_0.from_solscan.json` | `orderbook/jupiter_limit_order_v2` | `orderbook/jupiter_limit_order_v2` | Aucune | +| `orderbook.jupoNjAxXgZ4rjzxzPMP4oxduvQsQtZzyknqvzYNrNu.jupiter_limit_order.V0_1_0.from_solscan.json` | `orderbook/jupiter_limit_order` | `orderbook/jupiter_limit_order` | Aucune | +| `orderbook.opnb2LAfJYbRMAHHvqjCwQxanZn7ReEHp1k81EohpZb.openbook_v2.V0_1_0.from_solscan.json` | `orderbook/openbook_v2` | `orderbook/openbook_v2` | Aucune | +| `perpetuals.EtrnLzgbS7nMMy5fbD42kXiUzGg8XQzJ972Xtk1cjWih.phoenix_eternal.V0_2_0.from_solscan.json` | `—` | `perpetuals/phoenix_eternal` | Créer `perpetuals/phoenix_eternal` | +| `perpetuals.PERPHjGBqRHArX4DySjwM6UJHiR3sWAatqfdBS2qQJu.jupiter.V0_1_0.from_solscan.json` | `perpetuals/jupiter` | `perpetuals/jupiter` | Aucune | +| `perpetuals.ZETAxsqBRek56DhiGXrn75yj2NHU3aYUnxvHXpkf3aD.zeta.V0_1_0.from_solscan.json` | `perpetuals/zeta` | `perpetuals/zeta` | Aucune | +| `perpetuals.dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH.drift_v2.V2_150_0.from_solscan.json` | `perpetuals/drift_v2` | `perpetuals/drift_v2` | Aucune | +| `router.6m2CDdhRgxpH4WjvdzxAYbGxwdGUz5MziiL5jek2kBma.okx_labs_v1.V0_1_0.from_solscan.json` | `router/okx_labs_v1` | `router/okx_labs_v1` | Aucune | +| `router.DF1ow4tspfHX9JwWJsAb9epbkA8hmpSEAtxXy1V27QBH.dflow_aggregator_v4.V0_1_0.from_solscan.json` | `router/dflow_aggregator_v4` | `router/dflow_aggregator_v4` | Aucune | +| `router.JUP4Fb2cqiRUcaTHdrPC8h2gNsA2ETXiPDD33WcGuJB.jupiter_aggregator_v4.V0_1_0.from_solscan.json` | `router/jupiter_aggregator_v4` | `router/jupiter_aggregator_v4` | Aucune | +| `router.JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4.jupiter_aggregator_v6.V0_1_0.from_solscan.json` | `router/jupiter_aggregator_v6` | `router/jupiter_aggregator_v6` | Aucune | +| `router.proVF4pMXVaYqmy4NjniPh4pqKNfMmsihgd4wdkCX3u.okx_labs_v2.V0_1_0.from_solscan.json` | `router/okx_labs_v2` | `router/okx_labs_v2` | Aucune | +| `rwa.XzTT4XB8m7sLD2xi6snefSasaswsKCxx5Tifjondogm.ondo_global_markets.V0_1_0.from_solscan.json` | `rwa/ondo_global_markets` | `rwa/ondo_global_markets` | Aucune | +| `solana.11111111111111111111111111111111.core.system.V0_0_1.from_github_solana_program.json` | `solana/core/system` | `solana/core/system` | Aucune | +| `solana.AddressLookupTab1e1111111111111111111111111.core.address_lookup_table.V0_0_1.from_github_solana_program.json` | `solana/core/address_lookup_table` | `solana/core/address_lookup_table` | Aucune | +| `solana.BPFLoaderUpgradeab1e11111111111111111111111.core.loader_v3.V0_0_0.from_github_solana_program.json` | `solana/core/loader_v3` | `solana/core/loader_v3` | Aucune | +| `solana.ComputeBudget111111111111111111111111111111.core.compute_budget.V0_0_1.from_github_solana_program.json` | `solana/core/compute_budget` | `solana/core/compute_budget` | Aucune | +| `solana.Config1111111111111111111111111111111111111.core.config.V0_0_1.from_github_solana_program.json` | `solana/core/config` | `solana/core/config` | Aucune | +| `solana.Feature111111111111111111111111111111111111.core.feature.V0_0_1.from_github_solana_program.json` | `solana/core/feature` | `solana/core/feature` | Aucune | +| `solana.LoaderV411111111111111111111111111111111111.core.loader_v4.V0_0_0.from_github_solana_program.json` | `solana/core/loader_v4` | `solana/core/loader_v4` | Aucune | +| `solana.Stake11111111111111111111111111111111111111.core.stake.V4_3_1.from_github_solana_program.json` | `solana/core/stake` | `solana/core/stake` | Aucune | +| `spl.MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr.memo.v3.V3_0_1.from_github_solana_program.json` | `spl/memo/v3` | `spl/memo/v3` | Aucune | +| `spl.TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA.token.V3_3_0.from_github_solana_program.json` | `spl/token` | `spl/token` | Aucune | +| `spl.TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb.token_2022.V3_0_1.from_github_solana_program.json` | `spl/token_2022` | `spl/token_2022` | Aucune | +| `stable.HYEXCHtHkBagdStcJCp3xbbb9B7sdMdWXFNj6mdsG4hn.swap_hylo_exchange.V0_1_4.from_solscan.json` | `stable/swap_hylo_exchange` | `stable/swap_hylo_exchange` | Aucune | +| `stable.JUPUSDecMzAVgztLe6eGhwUBj1Pn3j9WAXwmtHmfbRr.swap_jupiter_stable.V0_1_0.from_solscan.json` | `stable/swap_jupiter_stable` | `stable/swap_jupiter_stable` | Aucune | +| `stable.NUMERUNsFCP3kuNmWZuXtm1AaQCPj9uw6Guv2Ekoi5P.swap_numeraire.V0_1_0.from_solscan.json` | `stable/swap_numeraire` | `stable/swap_numeraire` | Aucune | +| `stable.swapNyd8XiQwJ6ianp9snpu4brUqFxadzvHebnAXjJZ.swap_stabble.V1_5_0.from_solscan.json` | `stable/swap_stabble` | `stable/swap_stabble` | Aucune | +| `staking.4R3gSG8BpU4t19KYj8CfnbtRpnT8gtk4dvTHxVRwc2r7.jito_tip_distribution.V0_1_5.from_solscan.json` | `—` | `staking/jito_tip_distribution` | Créer `staking/jito_tip_distribution` | +| `staking.FarmsPZpWu9i7Kky8tPN37rs2TpmMrAZrC7S7vJa91Hr.kamino_farm.V1_6_5.from_solscan.json` | `staking/kamino_farm` | `staking/kamino_farm` | Aucune | +| `staking.MarBmsSgKXdrN1egZf5sqe1TMai9K1rChYNDJgjq7aD.marinade_finance.V0_1_0.from_solscan.json` | `staking/marinade_finance` | `staking/marinade_finance` | Aucune | +| `storage.recr1L3PCGKLbckBqMNcJhuuyU1zgo8nBhfLVsJNwr5.solana_record.V0_4_0.from_github_solana_program.json` | `—` | `storage/solana_record` | Créer `storage/solana_record` | +| `strategy.DCA265Vj8a9CEuX1eb1LWRnDT7uK6q1xMipnNyatn23M.jupiter_dca.V0_1_0.from_solscan.json` | `strategy/jupiter_dca` | `strategy/jupiter_dca` | Déplacer `strategy/jupiter_dca` → `strategy/jupiter_dca` | +| `treasury.treaf4wWBBty3fHdyBpo35Mz84M8k3heKXmjmi9vFt5.helium_treasury_management.V0_2_3.from_solscan.json` | `treasury/helium_treasury_management` | `treasury/helium_treasury_management` | Aucune | +| `vault.24Uqj9JCLxUeoC3hGfh5W3s9FM9uCHDS2SG3LYwBpyTi.meteora.V0_7_2.from_solscan.json` | `vault/meteora` | `vault/meteora` | Aucune | +| `vault.6LtLpnUFNByNXLyCoK9wA2MykKAmQNZKBdY8s47dehDc.kamino_yvaults.V0_1_0.from_solscan.json` | `vault/kamino_yvaults` | `vault/kamino_yvaults` | Aucune | +| `vault.CarrotwivhMpDnm27EHmRLeQ683Z1PufuqEmBZvD282s.carrot_defi.V0_1_0.from_solscan.json` | `vault/carrot_defi` | `vault/carrot_defi` | Aucune | +| `vault.HysTabVUfmQBFcmzu1ctRd1Y1fxd66RBpboy1bmtDSQQ.hylo_stability_pool.V0_1_4.from_solscan.json` | `vault/hylo_stability_pool` | `vault/hylo_stability_pool` | Aucune | +| `vault.KvauGMspG5k6rtzrqqn7WNn3oZdyKqLKwK2XWQ8FLjd.kamino_v2.V2_1_1.from_solscan.json` | `vault/kamino_v2` | `vault/kamino_v2` | Aucune | +| `vault.kvauTFR8qm1dhniz6pYuBZkuene3Hfrs1VQhVRgCNrr.kamino.V0_1_0.from_solscan.json` | `vault/kamino` | `vault/kamino` | Aucune | +| `vesting.LocpQgucEQHbqNABEYvBvwoxCPsSbG91A1QaQhQQqjn.jupiter_lock.V0_4_0.from_solscan.json` | `admin/jupiter_lock` | `vesting/jupiter_lock` | Déplacer `admin/jupiter_lock` → `vesting/jupiter_lock` | +| `vesting.strmRqUCoQUgGUan5YhzUZa6KqdzwX5L6FpUxfmKg5m.streamflow.V0_1_0.from_solscan.json` | `vesting/streamflow` | `vesting/streamflow` | Aucune | +| `wallet.JSW99DKmxNyREQM14SQLDykeBvEUG63TeohrvmofEiw.jupiter_apepro_smart_wallet.V0_1_0.from_solscan.json` | `wallet/jupiter_apepro_smart_wallet` | `wallet/jupiter_apepro_smart_wallet` | Aucune | +| `weighted.swapFpHZwjELNnjvThjajtiVmkz3yPQEHjLtka2fwHW.swap_stabble.V1_5_0.from_solscan.json` | `weighted/swap_stabble` | `weighted/swap_stabble` | Aucune | diff --git a/docs/plans/V0_4_8_SOLANA_PROGRAM_METADATA_AND_TOKEN_2022_COMPLETENESS_PLAN.md b/docs/plans/V0_4_8_SOLANA_PROGRAM_METADATA_AND_TOKEN_2022_COMPLETENESS_PLAN.md index 1548dcc..c0f502e 100644 --- a/docs/plans/V0_4_8_SOLANA_PROGRAM_METADATA_AND_TOKEN_2022_COMPLETENESS_PLAN.md +++ b/docs/plans/V0_4_8_SOLANA_PROGRAM_METADATA_AND_TOKEN_2022_COMPLETENESS_PLAN.md @@ -1,11 +1,11 @@ - + # Plan `0.4.8` — Solana Program Metadata et complétude Token-2022 ## 1. Statut et rôle -Ce document est le livrable principal de `0.4.8-pre.001` et a été réconcilié après l’audit contractuel de `0.4.8-pre.002`. Il constitue le plan vivant de la version et reste modifiable lorsque l’audit du code, des interfaces officielles, de l’IDL ou des validations Devnet impose un ajustement. +Ce document est le livrable principal de `0.4.8-pre.001`, réconcilié après l’audit contractuel de `0.4.8-pre.002`, les modèles de `pre.003` et les décodeurs de comptes de `pre.004`. Il constitue le plan vivant de la version et reste modifiable lorsque l’audit du code, des interfaces officielles, de l’IDL ou des validations Devnet impose un ajustement. Il doit être maintenu pendant chaque prerelease, puis archivé pendant la dernière prerelease après transfert des décisions durables vers le ROADMAP, les matrices, les guides, les rapports, les TODO et les changelogs concernés. @@ -15,13 +15,13 @@ Il doit être maintenu pendant chaque prerelease, puis archivé pendant la derni ### 2.1 Surfaces strictement distinctes -| Surface | Program ID ou frontière | Rôle dans `0.4.8` | -|-----------------------------------|--------------------------------------------------------------------|---------------------------------------------------------------------------------| -| Metaplex Token Metadata | `metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s` | surface clôturée en `0.4.7`, hors développement principal | -| Token-2022 Token Metadata | programme Token-2022 `TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb` | compléter le contrat déjà partiellement pris en charge | -| `spl-token-metadata-interface` | interface sans Program ID autonome imposé | source contractuelle des instructions metadata implémentées par Token-2022 | -| Solana Program Metadata | `ProgM6JCCvbYkfKqJYHePx4xxSUSqJp7rh8Lyv7nk7S` | nouvelle surface on-chain à implémenter sous `metadata/solana_program_metadata` | -| contenu distant HTTP/IPFS/Arweave | transport off-chain indépendant | hors `0.4.8`, reporté à l’horizon `0.15+` | +| Surface | Program ID ou frontière | Rôle dans `0.4.8` | +|---|---|---| +| Metaplex Token Metadata | `metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s` | surface clôturée en `0.4.7`, hors développement principal | +| Token-2022 Token Metadata | programme Token-2022 `TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb` | compléter le contrat déjà partiellement pris en charge | +| `spl-token-metadata-interface` | interface sans Program ID autonome imposé | source contractuelle des instructions metadata implémentées par Token-2022 | +| Solana Program Metadata | `ProgM6JCCvbYkfKqJYHePx4xxSUSqJp7rh8Lyv7nk7S` | nouvelle surface on-chain à implémenter sous `metadata/solana_program_metadata` | +| contenu distant HTTP/IPFS/Arweave | transport off-chain indépendant | hors `0.4.8`, reporté à l’horizon `0.15+` | Aucun décodeur Token-2022 existant n’est un décodeur partiel de `ProgM6…`. Les deux contrats doivent conserver des namespaces, modèles, matrices, tests et scénarios distincts. @@ -200,12 +200,26 @@ Résultat durable : la façade `kb-lib` expose les tailles fixes auditées, les - test d’API 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 l’environnement du projet avant validation définitive. +Validation définitive reçue le 5 août 2026 : -### `0.4.8-pre.004` — décodeur de comptes Solana Program Metadata +- `cargo fmt --all` : terminé ; +- `cargo check --workspace` : terminé ; +- `cargo clippy --all-targets` : terminé après ajout du `return` explicite demandé par le lint dans le test `external.to_wire_bytes().map(...)` ; +- `python3 scripts/audit_rust_workspace_rules.py` : propre ; +- `cargo test -p kb-lib` : 647 tests unitaires, cinq tests d’intégration et les doc-tests réussis. + +### `0.4.8-pre.004` — décodeur de comptes Solana Program Metadata — terminé côté delta + +Résultat durable : `kb-lib` expose des snapshots et parseurs bornés pour `Buffer` et `Metadata`. Le propriétaire, les discriminateurs, les headers, les enums fermées, les longueurs logiques, les sources de données et les PDA metadata sont vérifiés. Les données après `data_length` restent une capacité allouée valide ; les données Buffer occupent tout le reliquat. Aucune URL ni référence externe n’est résolue. - comptes `Buffer` et `Metadata` ; -- discriminateurs, options, données malformées et trailing bytes selon contrat. +- discriminateurs, options zeroable, données malformées et trailing capacity selon contrat ; +- borne maximale de 10 MiB avant toute projection allouante ; +- matrice `test-fixtures/contract-matrices/SOLANA_PROGRAM_METADATA_ACCOUNT_MATRIX.json` ; +- nomenclature IDL réconciliée avec le statut réel des comptes ; +- test de façade publique pour les consommateurs externes. + +Critère atteint côté delta : comptes exacts, erreurs fail-closed et politique de trailing bytes documentée ; compilation, Clippy et tests Rust restent à exécuter dans l’environnement du projet avant validation définitive. ### `0.4.8-pre.005` — décodeur d’instructions Solana Program Metadata diff --git a/kb-lib/CHANGELOG.md b/kb-lib/CHANGELOG.md index a9e3ce6..5b7cb11 100644 --- a/kb-lib/CHANGELOG.md +++ b/kb-lib/CHANGELOG.md @@ -1,8 +1,19 @@ - + # CHANGELOG — kb-lib +## 0.4.8-pre.004 — comptes Solana Program Metadata + +- ajoute les décodeurs publics bornés des comptes `Buffer` et `Metadata` ainsi que leur sélection par discriminateur ; +- valide le propriétaire `ProgM6…`, la borne Solana de 10 MiB et les headers fixes de 96 octets ; +- valide les PDA metadata canonical et non-canonical sans inventer de PDA stable pour les Buffers à autorité mutable ou alloués par keypair ; +- décode les sources `Direct`, `Url` et `External` sans lecture off-chain ni lecture implicite du compte référencé ; +- distingue `data_length` de la capacité allouée restante, compatible avec l’instruction `Extend` ; +- ajoute une matrice contractuelle machine-readable, les tests de bornes, de données malformées et de façade publique ; +- applique le retour explicite requis par Clippy dans le test de longueur wire du modèle externe. +- corrige l’ombre portée de `metadata_program` dans trois tests de comptes afin d’éviter l’inférence récursive `Option<_>` signalée par `E0275`. + ## 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 ; diff --git a/kb-lib/README.md b/kb-lib/README.md index 37d2ed3..43d0a70 100644 --- a/kb-lib/README.md +++ b/kb-lib/README.md @@ -1,5 +1,5 @@ - + # kb-lib @@ -7,7 +7,7 @@ ## 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. 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. +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 et les décodeurs de comptes `Buffer` et `Metadata` de Solana Program Metadata ; le décodeur d’instructions reste réservé jusqu’à la phase suivante. Pour Solana Program Metadata, la surface publique fondatrice comprend : @@ -15,7 +15,9 @@ Pour Solana Program Metadata, la surface publique fondatrice comprend : - 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. +- les cinq erreurs custom officielles et les erreurs de conversion fail-closed ; +- les snapshots bornés `Buffer` et `Metadata`, avec validation du propriétaire, des PDA metadata, des longueurs logiques et des trois sources de données ; +- une politique explicite où les octets situés après `data_length` sont conservés comme capacité allouée et jamais assimilés au contenu logique. Pour Metaplex Token Metadata, elle fournit : @@ -48,7 +50,7 @@ Pour Metaplex Token Metadata, elle fournit : - contrats `MtApi*` et matérialisateurs concrets ; - contrats `ExApi*`, politiques de sécurité et exécuteurs `Ex*Executor` ; - `ExMetadataMetaplexTokenMetadataExecutor`, `ExMetaplexTokenMetadataExecutionIntent` et `ExMetaplexTokenMetadataOperation` ; -- modèles `DcMetadataSpm*` et constantes `DC_METADATA_SPM_*` pour le contrat Solana Program Metadata ; +- modèles `DcMetadataSpm*`, constantes `DC_METADATA_SPM_*` et fonctions `decoder_metadata_solana_program_metadata_decode_*_account` pour le contrat Solana Program Metadata ; - modèles canoniques `Md*` réexportés par la façade. Les exemples d’appel et invariants sont documentés dans [USAGE.md](USAGE.md). @@ -63,7 +65,7 @@ Les exemples d’appel 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 d’un 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’à l’implémentation bornée des comptes et instructions. +Pour Solana Program Metadata, les modèles wire et les décodeurs bornés de comptes sont actifs. `Buffer` accepte tout reliquat comme données allouées sans inventer de PDA stable après changement d’autorité ; `Metadata` valide la dérivation canonical ou non-canonical et distingue le contenu logique de la capacité ajoutée par `Extend`. Le décodeur de protocole reste volontairement réservé jusqu’à l’implémentation des neuf instructions. Le registre ElGamal n’est pas déclaré validé réellement sur réseau. @@ -73,3 +75,4 @@ Le registre ElGamal n’est pas déclaré validé réellement sur réseau. - [Travaux restant à réaliser](TODO.md) - [Historique des changements](CHANGELOG.md) - [Matrice contractuelle Metaplex](../test-fixtures/contract-matrices/METAPLEX_TOKEN_METADATA_MATRIX.json) +- [Matrice des comptes Solana Program Metadata](../test-fixtures/contract-matrices/SOLANA_PROGRAM_METADATA_ACCOUNT_MATRIX.json) diff --git a/kb-lib/USAGE.md b/kb-lib/USAGE.md index bc61ccf..734bf4c 100644 --- a/kb-lib/USAGE.md +++ b/kb-lib/USAGE.md @@ -1,5 +1,5 @@ - + # Utilisation de kb-lib @@ -82,7 +82,7 @@ println!("parsed registry state: {registry:#?}"); Les parseurs vérifient les bornes et formats qu’ils annoncent. Ils ne reconstruisent pas un état historique depuis un RPC courant. -## Modèles Solana Program Metadata +## Modèles et comptes 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`. @@ -108,7 +108,37 @@ 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 d’instructions, matérialisateurs et builders. +Les trois fonctions publiques de comptes vérifient le propriétaire `ProgM6…`, la borne maximale de données et les layouts fixes : + +- `decoder_metadata_solana_program_metadata_decode_account` sélectionne `Buffer` ou `Metadata` depuis le discriminateur ; +- `decoder_metadata_solana_program_metadata_decode_buffer_account` conserve tous les octets alloués après le header, sans prétendre qu’un Buffer garde un PDA vérifiable après une modification d’autorité ; +- `decoder_metadata_solana_program_metadata_decode_metadata_account` valide le PDA canonical ou non-canonical, `data_length`, les enums fermées et les contraintes propres à `Direct`, `Url` et `External`. + +```rust +let snapshot = match kb_lib::decoder_metadata_solana_program_metadata_decode_account( + metadata_account.as_str(), + account_owner.as_str(), + &account_data, +) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error.to_string()), +}; + +match snapshot { + kb_lib::DcMetadataSpmAccountSnapshot::Buffer(value) => { + println!("buffer bytes={}", value.allocated_data_bytes); + }, + kb_lib::DcMetadataSpmAccountSnapshot::Metadata(value) => { + println!("metadata program={}", value.program); + println!("logical bytes={}", value.data_length); + println!("reserved capacity={}", value.trailing_capacity_bytes); + }, +} +``` + +Une URL est seulement décodée comme chaîne UTF-8 on-chain. Aucune requête HTTP, IPFS ou Arweave n’est effectuée. Une référence `External` est seulement décodée en adresse, offset et longueur zeroable ; le compte référencé n’est pas lu. + +Le décodeur de protocole `DcMetadataSolanaProgramMetadataDecoder` reste réservé aux instructions. Les comptes constituent désormais un contrat public stable pour les futurs matérialisateurs, builders et parcours stateful. ## Comptes Metaplex Token Metadata diff --git a/kb-lib/src/decoder.rs b/kb-lib/src/decoder.rs index b0bb7e1..48586ad 100644 --- a/kb-lib/src/decoder.rs +++ b/kb-lib/src/decoder.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder.rs -// version: 13 +// version: 14 //! Consolidated decoder modules. @@ -336,6 +336,8 @@ pub(crate) use self::metadata::DC_METADATA_MTM_VERIFY_SIZED_COLLECTION_ITEM_DISC 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; +/// Maximum Solana account-data length accepted by bounded account decoders. +pub use self::metadata::DC_METADATA_SPM_MAX_ACCOUNT_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. @@ -388,12 +390,18 @@ pub use self::metadata::DcMetadataMtmTokenOwnedEscrowAccountSnapshot; pub use self::metadata::DcMetadataMtmTokenOwnedEscrowAuthorityKind; /// Canonical bounded projection of one programmable Token Record account. pub use self::metadata::DcMetadataMtmTokenRecordAccountSnapshot; -/// Reserved `kb-lib.decoder.metadata.solana_program_metadata` decoder. +/// Solana Program Metadata instruction decoder; account decoders are active. pub use self::metadata::DcMetadataSolanaProgramMetadataDecoder; /// Reserved `kb-lib.decoder.metadata.spl_name_service` decoder. pub use self::metadata::DcMetadataSplNameServiceDecoder; +/// Error returned while decoding a Solana Program Metadata account. +pub use self::metadata::DcMetadataSpmAccountDecodeError; /// Solana Program Metadata account discriminator. pub use self::metadata::DcMetadataSpmAccountDiscriminator; +/// Supported bounded Solana Program Metadata account projection. +pub use self::metadata::DcMetadataSpmAccountSnapshot; +/// Bounded projection of one Solana Program Metadata Buffer account. +pub use self::metadata::DcMetadataSpmBufferAccountSnapshot; /// Compression declared for Solana Program Metadata content. pub use self::metadata::DcMetadataSpmCompression; /// Typed Solana Program Metadata content without off-chain resolution. @@ -406,6 +414,8 @@ pub use self::metadata::DcMetadataSpmEncoding; pub use self::metadata::DcMetadataSpmExternalData; /// Format declared for Solana Program Metadata content. pub use self::metadata::DcMetadataSpmFormat; +/// Bounded projection of one initialized Solana Program Metadata account. +pub use self::metadata::DcMetadataSpmMetadataAccountSnapshot; /// Bounded Solana Program Metadata model conversion error. pub use self::metadata::DcMetadataSpmModelError; /// Official Solana Program Metadata custom error. @@ -440,6 +450,12 @@ pub use self::metadata::decoder_metadata_metaplex_token_metadata_decode_use_auth pub(crate) use self::metadata::decoder_metadata_metaplex_token_metadata_entry_for_discriminator; /// Crate-root access to `payload_discriminator` from `instruction`. pub(crate) use self::metadata::decoder_metadata_metaplex_token_metadata_payload_discriminator; +/// Decodes one supported Solana Program Metadata account by its discriminator. +pub use self::metadata::decoder_metadata_solana_program_metadata_decode_account; +/// Decodes one Solana Program Metadata Buffer account. +pub use self::metadata::decoder_metadata_solana_program_metadata_decode_buffer_account; +/// Decodes one initialized Solana Program Metadata account. +pub use self::metadata::decoder_metadata_solana_program_metadata_decode_metadata_account; /// Reserved `kb-lib.decoder.nft.metaplex_bubblegum` decoder. pub use self::nft::DcNftMetaplexBubblegumDecoder; /// Reserved `kb-lib.decoder.nft.tensor_cnft` decoder. diff --git a/kb-lib/src/decoder/metadata.rs b/kb-lib/src/decoder/metadata.rs index 4ed5e4a..18377e4 100644 --- a/kb-lib/src/decoder/metadata.rs +++ b/kb-lib/src/decoder/metadata.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/metadata.rs -// version: 8 +// version: 9 //! `metadata` decoder family. @@ -81,14 +81,22 @@ pub use self::metaplex_token_metadata::decoder_metadata_metaplex_token_metadata_ 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; +/// Maximum Solana account-data length accepted by bounded account decoders. +pub use self::solana_program_metadata::DC_METADATA_SPM_MAX_ACCOUNT_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. +/// Solana Program Metadata instruction decoder; account decoders are active. pub use self::solana_program_metadata::DcMetadataSolanaProgramMetadataDecoder; +/// Error returned while decoding a Solana Program Metadata account. +pub use self::solana_program_metadata::DcMetadataSpmAccountDecodeError; /// Solana Program Metadata account discriminator. pub use self::solana_program_metadata::DcMetadataSpmAccountDiscriminator; +/// Supported bounded Solana Program Metadata account projection. +pub use self::solana_program_metadata::DcMetadataSpmAccountSnapshot; +/// Bounded projection of one Solana Program Metadata Buffer account. +pub use self::solana_program_metadata::DcMetadataSpmBufferAccountSnapshot; /// Compression declared for Solana Program Metadata content. pub use self::solana_program_metadata::DcMetadataSpmCompression; /// Typed Solana Program Metadata content without off-chain resolution. @@ -101,12 +109,20 @@ pub use self::solana_program_metadata::DcMetadataSpmEncoding; pub use self::solana_program_metadata::DcMetadataSpmExternalData; /// Format declared for Solana Program Metadata content. pub use self::solana_program_metadata::DcMetadataSpmFormat; +/// Bounded projection of one initialized Solana Program Metadata account. +pub use self::solana_program_metadata::DcMetadataSpmMetadataAccountSnapshot; /// 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; +/// Decodes one supported Solana Program Metadata account by its discriminator. +pub use self::solana_program_metadata::decoder_metadata_solana_program_metadata_decode_account; +/// Decodes one Solana Program Metadata Buffer account. +pub use self::solana_program_metadata::decoder_metadata_solana_program_metadata_decode_buffer_account; +/// Decodes one initialized Solana Program Metadata account. +pub use self::solana_program_metadata::decoder_metadata_solana_program_metadata_decode_metadata_account; /// Reserved `kb-lib.decoder.metadata.spl_name_service` decoder. pub use self::spl_name_service::DcMetadataSplNameServiceDecoder; diff --git a/kb-lib/src/decoder/metadata/solana_program_metadata.rs b/kb-lib/src/decoder/metadata/solana_program_metadata.rs index 8f4c6f3..4a98fb8 100644 --- a/kb-lib/src/decoder/metadata/solana_program_metadata.rs +++ b/kb-lib/src/decoder/metadata/solana_program_metadata.rs @@ -1,15 +1,32 @@ // file: kb-lib/src/decoder/metadata/solana_program_metadata.rs -// version: 2 +// version: 3 -//! Public Solana Program Metadata models and reserved protocol decoder. +//! Public Solana Program Metadata models, account decoders and reserved instruction decoder. +mod account; mod constants; mod model; +/// Error returned while decoding a Solana Program Metadata account. +pub use self::account::DcMetadataSpmAccountDecodeError; +/// Supported bounded Solana Program Metadata account projection. +pub use self::account::DcMetadataSpmAccountSnapshot; +/// Bounded projection of one Solana Program Metadata Buffer account. +pub use self::account::DcMetadataSpmBufferAccountSnapshot; +/// Bounded projection of one initialized Solana Program Metadata account. +pub use self::account::DcMetadataSpmMetadataAccountSnapshot; +/// Decodes one supported Solana Program Metadata account by its discriminator. +pub use self::account::decoder_metadata_solana_program_metadata_decode_account; +/// Decodes one Solana Program Metadata Buffer account. +pub use self::account::decoder_metadata_solana_program_metadata_decode_buffer_account; +/// Decodes one initialized Solana Program Metadata account. +pub use self::account::decoder_metadata_solana_program_metadata_decode_metadata_account; /// 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; +/// Maximum Solana account-data length accepted by bounded account decoders. +pub use self::constants::DC_METADATA_SPM_MAX_ACCOUNT_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. @@ -35,7 +52,7 @@ 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. +/// Decoder reserved for Solana Program Metadata instructions; account decoding is exposed separately. #[derive(Clone, Debug, Default)] pub struct DcMetadataSolanaProgramMetadataDecoder; diff --git a/kb-lib/src/decoder/metadata/solana_program_metadata/account.rs b/kb-lib/src/decoder/metadata/solana_program_metadata/account.rs new file mode 100644 index 0000000..3c97c3a --- /dev/null +++ b/kb-lib/src/decoder/metadata/solana_program_metadata/account.rs @@ -0,0 +1,1121 @@ +// file: kb-lib/src/decoder/metadata/solana_program_metadata/account.rs +// version: 2 + +//! Bounded account decoding for the Solana Program Metadata program. + +/// Error returned while decoding a Solana Program Metadata account. +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum DcMetadataSpmAccountDecodeError { + /// The account address is not a valid Solana public key. + InvalidAccountKey, + /// The account owner is not a valid Solana public key. + InvalidOwner, + /// The account is owned by another program. + ForeignOwner, + /// The account data is empty. + EmptyAccountData, + /// The account carries the published uninitialized discriminator. + UninitializedAccount, + /// The account exceeds the explicit Solana account-data bound. + AccountDataTooLarge { + /// Observed byte length. + actual_bytes: usize, + /// Maximum accepted byte length. + max_bytes: usize, + }, + /// The account is shorter than the fixed header required by its discriminator. + TruncatedHeader { + /// Observed byte length. + actual_bytes: usize, + /// Minimum required byte length. + minimum_bytes: usize, + }, + /// A type-specific decoder received another supported account discriminator. + UnexpectedAccountDiscriminator { + /// Discriminator required by the decoder. + expected: crate::DcMetadataSpmAccountDiscriminator, + /// Discriminator found on wire. + actual: crate::DcMetadataSpmAccountDiscriminator, + }, + /// The metadata header declares more logical data than the allocated account can hold. + LogicalDataOutOfBounds { + /// Logical data length declared by the header. + declared_bytes: usize, + /// Bytes allocated after the fixed header. + available_bytes: usize, + }, + /// The target architecture cannot represent the published `u32` logical length as `usize`. + LogicalDataLengthUnsupported { + /// Logical data length declared by the header. + declared_bytes: u32, + }, + /// A non-canonical metadata account does not retain its required authority. + MissingNonCanonicalAuthority, + /// The account address does not match the canonical or non-canonical PDA declared by its header. + InvalidMetadataPda, + /// URL data is not valid UTF-8. + InvalidUrlUtf8, + /// A bounded public model conversion failed. + Model(crate::DcMetadataSpmModelError), +} + +impl std::fmt::Display for DcMetadataSpmAccountDecodeError { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::InvalidAccountKey => { + return formatter.write_str("program metadata account key is invalid"); + }, + Self::InvalidOwner => { + return formatter.write_str("program metadata account owner is invalid"); + }, + Self::ForeignOwner => { + return formatter.write_str("program metadata account is owned by another program"); + }, + Self::EmptyAccountData => { + return formatter.write_str("program metadata account data is empty"); + }, + Self::UninitializedAccount => { + return formatter.write_str("program metadata account is uninitialized"); + }, + Self::AccountDataTooLarge { actual_bytes, max_bytes } => { + return write!( + formatter, + "program metadata account data length {actual_bytes} exceeds {max_bytes} bytes", + ); + }, + Self::TruncatedHeader { actual_bytes, minimum_bytes } => { + return write!( + formatter, + "program metadata account header length {actual_bytes} is below {minimum_bytes} bytes", + ); + }, + Self::UnexpectedAccountDiscriminator { expected, actual } => { + return write!( + formatter, + "program metadata account discriminator {actual:?} does not match {expected:?}", + ); + }, + Self::LogicalDataOutOfBounds { declared_bytes, available_bytes } => { + return write!( + formatter, + "program metadata logical data length {declared_bytes} exceeds {available_bytes} allocated bytes", + ); + }, + Self::LogicalDataLengthUnsupported { declared_bytes } => { + return write!( + formatter, + "program metadata logical data length {declared_bytes} cannot be represented on this architecture", + ); + }, + Self::MissingNonCanonicalAuthority => { + return formatter + .write_str("non-canonical program metadata account has no authority"); + }, + Self::InvalidMetadataPda => { + return formatter.write_str("program metadata account PDA is invalid"); + }, + Self::InvalidUrlUtf8 => { + return formatter.write_str("program metadata URL data is not valid UTF-8"); + }, + Self::Model(error) => { + return write!(formatter, "program metadata model conversion failed: {error}"); + }, + } + } +} + +impl std::error::Error for DcMetadataSpmAccountDecodeError {} + +/// Bounded projection of one Solana Program Metadata Buffer account. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct DcMetadataSpmBufferAccountSnapshot { + /// Account address supplied by the caller. + pub account: String, + /// Optional program retained by the zeroable Buffer header. + pub program: std::option::Option, + /// Optional current Buffer authority. + pub authority: std::option::Option, + /// Whether the Buffer was declared canonical. + pub canonical: bool, + /// Exact fixed-size derivation seed retained by the Buffer header. + pub seed: crate::DcMetadataSpmSeed, + /// Complete bytes allocated after the fixed Buffer header. + pub data: std::vec::Vec, + /// Number of bytes allocated after the fixed Buffer header. + pub allocated_data_bytes: usize, +} + +/// Bounded projection of one initialized Solana Program Metadata account. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct DcMetadataSpmMetadataAccountSnapshot { + /// Validated metadata PDA address. + pub account: String, + /// PDA bump derived from the header contract. + pub bump: u8, + /// Program to which the metadata belongs. + pub program: String, + /// Optional current metadata authority. + pub authority: std::option::Option, + /// Whether the metadata account accepts mutations. + pub mutable: bool, + /// Whether the metadata PDA is derived without a third-party authority seed. + pub canonical: bool, + /// Exact fixed-size metadata derivation seed. + pub seed: crate::DcMetadataSpmSeed, + /// Encoding declared for the logical metadata content. + pub encoding: crate::DcMetadataSpmEncoding, + /// Compression declared for the logical metadata content. + pub compression: crate::DcMetadataSpmCompression, + /// Format declared for the logical metadata content. + pub format: crate::DcMetadataSpmFormat, + /// Source declared for the logical metadata content. + pub data_source: crate::DcMetadataSpmDataSource, + /// Logical data byte length declared by the account header. + pub data_length: u32, + /// Complete typed on-chain metadata content without off-chain resolution. + pub data: crate::DcMetadataSpmData, + /// Number of bytes allocated after the fixed Metadata header. + pub allocated_data_bytes: usize, + /// Allocated capacity remaining after the declared logical content. + pub trailing_capacity_bytes: usize, +} + +/// Supported bounded Solana Program Metadata account projection. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +#[serde(tag = "account_kind", content = "snapshot", rename_all = "snake_case")] +pub enum DcMetadataSpmAccountSnapshot { + /// Writable staging Buffer account. + Buffer(crate::DcMetadataSpmBufferAccountSnapshot), + /// Initialized Metadata account. + Metadata(crate::DcMetadataSpmMetadataAccountSnapshot), +} + +/// Decodes one supported Solana Program Metadata account by its exact discriminator. +pub fn decoder_metadata_solana_program_metadata_decode_account( + account: &str, + owner: &str, + data: &[u8], +) -> std::result::Result +{ + match decoder_metadata_solana_program_metadata_validate_account_context(account, owner, data) { + std::result::Result::Ok(_) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let discriminator = match crate::DcMetadataSpmAccountDiscriminator::from_wire_value(data[0]) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => { + return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model(error)); + }, + }; + match discriminator { + crate::DcMetadataSpmAccountDiscriminator::Buffer => { + let snapshot = + match crate::decoder_metadata_solana_program_metadata_decode_buffer_account( + account, owner, data, + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return std::result::Result::Ok(crate::DcMetadataSpmAccountSnapshot::Buffer(snapshot)); + }, + crate::DcMetadataSpmAccountDiscriminator::Metadata => { + let snapshot = + match crate::decoder_metadata_solana_program_metadata_decode_metadata_account( + account, owner, data, + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return std::result::Result::Ok(crate::DcMetadataSpmAccountSnapshot::Metadata( + snapshot, + )); + }, + crate::DcMetadataSpmAccountDiscriminator::Empty => { + return std::result::Result::Err( + crate::DcMetadataSpmAccountDecodeError::UninitializedAccount, + ); + }, + } +} + +/// Decodes one Solana Program Metadata Buffer account without claiming a stable PDA after authority changes. +pub fn decoder_metadata_solana_program_metadata_decode_buffer_account( + account: &str, + owner: &str, + data: &[u8], +) -> std::result::Result< + crate::DcMetadataSpmBufferAccountSnapshot, + crate::DcMetadataSpmAccountDecodeError, +> { + let (account_key, _) = match decoder_metadata_solana_program_metadata_validate_account_context( + account, owner, data, + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + if data.len() < crate::DC_METADATA_SPM_BUFFER_HEADER_BYTES { + return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::TruncatedHeader { + actual_bytes: data.len(), + minimum_bytes: crate::DC_METADATA_SPM_BUFFER_HEADER_BYTES, + }); + } + let discriminator = match crate::DcMetadataSpmAccountDiscriminator::from_wire_value(data[0]) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => { + return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model(error)); + }, + }; + if discriminator != crate::DcMetadataSpmAccountDiscriminator::Buffer { + return std::result::Result::Err( + crate::DcMetadataSpmAccountDecodeError::UnexpectedAccountDiscriminator { + expected: crate::DcMetadataSpmAccountDiscriminator::Buffer, + actual: discriminator, + }, + ); + } + let program = decoder_metadata_solana_program_metadata_parse_zeroable_pubkey(&data[1..33]); + let authority = decoder_metadata_solana_program_metadata_parse_zeroable_pubkey(&data[33..65]); + let seed = match crate::DcMetadataSpmSeed::from_wire_bytes(&data[66..82]) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => { + return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model(error)); + }, + }; + let program_string = match program { + std::option::Option::Some(value) => std::option::Option::Some(value.to_string()), + std::option::Option::None => std::option::Option::None, + }; + let authority_string = match authority { + std::option::Option::Some(value) => std::option::Option::Some(value.to_string()), + std::option::Option::None => std::option::Option::None, + }; + let buffer_data = data[crate::DC_METADATA_SPM_BUFFER_HEADER_BYTES..].to_vec(); + return std::result::Result::Ok(crate::DcMetadataSpmBufferAccountSnapshot { + account: account_key.to_string(), + program: program_string, + authority: authority_string, + canonical: data[65] != 0, + seed, + allocated_data_bytes: buffer_data.len(), + data: buffer_data, + }); +} + +/// Decodes one Solana Program Metadata account with exact owner, PDA and logical-data validation. +pub fn decoder_metadata_solana_program_metadata_decode_metadata_account( + account: &str, + owner: &str, + data: &[u8], +) -> std::result::Result< + crate::DcMetadataSpmMetadataAccountSnapshot, + crate::DcMetadataSpmAccountDecodeError, +> { + let (account_key, metadata_program) = + match decoder_metadata_solana_program_metadata_validate_account_context( + account, owner, data, + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + if data.len() < crate::DC_METADATA_SPM_METADATA_HEADER_BYTES { + return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::TruncatedHeader { + actual_bytes: data.len(), + minimum_bytes: crate::DC_METADATA_SPM_METADATA_HEADER_BYTES, + }); + } + let discriminator = match crate::DcMetadataSpmAccountDiscriminator::from_wire_value(data[0]) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => { + return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model(error)); + }, + }; + if discriminator != crate::DcMetadataSpmAccountDiscriminator::Metadata { + return std::result::Result::Err( + crate::DcMetadataSpmAccountDecodeError::UnexpectedAccountDiscriminator { + expected: crate::DcMetadataSpmAccountDiscriminator::Metadata, + actual: discriminator, + }, + ); + } + let program = decoder_metadata_solana_program_metadata_parse_required_pubkey(&data[1..33]); + let authority = decoder_metadata_solana_program_metadata_parse_zeroable_pubkey(&data[33..65]); + let mutable = data[65] != 0; + let canonical = data[66] != 0; + let seed = match crate::DcMetadataSpmSeed::from_wire_bytes(&data[67..83]) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => { + return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model(error)); + }, + }; + let encoding = match crate::DcMetadataSpmEncoding::from_wire_value(data[83]) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => { + return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model(error)); + }, + }; + let compression = match crate::DcMetadataSpmCompression::from_wire_value(data[84]) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => { + return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model(error)); + }, + }; + let format = match crate::DcMetadataSpmFormat::from_wire_value(data[85]) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => { + return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model(error)); + }, + }; + let data_source = match crate::DcMetadataSpmDataSource::from_wire_value(data[86]) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => { + return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model(error)); + }, + }; + let mut data_length_wire = [0_u8; 4]; + data_length_wire.copy_from_slice(&data[87..91]); + let data_length = u32::from_le_bytes(data_length_wire); + let declared_bytes = match usize::try_from(data_length) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => { + return std::result::Result::Err( + crate::DcMetadataSpmAccountDecodeError::LogicalDataLengthUnsupported { + declared_bytes: data_length, + }, + ); + }, + }; + let allocated_data_bytes = data.len() - crate::DC_METADATA_SPM_METADATA_HEADER_BYTES; + if declared_bytes > allocated_data_bytes { + return std::result::Result::Err( + crate::DcMetadataSpmAccountDecodeError::LogicalDataOutOfBounds { + declared_bytes, + available_bytes: allocated_data_bytes, + }, + ); + } + let logical_data = &data[crate::DC_METADATA_SPM_METADATA_HEADER_BYTES + ..crate::DC_METADATA_SPM_METADATA_HEADER_BYTES + declared_bytes]; + let typed_data = + match decoder_metadata_solana_program_metadata_decode_typed_data(data_source, logical_data) + { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let bump = match decoder_metadata_solana_program_metadata_validate_metadata_pda( + &account_key, + &metadata_program, + &program, + authority.as_ref(), + canonical, + &seed, + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority_string = match authority { + std::option::Option::Some(value) => std::option::Option::Some(value.to_string()), + std::option::Option::None => std::option::Option::None, + }; + return std::result::Result::Ok(crate::DcMetadataSpmMetadataAccountSnapshot { + account: account_key.to_string(), + bump, + program: program.to_string(), + authority: authority_string, + mutable, + canonical, + seed, + encoding, + compression, + format, + data_source, + data_length, + data: typed_data, + allocated_data_bytes, + trailing_capacity_bytes: allocated_data_bytes - declared_bytes, + }); +} + +fn decoder_metadata_solana_program_metadata_validate_account_context( + account: &str, + owner: &str, + data: &[u8], +) -> std::result::Result< + (solana_pubkey::Pubkey, solana_pubkey::Pubkey), + crate::DcMetadataSpmAccountDecodeError, +> { + let account_key = match ::from_str(account) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => { + return std::result::Result::Err( + crate::DcMetadataSpmAccountDecodeError::InvalidAccountKey, + ); + }, + }; + let owner_key = match ::from_str(owner) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => { + return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::InvalidOwner); + }, + }; + let expected_owner = match ::from_str( + kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID, + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => { + return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::InvalidOwner); + }, + }; + if owner_key != expected_owner { + return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::ForeignOwner); + } + if data.is_empty() { + return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::EmptyAccountData); + } + if data.len() > crate::DC_METADATA_SPM_MAX_ACCOUNT_BYTES { + return std::result::Result::Err( + crate::DcMetadataSpmAccountDecodeError::AccountDataTooLarge { + actual_bytes: data.len(), + max_bytes: crate::DC_METADATA_SPM_MAX_ACCOUNT_BYTES, + }, + ); + } + return std::result::Result::Ok((account_key, expected_owner)); +} + +fn decoder_metadata_solana_program_metadata_parse_zeroable_pubkey( + data: &[u8], +) -> std::option::Option { + let mut bytes = [0_u8; 32]; + bytes.copy_from_slice(data); + if bytes == [0_u8; 32] { + return std::option::Option::None; + } + return std::option::Option::Some(solana_pubkey::Pubkey::new_from_array(bytes)); +} + +fn decoder_metadata_solana_program_metadata_parse_required_pubkey( + data: &[u8], +) -> solana_pubkey::Pubkey { + let mut bytes = [0_u8; 32]; + bytes.copy_from_slice(data); + return solana_pubkey::Pubkey::new_from_array(bytes); +} + +fn decoder_metadata_solana_program_metadata_decode_typed_data( + source: crate::DcMetadataSpmDataSource, + data: &[u8], +) -> std::result::Result { + let model = match source { + crate::DcMetadataSpmDataSource::Direct => crate::DcMetadataSpmData::Direct(data.to_vec()), + crate::DcMetadataSpmDataSource::Url => { + let url = match std::str::from_utf8(data) { + std::result::Result::Ok(value) => value.to_string(), + std::result::Result::Err(_) => { + return std::result::Result::Err( + crate::DcMetadataSpmAccountDecodeError::InvalidUrlUtf8, + ); + }, + }; + crate::DcMetadataSpmData::Url(url) + }, + crate::DcMetadataSpmDataSource::External => { + let external = match crate::DcMetadataSpmExternalData::from_wire_bytes(data) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => { + return std::result::Result::Err( + crate::DcMetadataSpmAccountDecodeError::Model(error), + ); + }, + }; + crate::DcMetadataSpmData::External(external) + }, + }; + match model.validate() { + std::result::Result::Ok(()) => return std::result::Result::Ok(model), + std::result::Result::Err(error) => { + return std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model(error)); + }, + } +} + +fn decoder_metadata_solana_program_metadata_validate_metadata_pda( + account: &solana_pubkey::Pubkey, + metadata_program: &solana_pubkey::Pubkey, + program: &solana_pubkey::Pubkey, + authority: std::option::Option<&solana_pubkey::Pubkey>, + canonical: bool, + seed: &crate::DcMetadataSpmSeed, +) -> std::result::Result { + let (expected, bump) = if canonical { + solana_pubkey::Pubkey::find_program_address( + &[program.as_ref(), seed.as_bytes()], + metadata_program, + ) + } else { + let authority_key = match authority { + std::option::Option::Some(value) => value, + std::option::Option::None => { + return std::result::Result::Err( + crate::DcMetadataSpmAccountDecodeError::MissingNonCanonicalAuthority, + ); + }, + }; + solana_pubkey::Pubkey::find_program_address( + &[program.as_ref(), authority_key.as_ref(), seed.as_bytes()], + metadata_program, + ) + }; + if account != &expected { + return std::result::Result::Err( + crate::DcMetadataSpmAccountDecodeError::InvalidMetadataPda, + ); + } + return std::result::Result::Ok(bump); +} + +#[cfg(test)] +mod tests { + fn metadata_program_id() -> std::option::Option { + match ::from_str( + kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID, + ) { + std::result::Result::Ok(value) => return std::option::Option::Some(value), + std::result::Result::Err(_) => return std::option::Option::None, + } + } + + #[derive(Clone, Copy)] + struct MetadataWireSettings { + mutable: u8, + canonical: u8, + encoding: crate::DcMetadataSpmEncoding, + compression: crate::DcMetadataSpmCompression, + format: crate::DcMetadataSpmFormat, + source: crate::DcMetadataSpmDataSource, + } + + fn metadata_wire( + program: solana_pubkey::Pubkey, + authority: std::option::Option, + seed: crate::DcMetadataSpmSeed, + settings: MetadataWireSettings, + logical_data: &[u8], + trailing_capacity: usize, + ) -> std::vec::Vec { + let mut data = vec![0_u8; crate::DC_METADATA_SPM_METADATA_HEADER_BYTES]; + data[0] = crate::DcMetadataSpmAccountDiscriminator::Metadata.wire_value(); + data[1..33].copy_from_slice(&program.to_bytes()); + if let std::option::Option::Some(value) = authority { + data[33..65].copy_from_slice(&value.to_bytes()); + } + data[65] = settings.mutable; + data[66] = settings.canonical; + data[67..83].copy_from_slice(seed.as_bytes()); + data[83] = settings.encoding.wire_value(); + data[84] = settings.compression.wire_value(); + data[85] = settings.format.wire_value(); + data[86] = settings.source.wire_value(); + let logical_length = match u32::try_from(logical_data.len()) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return data, + }; + data[87..91].copy_from_slice(&logical_length.to_le_bytes()); + data[91..96].fill(0xf3); + data.extend_from_slice(logical_data); + data.resize(data.len() + trailing_capacity, 0xa5); + return data; + } + + fn metadata_pda( + program: &solana_pubkey::Pubkey, + authority: std::option::Option<&solana_pubkey::Pubkey>, + canonical: bool, + seed: &crate::DcMetadataSpmSeed, + metadata_program: &solana_pubkey::Pubkey, + ) -> (solana_pubkey::Pubkey, u8) { + if canonical { + return solana_pubkey::Pubkey::find_program_address( + &[program.as_ref(), seed.as_bytes()], + metadata_program, + ); + } + if let std::option::Option::Some(value) = authority { + return solana_pubkey::Pubkey::find_program_address( + &[program.as_ref(), value.as_ref(), seed.as_bytes()], + metadata_program, + ); + } + return solana_pubkey::Pubkey::find_program_address( + &[program.as_ref(), seed.as_bytes()], + metadata_program, + ); + } + + #[test] + fn account_matrix_is_machine_readable_and_matches_compiled_bounds() { + let source = include_str!( + "../../../../../test-fixtures/contract-matrices/SOLANA_PROGRAM_METADATA_ACCOUNT_MATRIX.json" + ); + let matrix_result = serde_json::from_str::(source); + assert!(matrix_result.is_ok()); + if let std::result::Result::Ok(matrix) = matrix_result { + assert_eq!( + matrix["program_id"], + serde_json::Value::String( + kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID.to_string(), + ), + ); + assert_eq!( + matrix["official_contract"]["account_data_max_bytes"], + serde_json::Value::from(crate::DC_METADATA_SPM_MAX_ACCOUNT_BYTES), + ); + assert_eq!( + matrix["accounts"][1]["header_bytes"], + serde_json::Value::from(crate::DC_METADATA_SPM_BUFFER_HEADER_BYTES), + ); + assert_eq!( + matrix["accounts"][2]["header_bytes"], + serde_json::Value::from(crate::DC_METADATA_SPM_METADATA_HEADER_BYTES), + ); + assert_eq!( + matrix["data_sources"][2]["length"], + serde_json::Value::String("exactly_40_bytes".to_string()), + ); + } + } + + #[test] + fn buffer_decodes_zeroable_fields_nonzero_bool_and_all_allocated_bytes() { + let account = solana_pubkey::Pubkey::new_from_array([3_u8; 32]); + let program = solana_pubkey::Pubkey::new_from_array([4_u8; 32]); + let authority = solana_pubkey::Pubkey::new_from_array([5_u8; 32]); + let seed = crate::DcMetadataSpmSeed::from_bytes([6_u8; 16]); + let mut data = vec![0_u8; crate::DC_METADATA_SPM_BUFFER_HEADER_BYTES]; + data[0] = crate::DcMetadataSpmAccountDiscriminator::Buffer.wire_value(); + data[1..33].copy_from_slice(&program.to_bytes()); + data[33..65].copy_from_slice(&authority.to_bytes()); + data[65] = 7; + data[66..82].copy_from_slice(seed.as_bytes()); + data[82..96].fill(0xff); + data.extend_from_slice(&[8_u8, 9_u8]); + let result = crate::decoder_metadata_solana_program_metadata_decode_buffer_account( + account.to_string().as_str(), + kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID, + &data, + ); + assert!(result.is_ok()); + if let std::result::Result::Ok(snapshot) = result { + assert_eq!(snapshot.account, account.to_string()); + assert_eq!(snapshot.program, std::option::Option::Some(program.to_string())); + assert_eq!(snapshot.authority, std::option::Option::Some(authority.to_string())); + assert!(snapshot.canonical); + assert_eq!(snapshot.seed, seed); + assert_eq!(snapshot.data, vec![8_u8, 9_u8]); + assert_eq!(snapshot.allocated_data_bytes, 2); + } + let mut zeroable = data; + zeroable[1..65].fill(0); + let zeroable_result = crate::decoder_metadata_solana_program_metadata_decode_buffer_account( + account.to_string().as_str(), + kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID, + &zeroable, + ); + assert!(zeroable_result.is_ok()); + if let std::result::Result::Ok(snapshot) = zeroable_result { + assert_eq!(snapshot.program, std::option::Option::None); + assert_eq!(snapshot.authority, std::option::Option::None); + } + } + + #[test] + fn canonical_direct_metadata_validates_pda_and_preserves_trailing_capacity() { + let metadata_program_option: std::option::Option = + metadata_program_id(); + assert!(metadata_program_option.is_some()); + let metadata_program: solana_pubkey::Pubkey; + if let std::option::Option::Some(value) = metadata_program_option { + metadata_program = value; + } else { + return; + } + let program = solana_pubkey::Pubkey::new_from_array([11_u8; 32]); + let authority = solana_pubkey::Pubkey::new_from_array([12_u8; 32]); + let seed = crate::DcMetadataSpmSeed::from_bytes([13_u8; 16]); + let (account, expected_bump) = metadata_pda( + &program, + std::option::Option::Some(&authority), + true, + &seed, + &metadata_program, + ); + let data = metadata_wire( + program, + std::option::Option::Some(authority), + seed, + MetadataWireSettings { + mutable: 9, + canonical: 4, + encoding: crate::DcMetadataSpmEncoding::Utf8, + compression: crate::DcMetadataSpmCompression::Gzip, + format: crate::DcMetadataSpmFormat::Json, + source: crate::DcMetadataSpmDataSource::Direct, + }, + b"{}", + 7, + ); + let result = crate::decoder_metadata_solana_program_metadata_decode_metadata_account( + account.to_string().as_str(), + metadata_program.to_string().as_str(), + &data, + ); + assert!(result.is_ok()); + if let std::result::Result::Ok(snapshot) = result { + assert_eq!(snapshot.bump, expected_bump); + assert_eq!(snapshot.program, program.to_string()); + assert_eq!(snapshot.authority, std::option::Option::Some(authority.to_string())); + assert!(snapshot.mutable); + assert!(snapshot.canonical); + assert_eq!(snapshot.data_length, 2); + assert_eq!(snapshot.data, crate::DcMetadataSpmData::Direct(b"{}".to_vec())); + assert_eq!(snapshot.allocated_data_bytes, 9); + assert_eq!(snapshot.trailing_capacity_bytes, 7); + } + } + + #[test] + fn noncanonical_url_and_external_metadata_decode_exact_sources() { + let metadata_program_option: std::option::Option = + metadata_program_id(); + assert!(metadata_program_option.is_some()); + let metadata_program: solana_pubkey::Pubkey; + if let std::option::Option::Some(value) = metadata_program_option { + metadata_program = value; + } else { + return; + } + let program = solana_pubkey::Pubkey::new_from_array([21_u8; 32]); + let authority = solana_pubkey::Pubkey::new_from_array([22_u8; 32]); + let seed = crate::DcMetadataSpmSeed::from_bytes([23_u8; 16]); + let (url_account, _) = metadata_pda( + &program, + std::option::Option::Some(&authority), + false, + &seed, + &metadata_program, + ); + let url_data = metadata_wire( + program, + std::option::Option::Some(authority), + seed, + MetadataWireSettings { + mutable: 1, + canonical: 0, + encoding: crate::DcMetadataSpmEncoding::Utf8, + compression: crate::DcMetadataSpmCompression::None, + format: crate::DcMetadataSpmFormat::Json, + source: crate::DcMetadataSpmDataSource::Url, + }, + b"https://example.invalid/idl.json", + 0, + ); + let url_result = crate::decoder_metadata_solana_program_metadata_decode_account( + url_account.to_string().as_str(), + metadata_program.to_string().as_str(), + &url_data, + ); + assert!(url_result.is_ok()); + if let std::result::Result::Ok(crate::DcMetadataSpmAccountSnapshot::Metadata(snapshot)) = + url_result + { + assert_eq!( + snapshot.data, + crate::DcMetadataSpmData::Url("https://example.invalid/idl.json".to_string()), + ); + assert!(!snapshot.canonical); + } else { + return; + } + let external = crate::DcMetadataSpmExternalData::from_zeroable_length( + solana_pubkey::Pubkey::new_from_array([24_u8; 32]), + 128, + 4096, + ); + let external_wire = external.to_wire_bytes(); + let external_data = metadata_wire( + program, + std::option::Option::Some(authority), + seed, + MetadataWireSettings { + mutable: 1, + canonical: 0, + encoding: crate::DcMetadataSpmEncoding::None, + compression: crate::DcMetadataSpmCompression::None, + format: crate::DcMetadataSpmFormat::None, + source: crate::DcMetadataSpmDataSource::External, + }, + &external_wire, + 3, + ); + let external_result = + crate::decoder_metadata_solana_program_metadata_decode_metadata_account( + url_account.to_string().as_str(), + metadata_program.to_string().as_str(), + &external_data, + ); + assert!(external_result.is_ok()); + if let std::result::Result::Ok(snapshot) = external_result { + assert_eq!(snapshot.data, crate::DcMetadataSpmData::External(external)); + assert_eq!(snapshot.trailing_capacity_bytes, 3); + } + } + + #[test] + fn malformed_owner_discriminator_headers_and_logical_lengths_fail_closed() { + let account = solana_pubkey::Pubkey::new_from_array([31_u8; 32]); + assert_eq!( + crate::decoder_metadata_solana_program_metadata_decode_account( + account.to_string().as_str(), + kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID, + &[], + ), + std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::EmptyAccountData), + ); + let oversized = vec![1_u8; crate::DC_METADATA_SPM_MAX_ACCOUNT_BYTES + 1]; + assert_eq!( + crate::decoder_metadata_solana_program_metadata_decode_account( + account.to_string().as_str(), + kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID, + &oversized, + ), + std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::AccountDataTooLarge { + actual_bytes: crate::DC_METADATA_SPM_MAX_ACCOUNT_BYTES + 1, + max_bytes: crate::DC_METADATA_SPM_MAX_ACCOUNT_BYTES, + }), + ); + assert_eq!( + crate::decoder_metadata_solana_program_metadata_decode_account( + "invalid", + kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID, + &[1_u8], + ), + std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::InvalidAccountKey), + ); + assert_eq!( + crate::decoder_metadata_solana_program_metadata_decode_account( + account.to_string().as_str(), + solana_pubkey::Pubkey::new_from_array([32_u8; 32]).to_string().as_str(), + &[1_u8], + ), + std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::ForeignOwner), + ); + assert_eq!( + crate::decoder_metadata_solana_program_metadata_decode_account( + account.to_string().as_str(), + kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID, + &[0_u8], + ), + std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::UninitializedAccount), + ); + assert_eq!( + crate::decoder_metadata_solana_program_metadata_decode_account( + account.to_string().as_str(), + kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID, + &[9_u8], + ), + std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model( + crate::DcMetadataSpmModelError::UnknownAccountDiscriminator { value: 9 }, + )), + ); + assert_eq!( + crate::decoder_metadata_solana_program_metadata_decode_buffer_account( + account.to_string().as_str(), + kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID, + &[1_u8], + ), + std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::TruncatedHeader { + actual_bytes: 1, + minimum_bytes: crate::DC_METADATA_SPM_BUFFER_HEADER_BYTES, + }), + ); + let mut metadata = vec![0_u8; crate::DC_METADATA_SPM_METADATA_HEADER_BYTES]; + metadata[0] = crate::DcMetadataSpmAccountDiscriminator::Metadata.wire_value(); + metadata[86] = crate::DcMetadataSpmDataSource::Direct.wire_value(); + metadata[87..91].copy_from_slice(&4_u32.to_le_bytes()); + assert_eq!( + crate::decoder_metadata_solana_program_metadata_decode_metadata_account( + account.to_string().as_str(), + kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID, + &metadata, + ), + std::result::Result::Err( + crate::DcMetadataSpmAccountDecodeError::LogicalDataOutOfBounds { + declared_bytes: 4, + available_bytes: 0, + }, + ), + ); + } + + #[test] + fn source_specific_bounds_and_metadata_pda_fail_closed() { + let metadata_program_option: std::option::Option = + metadata_program_id(); + assert!(metadata_program_option.is_some()); + let metadata_program: solana_pubkey::Pubkey; + if let std::option::Option::Some(value) = metadata_program_option { + metadata_program = value; + } else { + return; + } + let program = solana_pubkey::Pubkey::new_from_array([41_u8; 32]); + let authority = solana_pubkey::Pubkey::new_from_array([42_u8; 32]); + let seed = crate::DcMetadataSpmSeed::from_bytes([43_u8; 16]); + let (account, _) = metadata_pda( + &program, + std::option::Option::Some(&authority), + true, + &seed, + &metadata_program, + ); + let empty = metadata_wire( + program, + std::option::Option::Some(authority), + seed, + MetadataWireSettings { + mutable: 1, + canonical: 1, + encoding: crate::DcMetadataSpmEncoding::None, + compression: crate::DcMetadataSpmCompression::None, + format: crate::DcMetadataSpmFormat::None, + source: crate::DcMetadataSpmDataSource::Direct, + }, + &[], + 0, + ); + assert_eq!( + crate::decoder_metadata_solana_program_metadata_decode_metadata_account( + account.to_string().as_str(), + metadata_program.to_string().as_str(), + &empty, + ), + std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model( + crate::DcMetadataSpmModelError::EmptyData { + source: crate::DcMetadataSpmDataSource::Direct, + }, + )), + ); + let invalid_url = metadata_wire( + program, + std::option::Option::Some(authority), + seed, + MetadataWireSettings { + mutable: 1, + canonical: 1, + encoding: crate::DcMetadataSpmEncoding::Utf8, + compression: crate::DcMetadataSpmCompression::None, + format: crate::DcMetadataSpmFormat::None, + source: crate::DcMetadataSpmDataSource::Url, + }, + &[0xff], + 0, + ); + assert_eq!( + crate::decoder_metadata_solana_program_metadata_decode_metadata_account( + account.to_string().as_str(), + metadata_program.to_string().as_str(), + &invalid_url, + ), + std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::InvalidUrlUtf8), + ); + let invalid_external = metadata_wire( + program, + std::option::Option::Some(authority), + seed, + MetadataWireSettings { + mutable: 1, + canonical: 1, + encoding: crate::DcMetadataSpmEncoding::None, + compression: crate::DcMetadataSpmCompression::None, + format: crate::DcMetadataSpmFormat::None, + source: crate::DcMetadataSpmDataSource::External, + }, + &[0_u8; 39], + 0, + ); + assert_eq!( + crate::decoder_metadata_solana_program_metadata_decode_metadata_account( + account.to_string().as_str(), + metadata_program.to_string().as_str(), + &invalid_external, + ), + std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model( + crate::DcMetadataSpmModelError::InvalidExternalDataLength { + actual_bytes: 39, + expected_bytes: crate::DC_METADATA_SPM_EXTERNAL_DATA_BYTES, + }, + )), + ); + let wrong_account = solana_pubkey::Pubkey::new_from_array([44_u8; 32]); + assert_eq!( + crate::decoder_metadata_solana_program_metadata_decode_metadata_account( + wrong_account.to_string().as_str(), + metadata_program.to_string().as_str(), + &invalid_external, + ), + std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::Model( + crate::DcMetadataSpmModelError::InvalidExternalDataLength { + actual_bytes: 39, + expected_bytes: crate::DC_METADATA_SPM_EXTERNAL_DATA_BYTES, + }, + )), + ); + let valid = metadata_wire( + program, + std::option::Option::Some(authority), + seed, + MetadataWireSettings { + mutable: 1, + canonical: 1, + encoding: crate::DcMetadataSpmEncoding::None, + compression: crate::DcMetadataSpmCompression::None, + format: crate::DcMetadataSpmFormat::None, + source: crate::DcMetadataSpmDataSource::Direct, + }, + &[1_u8], + 0, + ); + assert_eq!( + crate::decoder_metadata_solana_program_metadata_decode_metadata_account( + wrong_account.to_string().as_str(), + metadata_program.to_string().as_str(), + &valid, + ), + std::result::Result::Err(crate::DcMetadataSpmAccountDecodeError::InvalidMetadataPda), + ); + let missing_authority = metadata_wire( + program, + std::option::Option::None, + seed, + MetadataWireSettings { + mutable: 1, + canonical: 0, + encoding: crate::DcMetadataSpmEncoding::None, + compression: crate::DcMetadataSpmCompression::None, + format: crate::DcMetadataSpmFormat::None, + source: crate::DcMetadataSpmDataSource::Direct, + }, + &[1_u8], + 0, + ); + assert_eq!( + crate::decoder_metadata_solana_program_metadata_decode_metadata_account( + wrong_account.to_string().as_str(), + metadata_program.to_string().as_str(), + &missing_authority, + ), + std::result::Result::Err( + crate::DcMetadataSpmAccountDecodeError::MissingNonCanonicalAuthority, + ), + ); + } +} diff --git a/kb-lib/src/decoder/metadata/solana_program_metadata/constants.rs b/kb-lib/src/decoder/metadata/solana_program_metadata/constants.rs index 27c0cda..1f51f5f 100644 --- a/kb-lib/src/decoder/metadata/solana_program_metadata/constants.rs +++ b/kb-lib/src/decoder/metadata/solana_program_metadata/constants.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/metadata/solana_program_metadata/constants.rs -// version: 1 +// version: 2 //! Bounded Solana Program Metadata wire constants. @@ -11,3 +11,5 @@ pub const DC_METADATA_SPM_BUFFER_HEADER_BYTES: usize = 96; 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; +/// Maximum Solana account-data length accepted by bounded account decoders. +pub const DC_METADATA_SPM_MAX_ACCOUNT_BYTES: usize = 10_485_760; diff --git a/kb-lib/src/decoder/metadata/solana_program_metadata/model.rs b/kb-lib/src/decoder/metadata/solana_program_metadata/model.rs index 2cb682f..8b2eb71 100644 --- a/kb-lib/src/decoder/metadata/solana_program_metadata/model.rs +++ b/kb-lib/src/decoder/metadata/solana_program_metadata/model.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/decoder/metadata/solana_program_metadata/model.rs -// version: 1 +// version: 2 //! Public bounded models for the Solana Program Metadata wire contract. diff --git a/kb-lib/src/lib.rs b/kb-lib/src/lib.rs index 364c46b..6e7ad65 100644 --- a/kb-lib/src/lib.rs +++ b/kb-lib/src/lib.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/lib.rs -// version: 30 +// version: 31 //! Consolidated decoder, executor, materializer and shared model library. #![warn(missing_docs)] @@ -19,6 +19,8 @@ pub use self::decoder::DC_METADATA_MTM_METADATA_ACCOUNT_LAYOUT_AUDIT; 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 maximum account-data length accepted by bounded account decoders. +pub use self::decoder::DC_METADATA_SPM_MAX_ACCOUNT_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. @@ -230,12 +232,18 @@ pub use self::decoder::DcMetadataMtmTokenOwnedEscrowAccountSnapshot; pub use self::decoder::DcMetadataMtmTokenOwnedEscrowAuthorityKind; /// Canonical bounded projection of one programmable Token Record account. pub use self::decoder::DcMetadataMtmTokenRecordAccountSnapshot; -/// Exposes the reserved `DcMetadataSolanaProgramMetadataDecoder` implementation. +/// Exposes the Solana Program Metadata instruction decoder while account decoders are active. pub use self::decoder::DcMetadataSolanaProgramMetadataDecoder; /// Reserved `kb-lib.decoder.metadata.spl_name_service` decoder. pub use self::decoder::DcMetadataSplNameServiceDecoder; +/// Error returned while decoding a Solana Program Metadata account. +pub use self::decoder::DcMetadataSpmAccountDecodeError; /// Solana Program Metadata account discriminator. pub use self::decoder::DcMetadataSpmAccountDiscriminator; +/// Supported bounded Solana Program Metadata account projection. +pub use self::decoder::DcMetadataSpmAccountSnapshot; +/// Bounded projection of one Solana Program Metadata Buffer account. +pub use self::decoder::DcMetadataSpmBufferAccountSnapshot; /// Compression declared for Solana Program Metadata content. pub use self::decoder::DcMetadataSpmCompression; /// Typed Solana Program Metadata content without off-chain resolution. @@ -248,6 +256,8 @@ pub use self::decoder::DcMetadataSpmEncoding; pub use self::decoder::DcMetadataSpmExternalData; /// Format declared for Solana Program Metadata content. pub use self::decoder::DcMetadataSpmFormat; +/// Bounded projection of one initialized Solana Program Metadata account. +pub use self::decoder::DcMetadataSpmMetadataAccountSnapshot; /// Bounded Solana Program Metadata model conversion error. pub use self::decoder::DcMetadataSpmModelError; /// Official Solana Program Metadata custom error. @@ -382,6 +392,12 @@ pub use self::decoder::decoder_metadata_metaplex_token_metadata_decode_token_own pub use self::decoder::decoder_metadata_metaplex_token_metadata_decode_token_record_account; /// Decodes one Use Authority Record account. pub use self::decoder::decoder_metadata_metaplex_token_metadata_decode_use_authority_record_account; +/// Decodes one supported Solana Program Metadata account by its discriminator. +pub use self::decoder::decoder_metadata_solana_program_metadata_decode_account; +/// Decodes one Solana Program Metadata Buffer account. +pub use self::decoder::decoder_metadata_solana_program_metadata_decode_buffer_account; +/// Decodes one initialized Solana Program Metadata account. +pub use self::decoder::decoder_metadata_solana_program_metadata_decode_metadata_account; /// Parses one exact SPL ElGamal registry account. pub use self::decoder::decoder_spl_elgamal_registry_parse_elgamal_registry_state; /// Parses one Token-2022 Mint, Account, or Multisig state. diff --git a/kb-lib/tests/external_solana_program_metadata_account_api.rs b/kb-lib/tests/external_solana_program_metadata_account_api.rs new file mode 100644 index 0000000..3ad3caa --- /dev/null +++ b/kb-lib/tests/external_solana_program_metadata_account_api.rs @@ -0,0 +1,35 @@ +// file: kb-lib/tests/external_solana_program_metadata_account_api.rs +// version: 1 + +//! Downstream-style compilation contract for Solana Program Metadata account decoders. + +#[test] +fn external_consumers_decode_accounts_only_through_crate_root_exports() { + let account = solana_pubkey::Pubkey::new_from_array([17_u8; 32]); + let program = solana_pubkey::Pubkey::new_from_array([18_u8; 32]); + let authority = solana_pubkey::Pubkey::new_from_array([19_u8; 32]); + let seed = kb_lib::DcMetadataSpmSeed::from_bytes([20_u8; 16]); + let mut data = vec![0_u8; kb_lib::DC_METADATA_SPM_BUFFER_HEADER_BYTES]; + data[0] = kb_lib::DcMetadataSpmAccountDiscriminator::Buffer.wire_value(); + data[1..33].copy_from_slice(&program.to_bytes()); + data[33..65].copy_from_slice(&authority.to_bytes()); + data[65] = 1; + data[66..82].copy_from_slice(seed.as_bytes()); + data.extend_from_slice(&[21_u8, 22_u8]); + let result = kb_lib::decoder_metadata_solana_program_metadata_decode_account( + account.to_string().as_str(), + kb_program_ids::METADATA_SOLANA_PROGRAM_METADATA_PROGRAM_ID, + &data, + ); + assert!(result.is_ok()); + if let std::result::Result::Ok(kb_lib::DcMetadataSpmAccountSnapshot::Buffer(snapshot)) = result + { + assert_eq!(snapshot.account, account.to_string()); + assert_eq!(snapshot.program, std::option::Option::Some(program.to_string())); + assert_eq!(snapshot.authority, std::option::Option::Some(authority.to_string())); + assert_eq!(snapshot.data, vec![21_u8, 22_u8]); + } else { + return; + } + assert_eq!(kb_lib::DC_METADATA_SPM_MAX_ACCOUNT_BYTES, 10_485_760); +} diff --git a/test-fixtures/contract-matrices/SOLANA_PROGRAM_METADATA_ACCOUNT_MATRIX.json b/test-fixtures/contract-matrices/SOLANA_PROGRAM_METADATA_ACCOUNT_MATRIX.json new file mode 100644 index 0000000..d996038 --- /dev/null +++ b/test-fixtures/contract-matrices/SOLANA_PROGRAM_METADATA_ACCOUNT_MATRIX.json @@ -0,0 +1,96 @@ +{ + "schema_version": 1, + "milestone": "0.4.8-pre.004", + "surface_code": "metadata.solana_program_metadata", + "program_id": "ProgM6JCCvbYkfKqJYHePx4xxSUSqJp7rh8Lyv7nk7S", + "official_contract": { + "idl": "idls/metadata.ProgM6JCCvbYkfKqJYHePx4xxSUSqJp7rh8Lyv7nk7S.solana_program_metadata.V0_0_0.from_github_solana_program.json", + "repository": "solana-program/program-metadata", + "account_data_max_bytes": 10485760 + }, + "accounts": [ + { + "name": "empty", + "discriminator": 0, + "status": "rejected_uninitialized" + }, + { + "name": "buffer", + "discriminator": 1, + "header_bytes": 96, + "minimum_bytes": 96, + "fields": { + "program": "zeroable_pubkey", + "authority": "zeroable_pubkey", + "canonical": "nonzero_u8_is_true", + "seed_bytes": 16, + "padding_bytes": 14, + "data": "all_allocated_bytes_after_header" + }, + "pda_validation": "not_claimed_after_mutable_authority_or_keypair_allocation" + }, + { + "name": "metadata", + "discriminator": 2, + "header_bytes": 96, + "minimum_bytes": 96, + "fields": { + "program": "pubkey", + "authority": "zeroable_pubkey", + "mutable": "nonzero_u8_is_true", + "canonical": "nonzero_u8_is_true", + "seed_bytes": 16, + "encoding": "closed_u8_enum", + "compression": "closed_u8_enum", + "format": "closed_u8_enum", + "data_source": "closed_u8_enum", + "data_length": "u32_little_endian", + "padding_bytes": 5 + }, + "pda_validation": { + "canonical": [ + "program", + "seed" + ], + "non_canonical": [ + "program", + "authority", + "seed" + ] + }, + "trailing_bytes": "accepted_as_allocated_capacity_after_data_length" + } + ], + "data_sources": [ + { + "name": "direct", + "discriminator": 0, + "length": "nonzero_bounded_by_allocated_account_data", + "projection": "raw_on_chain_bytes" + }, + { + "name": "url", + "discriminator": 1, + "length": "nonzero_bounded_by_allocated_account_data", + "projection": "utf8_on_chain_string_without_fetch" + }, + { + "name": "external", + "discriminator": 2, + "length": "exactly_40_bytes", + "projection": "address_offset_zeroable_length_without_account_read" + } + ], + "failure_policy": { + "foreign_owner": "error", + "empty_data": "error", + "unknown_discriminator": "error", + "truncated_header": "error", + "logical_data_exceeds_allocation": "error", + "unknown_closed_enum": "error", + "invalid_url_utf8": "error", + "missing_non_canonical_authority": "error", + "invalid_metadata_pda": "error", + "off_chain_resolution": "forbidden" + } +}