From a818c4a60e4ede649dace8498d7b746bb4aabccc Mon Sep 17 00:00:00 2001 From: SinuS Von SifriduS Date: Fri, 24 Jul 2026 17:25:51 +0200 Subject: [PATCH] v0.1.0-pre.015 --- CHANGELOG.md | 11 +- README.md | 4 +- ROADMAP.md | 19 +- kb-lib/Cargo.toml | 8 +- kb-lib/README.md | 8 +- kb-lib/src/executor.rs | 294 +- kb-lib/src/executor/solana.rs | 290 +- kb-lib/src/executor/solana/core.rs | 365 ++- .../solana/core/address_lookup_table.rs | 594 ++++ kb-lib/src/executor/solana/core/builder.rs | 2223 ++++++++++++++ kb-lib/src/executor/solana/core/constants.rs | 7 + kb-lib/src/executor/solana/core/executor.rs | 658 +++++ kb-lib/src/executor/solana/core/intent.rs | 2604 +++++++++++++++++ kb-lib/src/executor/solana/core/loader_v3.rs | 1139 +++++++ kb-lib/src/executor/solana/core/loader_v4.rs | 890 ++++++ .../src/executor/solana/core/native_admin.rs | 549 ++++ .../src/executor/solana/core/precompiles.rs | 1247 ++++++++ kb-lib/src/executor/solana/core/slashing.rs | 499 ++++ kb-lib/src/executor/solana/core/stake.rs | 2021 +++++++++++++ kb-lib/src/executor/solana/core/validation.rs | 216 ++ kb-lib/src/executor/solana/core/vote.rs | 2358 +++++++++++++++ kb-lib/src/executor/solana/core/zk_elgamal.rs | 451 +++ kb-lib/src/lib.rs | 306 +- kb-lib/tests/external_executor_api.rs | 27 +- scripts/audit_khadhroony_workspace_rules.py | 6 +- scripts/audit_rust_general_rules.py | 32 +- 26 files changed, 16723 insertions(+), 103 deletions(-) create mode 100644 kb-lib/src/executor/solana/core/address_lookup_table.rs create mode 100644 kb-lib/src/executor/solana/core/builder.rs create mode 100644 kb-lib/src/executor/solana/core/constants.rs create mode 100644 kb-lib/src/executor/solana/core/executor.rs create mode 100644 kb-lib/src/executor/solana/core/intent.rs create mode 100644 kb-lib/src/executor/solana/core/loader_v3.rs create mode 100644 kb-lib/src/executor/solana/core/loader_v4.rs create mode 100644 kb-lib/src/executor/solana/core/native_admin.rs create mode 100644 kb-lib/src/executor/solana/core/precompiles.rs create mode 100644 kb-lib/src/executor/solana/core/slashing.rs create mode 100644 kb-lib/src/executor/solana/core/stake.rs create mode 100644 kb-lib/src/executor/solana/core/validation.rs create mode 100644 kb-lib/src/executor/solana/core/vote.rs create mode 100644 kb-lib/src/executor/solana/core/zk_elgamal.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c706c7..e14f8a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,17 @@ - + # CHANGELOG +## 0.1.0-pre.015 + +- Portage fonctionnel complet de l’exécuteur Solana Core dans `kb-lib`. +- Conservation des 109 opérations validées dans bot2 sur 18 surfaces natives. +- Préfixage bot3 des contrats publics avec `ExSolanaCore*` et `EX_SOLANA_CORE_*`. +- Conservation des deux contrats `ExApiTypedInstructionExecutor` et `ExApiInstructionExecutor`. +- Regroupement canonique des réexports : bloc `pub use`, puis bloc `pub(crate) use`. +- Réduction de l’inventaire réservé de 104 à 103 exécuteurs. + ## 0.1.0-pre.014 - Vérification de la parité exacte des contrats publics `ExApi*` avec `kb_execution_api` de bot2. diff --git a/README.md b/README.md index 2812a7d..734ba81 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ - + # Khadhroony Bot3 @@ -85,3 +85,5 @@ Les modules de matérialisation sont privés. Tous les contrats, types concrets `0.1.0-pre.013` remplace les 14 dernières frontières temporaires de matérialisation par des squelettes `Mt*Materializer`. Ils conservent le contrat historique `MtMaterializer`, implémentent également `MtApiEventMaterializer`, restent volontairement inactifs et ne produisent aucun faux événement via l’API bot3. Tous les types sont accessibles exclusivement depuis la façade `kb_lib`. `0.1.0-pre.014` confirme la parité de l’API publique `ExApi*` avec bot2 et remplace les 104 frontières temporaires d’exécution par des squelettes `Ex*Executor`. Chaque squelette référence uniquement ses Program IDs enregistrés, annonce `Maybe` sur sa surface et construit exclusivement un plan réservé à zéro instruction. Les exécuteurs opérationnels seront portés séparément, en commençant par Solana Core puis SPL. + +`0.1.0-pre.015` rend `ExSolanaCoreExecutor` fonctionnel. Il construit des plans déterministes pour 109 opérations natives réparties sur 18 surfaces, conserve les refus explicites des programmes historiques non exécutables et expose ses intents sous les préfixes bot3 `ExSolanaCore*` et `EX_SOLANA_CORE_*`. Les 103 autres exécuteurs restent réservés jusqu’à leur port fonctionnel. diff --git a/ROADMAP.md b/ROADMAP.md index 95baacd..175f441 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,5 +1,5 @@ - + # ROADMAP — khadhroony-bot3 @@ -176,5 +176,18 @@ - [x] Maintenir les squelettes inactifs : support `Maybe` et plan explicite à zéro instruction. - [x] Exposer les 104 types uniquement depuis la façade `kb-lib/src/lib.rs`. - [x] Ajouter un test central couvrant les 104 squelettes et étendre l’audit contre le retour des marqueurs temporaires. -- [ ] Valider le nouveau test unitaire, le test aval, Clippy et le workspace avec Cargo sur la machine de développement. -- [ ] Porter ensuite les exécuteurs fonctionnels déjà validés dans bot2, en commençant par Solana Core puis SPL. +- [x] Valider le nouveau test unitaire, le test aval, Clippy et le workspace avec Cargo sur la machine de développement. +- [x] Porter ensuite l’exécuteur Solana Core déjà validé dans bot2. + +### 0.1.0-pre.015 — Exécuteur Solana Core + +- [x] Porter les 109 opérations typées de `kb_executor_solana_core`. +- [x] Couvrir System, Compute Budget, Address Lookup Table, précompiles, Config, Feature, Slashing, ZK ElGamal, Stake, Vote et Loaders v3/v4. +- [x] Conserver les surfaces historiques non exécutables en refus explicite. +- [x] Préfixer les types publics `ExSolanaCore*` et les constantes `EX_SOLANA_CORE_*`. +- [x] Préserver le pont JSON historique et le contrat de plan préparé typé. +- [x] Étendre le test aval à une construction Compute Budget réelle. +- [x] Réduire les frontières réservées à 103 et adapter l’audit. +- [x] Imposer les blocs `pub use` puis `pub(crate) use`, séparés par une ligne vide. +- [ ] Valider les tests portés, Clippy et le workspace avec Cargo sur la machine de développement. +- [ ] Porter ensuite les exécuteurs SPL déjà validés dans bot2. diff --git a/kb-lib/Cargo.toml b/kb-lib/Cargo.toml index b519a59..fda9eeb 100644 --- a/kb-lib/Cargo.toml +++ b/kb-lib/Cargo.toml @@ -1,5 +1,5 @@ # file: kb-lib/Cargo.toml -# version: 8 +# version: 9 [package] name = "kb-lib" @@ -21,6 +21,9 @@ serde_json.workspace = true sha2.workspace = true solana-address-lookup-table-interface.workspace = true solana-compute-budget-interface.workspace = true +solana-feature-gate-interface.workspace = true +solana-hash.workspace = true +solana-instruction.workspace = true solana-loader-v3-interface.workspace = true solana-pubkey.workspace = true solana-sdk-ids.workspace = true @@ -34,6 +37,9 @@ ts-rs.workspace = true wincode.workspace = true [dev-dependencies] +solana-ed25519-program.workspace = true +solana-secp256k1-program.workspace = true +solana-secp256r1-program.workspace = true spl-elgamal-registry-interface.workspace = true spl-memo-interface.workspace = true spl-token-2022-interface.workspace = true diff --git a/kb-lib/README.md b/kb-lib/README.md index 2c67303..4745d8f 100644 --- a/kb-lib/README.md +++ b/kb-lib/README.md @@ -1,5 +1,5 @@ - + # kb-lib @@ -114,6 +114,9 @@ politique d’exécution définitivement interdite. La matrice normative est - les 98 types réservés `Dc*Decoder` : squelettes compatibles avec `DcApiProtocolDecoder`, exposant 97 Program IDs enregistrés et la frontière générique Anchor sans Program ID ; - `DcApiInstructionDecoder` : contrat de reconnaissance, couverture et décodage contextualisé ; - `DcApiProtocolDecoder` : contrat de compatibilité avec les observations historiques ; +- `ExSolanaCoreExecutor`, `ExSolanaCoreExecutionIntent` et `ExSolanaCoreOperation` : exécuteur natif fonctionnel et contrat typé couvrant 109 opérations ; +- les constantes `EX_SOLANA_CORE_*_OPERATION` : codes stables des opérations System, Compute Budget, ALT, précompiles, Config, Feature, Slashing, ZK ElGamal, Stake, Vote et Loaders ; +- `ExApiTypedInstructionExecutor` et `ExApiInstructionExecutor` : contrat typé actuel et pont JSON historique de l’exécution ; - `MdCoreInstructionReplayInput` : input source-neutral produit par l’extraction core ; - `DcApiDecoderExecutionResult`, `DcApiDecoderRecognition` et `DcApiDecoderCoverageDeclaration` : résultats typés du pipeline ; - modèles canoniques et nomenclature réexportés depuis la façade. @@ -136,5 +139,6 @@ L’appel de décodage complet utilise `DcApiInstructionDecoder::decode` après - Une transaction échouée peut produire une intention structurée, jamais une mutation commitée. - Un matérialiseur externe dépend uniquement des contrats et modèles publics de `kb-lib`, jamais d’un décodeur concret. - Les squelettes réservés répondent uniquement `Maybe` pour leur Program ID et retournent une liste vide ; ils ne prétendent pas décoder une surface avant son port fonctionnel. -- Les squelettes d’exécuteurs annoncent uniquement `Maybe` pour leurs Program IDs enregistrés et construisent un plan réservé à zéro instruction. +- Les 103 squelettes d’exécuteurs encore réservés annoncent uniquement `Maybe` pour leurs Program IDs enregistrés et construisent un plan réservé à zéro instruction. +- `ExSolanaCoreExecutor` annonce uniquement des capacités exactes `Supported` ou `Unsupported` et ne signe, n’envoie ni ne simule aucune transaction. - Les exécuteurs fonctionnels sont portés dans des tranches séparées, sans dépendre des décodeurs. diff --git a/kb-lib/src/executor.rs b/kb-lib/src/executor.rs index e4c7917..6d1af26 100644 --- a/kb-lib/src/executor.rs +++ b/kb-lib/src/executor.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/executor.rs -// version: 2 +// version: 3 //! Consolidated executor modules. @@ -236,8 +236,254 @@ pub use self::router::ExRouterOkxLabsV1Executor; pub use self::router::ExRouterOkxLabsV2Executor; /// Exposes the reserved `rwa/ondo_global_markets` executor. pub use self::rwa::ExRwaOndoGlobalMarketsExecutor; -/// Exposes the reserved `solana/core` executor. +/// Exposes the Address Lookup Table close operation code. +pub use self::solana::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_CLOSE_OPERATION; +/// Exposes the Address Lookup Table create operation code. +pub use self::solana::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_CREATE_OPERATION; +/// Exposes the Address Lookup Table deactivate operation code. +pub use self::solana::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_DEACTIVATE_OPERATION; +/// Exposes the Address Lookup Table extend operation code. +pub use self::solana::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_EXTEND_OPERATION; +/// Exposes the Address Lookup Table freeze operation code. +pub use self::solana::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_FREEZE_OPERATION; +/// Exposes the Compute Budget heap-frame operation code. +pub use self::solana::EX_SOLANA_CORE_COMPUTE_BUDGET_REQUEST_HEAP_FRAME_OPERATION; +/// Exposes the Compute Budget loaded-account data-size operation code. +pub use self::solana::EX_SOLANA_CORE_COMPUTE_BUDGET_SET_LOADED_ACCOUNTS_DATA_SIZE_LIMIT_OPERATION; +/// Exposes the Compute Budget unit-limit operation code. +pub use self::solana::EX_SOLANA_CORE_COMPUTE_BUDGET_SET_UNIT_LIMIT_OPERATION; +/// Exposes the Compute Budget unit-price operation code. +pub use self::solana::EX_SOLANA_CORE_COMPUTE_BUDGET_SET_UNIT_PRICE_OPERATION; +/// Exposes the Config account creation operation code. +pub use self::solana::EX_SOLANA_CORE_CONFIG_CREATE_ACCOUNT_OPERATION; +/// Exposes the Config store operation code. +pub use self::solana::EX_SOLANA_CORE_CONFIG_STORE_OPERATION; +/// Exposes the Ed25519 inline verification operation code. +pub use self::solana::EX_SOLANA_CORE_ED25519_VERIFY_INLINE_OPERATION; +/// Exposes the Ed25519 offset-table verification operation code. +pub use self::solana::EX_SOLANA_CORE_ED25519_VERIFY_OFFSETS_OPERATION; +/// Exposes the feature activation operation code. +pub use self::solana::EX_SOLANA_CORE_FEATURE_ACTIVATE_OPERATION; +/// Exposes the feature pending-activation revoke operation code. +pub use self::solana::EX_SOLANA_CORE_FEATURE_REVOKE_PENDING_ACTIVATION_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::solana::EX_SOLANA_CORE_LOADER_V3_CLOSE_ANY_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::solana::EX_SOLANA_CORE_LOADER_V3_CLOSE_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::solana::EX_SOLANA_CORE_LOADER_V3_CREATE_BUFFER_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::solana::EX_SOLANA_CORE_LOADER_V3_DEPLOY_WITH_MAX_PROGRAM_LEN_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::solana::EX_SOLANA_CORE_LOADER_V3_EXTEND_PROGRAM_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::solana::EX_SOLANA_CORE_LOADER_V3_SET_BUFFER_AUTHORITY_CHECKED_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::solana::EX_SOLANA_CORE_LOADER_V3_SET_BUFFER_AUTHORITY_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::solana::EX_SOLANA_CORE_LOADER_V3_SET_UPGRADE_AUTHORITY_CHECKED_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::solana::EX_SOLANA_CORE_LOADER_V3_SET_UPGRADE_AUTHORITY_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::solana::EX_SOLANA_CORE_LOADER_V3_UPGRADE_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::solana::EX_SOLANA_CORE_LOADER_V3_WRITE_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::solana::EX_SOLANA_CORE_LOADER_V4_COPY_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::solana::EX_SOLANA_CORE_LOADER_V4_CREATE_BUFFER_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::solana::EX_SOLANA_CORE_LOADER_V4_DEPLOY_FROM_SOURCE_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::solana::EX_SOLANA_CORE_LOADER_V4_DEPLOY_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::solana::EX_SOLANA_CORE_LOADER_V4_FINALIZE_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::solana::EX_SOLANA_CORE_LOADER_V4_RETRACT_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::solana::EX_SOLANA_CORE_LOADER_V4_SET_PROGRAM_LENGTH_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::solana::EX_SOLANA_CORE_LOADER_V4_TRANSFER_AUTHORITY_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::solana::EX_SOLANA_CORE_LOADER_V4_WRITE_OPERATION; +/// Exposes every stable callable Solana core operation code. +pub use self::solana::EX_SOLANA_CORE_OPERATION_CODES; +/// Exposes the secp256k1 inline verification operation code. +pub use self::solana::EX_SOLANA_CORE_SECP256K1_VERIFY_INLINE_OPERATION; +/// Exposes the secp256k1 offset-table verification operation code. +pub use self::solana::EX_SOLANA_CORE_SECP256K1_VERIFY_OFFSETS_OPERATION; +/// Exposes the secp256r1 inline verification operation code. +pub use self::solana::EX_SOLANA_CORE_SECP256R1_VERIFY_INLINE_OPERATION; +/// Exposes the secp256r1 offset-table verification operation code. +pub use self::solana::EX_SOLANA_CORE_SECP256R1_VERIFY_OFFSETS_OPERATION; +/// Exposes the Slashing violation-report close operation code. +pub use self::solana::EX_SOLANA_CORE_SLASHING_CLOSE_VIOLATION_REPORT_OPERATION; +/// Exposes the Slashing duplicate-block proof submission operation code. +pub use self::solana::EX_SOLANA_CORE_SLASHING_SUBMIT_DUPLICATE_BLOCK_PROOF_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::solana::EX_SOLANA_CORE_STAKE_AUTHORIZE_CHECKED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::solana::EX_SOLANA_CORE_STAKE_AUTHORIZE_CHECKED_WITH_SEED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::solana::EX_SOLANA_CORE_STAKE_AUTHORIZE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::solana::EX_SOLANA_CORE_STAKE_AUTHORIZE_WITH_SEED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::solana::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_AND_DELEGATE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::solana::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_CHECKED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::solana::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::solana::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_WITH_SEED_AND_DELEGATE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::solana::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_WITH_SEED_CHECKED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::solana::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_WITH_SEED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::solana::EX_SOLANA_CORE_STAKE_DEACTIVATE_DELINQUENT_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::solana::EX_SOLANA_CORE_STAKE_DEACTIVATE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::solana::EX_SOLANA_CORE_STAKE_DELEGATE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::solana::EX_SOLANA_CORE_STAKE_GET_MINIMUM_DELEGATION_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::solana::EX_SOLANA_CORE_STAKE_INITIALIZE_CHECKED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::solana::EX_SOLANA_CORE_STAKE_INITIALIZE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::solana::EX_SOLANA_CORE_STAKE_MERGE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::solana::EX_SOLANA_CORE_STAKE_MOVE_LAMPORTS_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::solana::EX_SOLANA_CORE_STAKE_MOVE_STAKE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::solana::EX_SOLANA_CORE_STAKE_SET_LOCKUP_CHECKED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::solana::EX_SOLANA_CORE_STAKE_SET_LOCKUP_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::solana::EX_SOLANA_CORE_STAKE_SPLIT_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::solana::EX_SOLANA_CORE_STAKE_SPLIT_WITH_SEED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::solana::EX_SOLANA_CORE_STAKE_WITHDRAW_OPERATION; +/// Exposes the System allocate operation code. +pub use self::solana::EX_SOLANA_CORE_SYSTEM_ALLOCATE_OPERATION; +/// Exposes the System seeded allocate operation code. +pub use self::solana::EX_SOLANA_CORE_SYSTEM_ALLOCATE_WITH_SEED_OPERATION; +/// Exposes the System assign operation code. +pub use self::solana::EX_SOLANA_CORE_SYSTEM_ASSIGN_OPERATION; +/// Exposes the System seeded assign operation code. +pub use self::solana::EX_SOLANA_CORE_SYSTEM_ASSIGN_WITH_SEED_OPERATION; +/// Exposes the System prefunded create-account operation code. +pub use self::solana::EX_SOLANA_CORE_SYSTEM_CREATE_ACCOUNT_ALLOW_PREFUND_OPERATION; +/// Exposes the System create-account operation code. +pub use self::solana::EX_SOLANA_CORE_SYSTEM_CREATE_ACCOUNT_OPERATION; +/// Exposes the System seeded create-account operation code. +pub use self::solana::EX_SOLANA_CORE_SYSTEM_CREATE_ACCOUNT_WITH_SEED_OPERATION; +/// Exposes the durable nonce advance operation code. +pub use self::solana::EX_SOLANA_CORE_SYSTEM_NONCE_ADVANCE_OPERATION; +/// Exposes the durable nonce authorize operation code. +pub use self::solana::EX_SOLANA_CORE_SYSTEM_NONCE_AUTHORIZE_OPERATION; +/// Exposes the durable nonce account creation operation code. +pub use self::solana::EX_SOLANA_CORE_SYSTEM_NONCE_CREATE_OPERATION; +/// Exposes the seeded durable nonce account creation operation code. +pub use self::solana::EX_SOLANA_CORE_SYSTEM_NONCE_CREATE_WITH_SEED_OPERATION; +/// Exposes the durable nonce initialize operation code. +pub use self::solana::EX_SOLANA_CORE_SYSTEM_NONCE_INITIALIZE_OPERATION; +/// Exposes the durable nonce upgrade operation code. +pub use self::solana::EX_SOLANA_CORE_SYSTEM_NONCE_UPGRADE_OPERATION; +/// Exposes the durable nonce withdraw operation code. +pub use self::solana::EX_SOLANA_CORE_SYSTEM_NONCE_WITHDRAW_OPERATION; +/// Exposes the System multi-transfer operation code. +pub use self::solana::EX_SOLANA_CORE_SYSTEM_TRANSFER_MANY_OPERATION; +/// Exposes the System transfer operation code. +pub use self::solana::EX_SOLANA_CORE_SYSTEM_TRANSFER_OPERATION; +/// Exposes the System seeded transfer operation code. +pub use self::solana::EX_SOLANA_CORE_SYSTEM_TRANSFER_WITH_SEED_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::solana::EX_SOLANA_CORE_VOTE_AUTHORIZE_CHECKED_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::solana::EX_SOLANA_CORE_VOTE_AUTHORIZE_CHECKED_WITH_SEED_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::solana::EX_SOLANA_CORE_VOTE_AUTHORIZE_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::solana::EX_SOLANA_CORE_VOTE_AUTHORIZE_WITH_SEED_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::solana::EX_SOLANA_CORE_VOTE_COMPACT_UPDATE_VOTE_STATE_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::solana::EX_SOLANA_CORE_VOTE_COMPACT_UPDATE_VOTE_STATE_SWITCH_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::solana::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::solana::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_V2_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::solana::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_WITH_SEED_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::solana::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_WITH_SEED_V2_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::solana::EX_SOLANA_CORE_VOTE_DEPOSIT_DELEGATOR_REWARDS_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::solana::EX_SOLANA_CORE_VOTE_INITIALIZE_ACCOUNT_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::solana::EX_SOLANA_CORE_VOTE_INITIALIZE_ACCOUNT_V2_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::solana::EX_SOLANA_CORE_VOTE_SUBMIT_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::solana::EX_SOLANA_CORE_VOTE_SWITCH_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::solana::EX_SOLANA_CORE_VOTE_TOWER_SYNC_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::solana::EX_SOLANA_CORE_VOTE_TOWER_SYNC_SWITCH_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::solana::EX_SOLANA_CORE_VOTE_UPDATE_COMMISSION_BPS_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::solana::EX_SOLANA_CORE_VOTE_UPDATE_COMMISSION_COLLECTOR_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::solana::EX_SOLANA_CORE_VOTE_UPDATE_COMMISSION_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::solana::EX_SOLANA_CORE_VOTE_UPDATE_VALIDATOR_IDENTITY_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::solana::EX_SOLANA_CORE_VOTE_UPDATE_VOTE_STATE_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::solana::EX_SOLANA_CORE_VOTE_UPDATE_VOTE_STATE_SWITCH_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::solana::EX_SOLANA_CORE_VOTE_WITHDRAW_OPERATION; +/// Exposes the ZK ElGamal context-state close operation code. +pub use self::solana::EX_SOLANA_CORE_ZK_ELGAMAL_CLOSE_CONTEXT_STATE_OPERATION; +/// Exposes the ZK ElGamal proof-account verification operation code. +pub use self::solana::EX_SOLANA_CORE_ZK_ELGAMAL_VERIFY_FROM_ACCOUNT_OPERATION; +/// Exposes the ZK ElGamal inline proof verification operation code. +pub use self::solana::EX_SOLANA_CORE_ZK_ELGAMAL_VERIFY_INLINE_OPERATION; +/// Exposes one Config Program key entry. +pub use self::solana::ExSolanaCoreConfigKey; +/// Exposes one Ed25519 verification offset record. +pub use self::solana::ExSolanaCoreEd25519VerificationOffsets; +/// Exposes the complete typed Solana core intent. +pub use self::solana::ExSolanaCoreExecutionIntent; +/// Exposes the Solana core executor. pub use self::solana::ExSolanaCoreExecutor; +/// Exposes typed Solana core operation arguments. +pub use self::solana::ExSolanaCoreOperation; +/// Exposes one secp256k1 verification offset record. +pub use self::solana::ExSolanaCoreSecp256k1VerificationOffsets; +/// Exposes one secp256r1 verification offset record. +pub use self::solana::ExSolanaCoreSecp256r1VerificationOffsets; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::solana::ExSolanaCoreStakeAuthorizationKind; +/// Exposes one recipient in a System multi-transfer. +pub use self::solana::ExSolanaCoreSystemTransferRecipient; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::solana::ExSolanaCoreVoteAuthorizationKind; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::solana::ExSolanaCoreVoteCommissionKind; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::solana::ExSolanaCoreVoteLockout; +/// Exposes optional ZK ElGamal context-state accounts. +pub use self::solana::ExSolanaCoreZkElGamalContextState; +/// Exposes the official ZK ElGamal proof kinds. +pub use self::solana::ExSolanaCoreZkElGamalProofType; /// Exposes the reserved `spl/account_compression` executor. pub use self::spl::ExSplAccountCompressionExecutor; /// Exposes the reserved `spl/associated_token_account` executor. @@ -291,11 +537,50 @@ pub use self::wallet::ExWalletJupiterApeproSmartWalletExecutor; /// Exposes the reserved `weighted/swap_stabble` executor. pub use self::weighted::ExWeightedSwapStabbleExecutor; +/// Canonical tracing target for this crate. +pub(crate) use self::solana::EX_SOLANA_CORE_TRACING_TARGET; +/// Crate-root access to `build_prepared_plan` from `address_lookup_table`. +pub(crate) use self::solana::executor_solana_core_address_lookup_table_build_prepared_plan; +/// Crate-root access to `build_prepared_plan` from `builder`. +pub(crate) use self::solana::executor_solana_core_builder_build_prepared_plan; +/// Crate-root access to `finalize_prepared_plan` from `builder`. +pub(crate) use self::solana::executor_solana_core_finalize_prepared_plan; +/// Builds one typed validation error without duplicating domain-local wrappers. +pub(crate) use self::solana::executor_solana_core_invalid; +/// Crate-root access to `build_prepared_plan` from `loader_v3`. +pub(crate) use self::solana::executor_solana_core_loader_v3_build_prepared_plan; +/// Crate-root access to `build_prepared_plan` from `loader_v4`. +pub(crate) use self::solana::executor_solana_core_loader_v4_build_prepared_plan; +/// Crate-root access to `build_prepared_plan` from `native_admin`. +pub(crate) use self::solana::executor_solana_core_native_admin_build_prepared_plan; +/// Parses one canonical native program identifier. +pub(crate) use self::solana::executor_solana_core_parse_native_program_id; +/// Parses one model public key with the canonical execution error contract. +pub(crate) use self::solana::executor_solana_core_parse_pubkey; +/// Crate-root access to `build_prepared_plan` from `precompiles`. +pub(crate) use self::solana::executor_solana_core_precompiles_build_prepared_plan; +/// Crate-root access to `build_prepared_plan` from `slashing`. +pub(crate) use self::solana::executor_solana_core_slashing_build_prepared_plan; +/// Crate-root access to `build_prepared_plan` from `stake`. +pub(crate) use self::solana::executor_solana_core_stake_build_prepared_plan; +/// Validates that two model public keys identify distinct accounts. +pub(crate) use self::solana::executor_solana_core_validate_distinct_pubkeys; +/// Validates an exact byte-slice length while preserving the caller error code. +pub(crate) use self::solana::executor_solana_core_validate_exact_length; +/// Validates that an unsigned execution amount is strictly positive. +pub(crate) use self::solana::executor_solana_core_validate_positive_u64; +/// Validates one `Pubkey::create_with_seed` derivation with caller-owned error codes. +pub(crate) use self::solana::executor_solana_core_validate_seeded_address; +/// Crate-root access to `build_prepared_plan` from `vote`. +pub(crate) use self::solana::executor_solana_core_vote_build_prepared_plan; +/// Crate-root access to `build_prepared_plan` from `zk_elgamal`. +pub(crate) use self::solana::executor_solana_core_zk_elgamal_build_prepared_plan; + #[cfg(test)] mod reserved_executor_tests { #[test] fn every_reserved_executor_is_registered_and_builds_only_an_empty_plan() { - let executors: [&dyn crate::ExApiInstructionExecutor; 104] = [ + let executors: [&dyn crate::ExApiInstructionExecutor; 103] = [ &crate::ExAdapterSaberDecimalWrapperExecutor, &crate::ExAdminJupiterLockExecutor, &crate::ExAdminPumpFeesExecutor, @@ -373,7 +658,6 @@ mod reserved_executor_tests { &crate::ExRouterOkxLabsV1Executor, &crate::ExRouterOkxLabsV2Executor, &crate::ExRwaOndoGlobalMarketsExecutor, - &crate::ExSolanaCoreExecutor, &crate::ExSplAccountCompressionExecutor, &crate::ExSplAssociatedTokenAccountExecutor, &crate::ExSplElgamalRegistryExecutor, @@ -401,7 +685,7 @@ mod reserved_executor_tests { &crate::ExWalletJupiterApeproSmartWalletExecutor, &crate::ExWeightedSwapStabbleExecutor, ]; - assert_eq!(executors.len(), 104); + assert_eq!(executors.len(), 103); for executor in executors { assert!(!executor.program_ids().is_empty()); for program_id in executor.program_ids() { diff --git a/kb-lib/src/executor/solana.rs b/kb-lib/src/executor/solana.rs index ecfd9e4..f283501 100644 --- a/kb-lib/src/executor/solana.rs +++ b/kb-lib/src/executor/solana.rs @@ -1,8 +1,294 @@ // file: kb-lib/src/executor/solana.rs -// version: 2 +// version: 3 //! `solana` executor family. mod core; -/// Exposes the reserved `solana/core` executor. + +/// Exposes the Address Lookup Table close operation code. +pub use self::core::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_CLOSE_OPERATION; +/// Exposes the Address Lookup Table create operation code. +pub use self::core::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_CREATE_OPERATION; +/// Exposes the Address Lookup Table deactivate operation code. +pub use self::core::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_DEACTIVATE_OPERATION; +/// Exposes the Address Lookup Table extend operation code. +pub use self::core::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_EXTEND_OPERATION; +/// Exposes the Address Lookup Table freeze operation code. +pub use self::core::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_FREEZE_OPERATION; +/// Exposes the Compute Budget heap-frame operation code. +pub use self::core::EX_SOLANA_CORE_COMPUTE_BUDGET_REQUEST_HEAP_FRAME_OPERATION; +/// Exposes the Compute Budget loaded-account data-size operation code. +pub use self::core::EX_SOLANA_CORE_COMPUTE_BUDGET_SET_LOADED_ACCOUNTS_DATA_SIZE_LIMIT_OPERATION; +/// Exposes the Compute Budget unit-limit operation code. +pub use self::core::EX_SOLANA_CORE_COMPUTE_BUDGET_SET_UNIT_LIMIT_OPERATION; +/// Exposes the Compute Budget unit-price operation code. +pub use self::core::EX_SOLANA_CORE_COMPUTE_BUDGET_SET_UNIT_PRICE_OPERATION; +/// Exposes the Config account creation operation code. +pub use self::core::EX_SOLANA_CORE_CONFIG_CREATE_ACCOUNT_OPERATION; +/// Exposes the Config store operation code. +pub use self::core::EX_SOLANA_CORE_CONFIG_STORE_OPERATION; +/// Exposes the Ed25519 inline verification operation code. +pub use self::core::EX_SOLANA_CORE_ED25519_VERIFY_INLINE_OPERATION; +/// Exposes the Ed25519 offset-table verification operation code. +pub use self::core::EX_SOLANA_CORE_ED25519_VERIFY_OFFSETS_OPERATION; +/// Exposes the feature activation operation code. +pub use self::core::EX_SOLANA_CORE_FEATURE_ACTIVATE_OPERATION; +/// Exposes the feature pending-activation revoke operation code. +pub use self::core::EX_SOLANA_CORE_FEATURE_REVOKE_PENDING_ACTIVATION_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::core::EX_SOLANA_CORE_LOADER_V3_CLOSE_ANY_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::core::EX_SOLANA_CORE_LOADER_V3_CLOSE_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::core::EX_SOLANA_CORE_LOADER_V3_CREATE_BUFFER_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::core::EX_SOLANA_CORE_LOADER_V3_DEPLOY_WITH_MAX_PROGRAM_LEN_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::core::EX_SOLANA_CORE_LOADER_V3_EXTEND_PROGRAM_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::core::EX_SOLANA_CORE_LOADER_V3_SET_BUFFER_AUTHORITY_CHECKED_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::core::EX_SOLANA_CORE_LOADER_V3_SET_BUFFER_AUTHORITY_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::core::EX_SOLANA_CORE_LOADER_V3_SET_UPGRADE_AUTHORITY_CHECKED_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::core::EX_SOLANA_CORE_LOADER_V3_SET_UPGRADE_AUTHORITY_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::core::EX_SOLANA_CORE_LOADER_V3_UPGRADE_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::core::EX_SOLANA_CORE_LOADER_V3_WRITE_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::core::EX_SOLANA_CORE_LOADER_V4_COPY_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::core::EX_SOLANA_CORE_LOADER_V4_CREATE_BUFFER_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::core::EX_SOLANA_CORE_LOADER_V4_DEPLOY_FROM_SOURCE_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::core::EX_SOLANA_CORE_LOADER_V4_DEPLOY_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::core::EX_SOLANA_CORE_LOADER_V4_FINALIZE_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::core::EX_SOLANA_CORE_LOADER_V4_RETRACT_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::core::EX_SOLANA_CORE_LOADER_V4_SET_PROGRAM_LENGTH_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::core::EX_SOLANA_CORE_LOADER_V4_TRANSFER_AUTHORITY_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::core::EX_SOLANA_CORE_LOADER_V4_WRITE_OPERATION; +/// Exposes every stable callable Solana core operation code. +pub use self::core::EX_SOLANA_CORE_OPERATION_CODES; +/// Exposes the secp256k1 inline verification operation code. +pub use self::core::EX_SOLANA_CORE_SECP256K1_VERIFY_INLINE_OPERATION; +/// Exposes the secp256k1 offset-table verification operation code. +pub use self::core::EX_SOLANA_CORE_SECP256K1_VERIFY_OFFSETS_OPERATION; +/// Exposes the secp256r1 inline verification operation code. +pub use self::core::EX_SOLANA_CORE_SECP256R1_VERIFY_INLINE_OPERATION; +/// Exposes the secp256r1 offset-table verification operation code. +pub use self::core::EX_SOLANA_CORE_SECP256R1_VERIFY_OFFSETS_OPERATION; +/// Exposes the Slashing violation-report close operation code. +pub use self::core::EX_SOLANA_CORE_SLASHING_CLOSE_VIOLATION_REPORT_OPERATION; +/// Exposes the Slashing duplicate-block proof submission operation code. +pub use self::core::EX_SOLANA_CORE_SLASHING_SUBMIT_DUPLICATE_BLOCK_PROOF_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::core::EX_SOLANA_CORE_STAKE_AUTHORIZE_CHECKED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::core::EX_SOLANA_CORE_STAKE_AUTHORIZE_CHECKED_WITH_SEED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::core::EX_SOLANA_CORE_STAKE_AUTHORIZE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::core::EX_SOLANA_CORE_STAKE_AUTHORIZE_WITH_SEED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::core::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_AND_DELEGATE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::core::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_CHECKED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::core::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::core::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_WITH_SEED_AND_DELEGATE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::core::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_WITH_SEED_CHECKED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::core::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_WITH_SEED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::core::EX_SOLANA_CORE_STAKE_DEACTIVATE_DELINQUENT_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::core::EX_SOLANA_CORE_STAKE_DEACTIVATE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::core::EX_SOLANA_CORE_STAKE_DELEGATE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::core::EX_SOLANA_CORE_STAKE_GET_MINIMUM_DELEGATION_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::core::EX_SOLANA_CORE_STAKE_INITIALIZE_CHECKED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::core::EX_SOLANA_CORE_STAKE_INITIALIZE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::core::EX_SOLANA_CORE_STAKE_MERGE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::core::EX_SOLANA_CORE_STAKE_MOVE_LAMPORTS_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::core::EX_SOLANA_CORE_STAKE_MOVE_STAKE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::core::EX_SOLANA_CORE_STAKE_SET_LOCKUP_CHECKED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::core::EX_SOLANA_CORE_STAKE_SET_LOCKUP_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::core::EX_SOLANA_CORE_STAKE_SPLIT_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::core::EX_SOLANA_CORE_STAKE_SPLIT_WITH_SEED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::core::EX_SOLANA_CORE_STAKE_WITHDRAW_OPERATION; +/// Exposes the System allocate operation code. +pub use self::core::EX_SOLANA_CORE_SYSTEM_ALLOCATE_OPERATION; +/// Exposes the System seeded allocate operation code. +pub use self::core::EX_SOLANA_CORE_SYSTEM_ALLOCATE_WITH_SEED_OPERATION; +/// Exposes the System assign operation code. +pub use self::core::EX_SOLANA_CORE_SYSTEM_ASSIGN_OPERATION; +/// Exposes the System seeded assign operation code. +pub use self::core::EX_SOLANA_CORE_SYSTEM_ASSIGN_WITH_SEED_OPERATION; +/// Exposes the System prefunded create-account operation code. +pub use self::core::EX_SOLANA_CORE_SYSTEM_CREATE_ACCOUNT_ALLOW_PREFUND_OPERATION; +/// Exposes the System create-account operation code. +pub use self::core::EX_SOLANA_CORE_SYSTEM_CREATE_ACCOUNT_OPERATION; +/// Exposes the System seeded create-account operation code. +pub use self::core::EX_SOLANA_CORE_SYSTEM_CREATE_ACCOUNT_WITH_SEED_OPERATION; +/// Exposes the durable nonce advance operation code. +pub use self::core::EX_SOLANA_CORE_SYSTEM_NONCE_ADVANCE_OPERATION; +/// Exposes the durable nonce authorize operation code. +pub use self::core::EX_SOLANA_CORE_SYSTEM_NONCE_AUTHORIZE_OPERATION; +/// Exposes the durable nonce account creation operation code. +pub use self::core::EX_SOLANA_CORE_SYSTEM_NONCE_CREATE_OPERATION; +/// Exposes the seeded durable nonce account creation operation code. +pub use self::core::EX_SOLANA_CORE_SYSTEM_NONCE_CREATE_WITH_SEED_OPERATION; +/// Exposes the durable nonce initialize operation code. +pub use self::core::EX_SOLANA_CORE_SYSTEM_NONCE_INITIALIZE_OPERATION; +/// Exposes the durable nonce upgrade operation code. +pub use self::core::EX_SOLANA_CORE_SYSTEM_NONCE_UPGRADE_OPERATION; +/// Exposes the durable nonce withdraw operation code. +pub use self::core::EX_SOLANA_CORE_SYSTEM_NONCE_WITHDRAW_OPERATION; +/// Exposes the System multi-transfer operation code. +pub use self::core::EX_SOLANA_CORE_SYSTEM_TRANSFER_MANY_OPERATION; +/// Exposes the System transfer operation code. +pub use self::core::EX_SOLANA_CORE_SYSTEM_TRANSFER_OPERATION; +/// Exposes the System seeded transfer operation code. +pub use self::core::EX_SOLANA_CORE_SYSTEM_TRANSFER_WITH_SEED_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::core::EX_SOLANA_CORE_VOTE_AUTHORIZE_CHECKED_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::core::EX_SOLANA_CORE_VOTE_AUTHORIZE_CHECKED_WITH_SEED_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::core::EX_SOLANA_CORE_VOTE_AUTHORIZE_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::core::EX_SOLANA_CORE_VOTE_AUTHORIZE_WITH_SEED_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::core::EX_SOLANA_CORE_VOTE_COMPACT_UPDATE_VOTE_STATE_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::core::EX_SOLANA_CORE_VOTE_COMPACT_UPDATE_VOTE_STATE_SWITCH_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::core::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::core::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_V2_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::core::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_WITH_SEED_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::core::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_WITH_SEED_V2_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::core::EX_SOLANA_CORE_VOTE_DEPOSIT_DELEGATOR_REWARDS_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::core::EX_SOLANA_CORE_VOTE_INITIALIZE_ACCOUNT_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::core::EX_SOLANA_CORE_VOTE_INITIALIZE_ACCOUNT_V2_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::core::EX_SOLANA_CORE_VOTE_SUBMIT_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::core::EX_SOLANA_CORE_VOTE_SWITCH_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::core::EX_SOLANA_CORE_VOTE_TOWER_SYNC_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::core::EX_SOLANA_CORE_VOTE_TOWER_SYNC_SWITCH_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::core::EX_SOLANA_CORE_VOTE_UPDATE_COMMISSION_BPS_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::core::EX_SOLANA_CORE_VOTE_UPDATE_COMMISSION_COLLECTOR_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::core::EX_SOLANA_CORE_VOTE_UPDATE_COMMISSION_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::core::EX_SOLANA_CORE_VOTE_UPDATE_VALIDATOR_IDENTITY_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::core::EX_SOLANA_CORE_VOTE_UPDATE_VOTE_STATE_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::core::EX_SOLANA_CORE_VOTE_UPDATE_VOTE_STATE_SWITCH_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::core::EX_SOLANA_CORE_VOTE_WITHDRAW_OPERATION; +/// Exposes the ZK ElGamal context-state close operation code. +pub use self::core::EX_SOLANA_CORE_ZK_ELGAMAL_CLOSE_CONTEXT_STATE_OPERATION; +/// Exposes the ZK ElGamal proof-account verification operation code. +pub use self::core::EX_SOLANA_CORE_ZK_ELGAMAL_VERIFY_FROM_ACCOUNT_OPERATION; +/// Exposes the ZK ElGamal inline proof verification operation code. +pub use self::core::EX_SOLANA_CORE_ZK_ELGAMAL_VERIFY_INLINE_OPERATION; +/// Exposes one Config Program key entry. +pub use self::core::ExSolanaCoreConfigKey; +/// Exposes one Ed25519 verification offset record. +pub use self::core::ExSolanaCoreEd25519VerificationOffsets; +/// Exposes the complete typed Solana core intent. +pub use self::core::ExSolanaCoreExecutionIntent; +/// Exposes the Solana core executor. pub use self::core::ExSolanaCoreExecutor; +/// Exposes typed Solana core operation arguments. +pub use self::core::ExSolanaCoreOperation; +/// Exposes one secp256k1 verification offset record. +pub use self::core::ExSolanaCoreSecp256k1VerificationOffsets; +/// Exposes one secp256r1 verification offset record. +pub use self::core::ExSolanaCoreSecp256r1VerificationOffsets; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::core::ExSolanaCoreStakeAuthorizationKind; +/// Exposes one recipient in a System multi-transfer. +pub use self::core::ExSolanaCoreSystemTransferRecipient; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::core::ExSolanaCoreVoteAuthorizationKind; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::core::ExSolanaCoreVoteCommissionKind; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::core::ExSolanaCoreVoteLockout; +/// Exposes optional ZK ElGamal context-state accounts. +pub use self::core::ExSolanaCoreZkElGamalContextState; +/// Exposes the official ZK ElGamal proof kinds. +pub use self::core::ExSolanaCoreZkElGamalProofType; + +/// Canonical tracing target for this crate. +pub(crate) use self::core::EX_SOLANA_CORE_TRACING_TARGET; +/// Crate-root access to `build_prepared_plan` from `address_lookup_table`. +pub(crate) use self::core::executor_solana_core_address_lookup_table_build_prepared_plan; +/// Crate-root access to `build_prepared_plan` from `builder`. +pub(crate) use self::core::executor_solana_core_builder_build_prepared_plan; +/// Crate-root access to `finalize_prepared_plan` from `builder`. +pub(crate) use self::core::executor_solana_core_finalize_prepared_plan; +/// Builds one typed validation error without duplicating domain-local wrappers. +pub(crate) use self::core::executor_solana_core_invalid; +/// Crate-root access to `build_prepared_plan` from `loader_v3`. +pub(crate) use self::core::executor_solana_core_loader_v3_build_prepared_plan; +/// Crate-root access to `build_prepared_plan` from `loader_v4`. +pub(crate) use self::core::executor_solana_core_loader_v4_build_prepared_plan; +/// Crate-root access to `build_prepared_plan` from `native_admin`. +pub(crate) use self::core::executor_solana_core_native_admin_build_prepared_plan; +/// Parses one canonical native program identifier. +pub(crate) use self::core::executor_solana_core_parse_native_program_id; +/// Parses one model public key with the canonical execution error contract. +pub(crate) use self::core::executor_solana_core_parse_pubkey; +/// Crate-root access to `build_prepared_plan` from `precompiles`. +pub(crate) use self::core::executor_solana_core_precompiles_build_prepared_plan; +/// Crate-root access to `build_prepared_plan` from `slashing`. +pub(crate) use self::core::executor_solana_core_slashing_build_prepared_plan; +/// Crate-root access to `build_prepared_plan` from `stake`. +pub(crate) use self::core::executor_solana_core_stake_build_prepared_plan; +/// Validates that two model public keys identify distinct accounts. +pub(crate) use self::core::executor_solana_core_validate_distinct_pubkeys; +/// Validates an exact byte-slice length while preserving the caller error code. +pub(crate) use self::core::executor_solana_core_validate_exact_length; +/// Validates that an unsigned execution amount is strictly positive. +pub(crate) use self::core::executor_solana_core_validate_positive_u64; +/// Validates one `Pubkey::create_with_seed` derivation with caller-owned error codes. +pub(crate) use self::core::executor_solana_core_validate_seeded_address; +/// Crate-root access to `build_prepared_plan` from `vote`. +pub(crate) use self::core::executor_solana_core_vote_build_prepared_plan; +/// Crate-root access to `build_prepared_plan` from `zk_elgamal`. +pub(crate) use self::core::executor_solana_core_zk_elgamal_build_prepared_plan; diff --git a/kb-lib/src/executor/solana/core.rs b/kb-lib/src/executor/solana/core.rs index 0000f81..f01964a 100644 --- a/kb-lib/src/executor/solana/core.rs +++ b/kb-lib/src/executor/solana/core.rs @@ -1,68 +1,307 @@ // file: kb-lib/src/executor/solana/core.rs -// version: 2 +// version: 3 -//! Reserved executor for `solana_core`. +//! Safe typed executor for supported Solana core operations. -/// Reserved executor for the `solana_core` program surface. -#[derive(Clone, Debug, Default)] -pub struct ExSolanaCoreExecutor; +mod address_lookup_table; +mod builder; +mod constants; +mod executor; +mod intent; +mod loader_v3; +mod loader_v4; +mod native_admin; +mod precompiles; +mod slashing; +mod stake; +mod validation; +mod vote; +mod zk_elgamal; -impl crate::ExApiInstructionExecutor for crate::ExSolanaCoreExecutor { - fn executor_name(&self) -> &'static str { - return "kb_executor_solana_core"; - } +/// Exposes the Solana core executor. +pub use self::executor::ExSolanaCoreExecutor; +/// Exposes the Address Lookup Table close operation code. +pub use self::intent::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_CLOSE_OPERATION; +/// Exposes the Address Lookup Table create operation code. +pub use self::intent::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_CREATE_OPERATION; +/// Exposes the Address Lookup Table deactivate operation code. +pub use self::intent::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_DEACTIVATE_OPERATION; +/// Exposes the Address Lookup Table extend operation code. +pub use self::intent::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_EXTEND_OPERATION; +/// Exposes the Address Lookup Table freeze operation code. +pub use self::intent::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_FREEZE_OPERATION; +/// Exposes the Compute Budget heap-frame operation code. +pub use self::intent::EX_SOLANA_CORE_COMPUTE_BUDGET_REQUEST_HEAP_FRAME_OPERATION; +/// Exposes the Compute Budget loaded-account data-size operation code. +pub use self::intent::EX_SOLANA_CORE_COMPUTE_BUDGET_SET_LOADED_ACCOUNTS_DATA_SIZE_LIMIT_OPERATION; +/// Exposes the Compute Budget unit-limit operation code. +pub use self::intent::EX_SOLANA_CORE_COMPUTE_BUDGET_SET_UNIT_LIMIT_OPERATION; +/// Exposes the Compute Budget unit-price operation code. +pub use self::intent::EX_SOLANA_CORE_COMPUTE_BUDGET_SET_UNIT_PRICE_OPERATION; +/// Exposes the Config account creation operation code. +pub use self::intent::EX_SOLANA_CORE_CONFIG_CREATE_ACCOUNT_OPERATION; +/// Exposes the Config store operation code. +pub use self::intent::EX_SOLANA_CORE_CONFIG_STORE_OPERATION; +/// Exposes the Ed25519 inline verification operation code. +pub use self::intent::EX_SOLANA_CORE_ED25519_VERIFY_INLINE_OPERATION; +/// Exposes the Ed25519 offset-table verification operation code. +pub use self::intent::EX_SOLANA_CORE_ED25519_VERIFY_OFFSETS_OPERATION; +/// Exposes the feature activation operation code. +pub use self::intent::EX_SOLANA_CORE_FEATURE_ACTIVATE_OPERATION; +/// Exposes the feature pending-activation revoke operation code. +pub use self::intent::EX_SOLANA_CORE_FEATURE_REVOKE_PENDING_ACTIVATION_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::intent::EX_SOLANA_CORE_LOADER_V3_CLOSE_ANY_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::intent::EX_SOLANA_CORE_LOADER_V3_CLOSE_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::intent::EX_SOLANA_CORE_LOADER_V3_CREATE_BUFFER_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::intent::EX_SOLANA_CORE_LOADER_V3_DEPLOY_WITH_MAX_PROGRAM_LEN_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::intent::EX_SOLANA_CORE_LOADER_V3_EXTEND_PROGRAM_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::intent::EX_SOLANA_CORE_LOADER_V3_SET_BUFFER_AUTHORITY_CHECKED_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::intent::EX_SOLANA_CORE_LOADER_V3_SET_BUFFER_AUTHORITY_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::intent::EX_SOLANA_CORE_LOADER_V3_SET_UPGRADE_AUTHORITY_CHECKED_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::intent::EX_SOLANA_CORE_LOADER_V3_SET_UPGRADE_AUTHORITY_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::intent::EX_SOLANA_CORE_LOADER_V3_UPGRADE_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::intent::EX_SOLANA_CORE_LOADER_V3_WRITE_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::intent::EX_SOLANA_CORE_LOADER_V4_COPY_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::intent::EX_SOLANA_CORE_LOADER_V4_CREATE_BUFFER_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::intent::EX_SOLANA_CORE_LOADER_V4_DEPLOY_FROM_SOURCE_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::intent::EX_SOLANA_CORE_LOADER_V4_DEPLOY_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::intent::EX_SOLANA_CORE_LOADER_V4_FINALIZE_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::intent::EX_SOLANA_CORE_LOADER_V4_RETRACT_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::intent::EX_SOLANA_CORE_LOADER_V4_SET_PROGRAM_LENGTH_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::intent::EX_SOLANA_CORE_LOADER_V4_TRANSFER_AUTHORITY_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::intent::EX_SOLANA_CORE_LOADER_V4_WRITE_OPERATION; +/// Exposes every stable callable Solana core operation code. +pub use self::intent::EX_SOLANA_CORE_OPERATION_CODES; +/// Exposes the secp256k1 inline verification operation code. +pub use self::intent::EX_SOLANA_CORE_SECP256K1_VERIFY_INLINE_OPERATION; +/// Exposes the secp256k1 offset-table verification operation code. +pub use self::intent::EX_SOLANA_CORE_SECP256K1_VERIFY_OFFSETS_OPERATION; +/// Exposes the secp256r1 inline verification operation code. +pub use self::intent::EX_SOLANA_CORE_SECP256R1_VERIFY_INLINE_OPERATION; +/// Exposes the secp256r1 offset-table verification operation code. +pub use self::intent::EX_SOLANA_CORE_SECP256R1_VERIFY_OFFSETS_OPERATION; +/// Exposes the Slashing violation-report close operation code. +pub use self::intent::EX_SOLANA_CORE_SLASHING_CLOSE_VIOLATION_REPORT_OPERATION; +/// Exposes the Slashing duplicate-block proof submission operation code. +pub use self::intent::EX_SOLANA_CORE_SLASHING_SUBMIT_DUPLICATE_BLOCK_PROOF_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::intent::EX_SOLANA_CORE_STAKE_AUTHORIZE_CHECKED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::intent::EX_SOLANA_CORE_STAKE_AUTHORIZE_CHECKED_WITH_SEED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::intent::EX_SOLANA_CORE_STAKE_AUTHORIZE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::intent::EX_SOLANA_CORE_STAKE_AUTHORIZE_WITH_SEED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::intent::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_AND_DELEGATE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::intent::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_CHECKED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::intent::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::intent::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_WITH_SEED_AND_DELEGATE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::intent::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_WITH_SEED_CHECKED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::intent::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_WITH_SEED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::intent::EX_SOLANA_CORE_STAKE_DEACTIVATE_DELINQUENT_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::intent::EX_SOLANA_CORE_STAKE_DEACTIVATE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::intent::EX_SOLANA_CORE_STAKE_DELEGATE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::intent::EX_SOLANA_CORE_STAKE_GET_MINIMUM_DELEGATION_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::intent::EX_SOLANA_CORE_STAKE_INITIALIZE_CHECKED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::intent::EX_SOLANA_CORE_STAKE_INITIALIZE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::intent::EX_SOLANA_CORE_STAKE_MERGE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::intent::EX_SOLANA_CORE_STAKE_MOVE_LAMPORTS_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::intent::EX_SOLANA_CORE_STAKE_MOVE_STAKE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::intent::EX_SOLANA_CORE_STAKE_SET_LOCKUP_CHECKED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::intent::EX_SOLANA_CORE_STAKE_SET_LOCKUP_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::intent::EX_SOLANA_CORE_STAKE_SPLIT_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::intent::EX_SOLANA_CORE_STAKE_SPLIT_WITH_SEED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::intent::EX_SOLANA_CORE_STAKE_WITHDRAW_OPERATION; +/// Exposes the System allocate operation code. +pub use self::intent::EX_SOLANA_CORE_SYSTEM_ALLOCATE_OPERATION; +/// Exposes the System seeded allocate operation code. +pub use self::intent::EX_SOLANA_CORE_SYSTEM_ALLOCATE_WITH_SEED_OPERATION; +/// Exposes the System assign operation code. +pub use self::intent::EX_SOLANA_CORE_SYSTEM_ASSIGN_OPERATION; +/// Exposes the System seeded assign operation code. +pub use self::intent::EX_SOLANA_CORE_SYSTEM_ASSIGN_WITH_SEED_OPERATION; +/// Exposes the System prefunded create-account operation code. +pub use self::intent::EX_SOLANA_CORE_SYSTEM_CREATE_ACCOUNT_ALLOW_PREFUND_OPERATION; +/// Exposes the System create-account operation code. +pub use self::intent::EX_SOLANA_CORE_SYSTEM_CREATE_ACCOUNT_OPERATION; +/// Exposes the System seeded create-account operation code. +pub use self::intent::EX_SOLANA_CORE_SYSTEM_CREATE_ACCOUNT_WITH_SEED_OPERATION; +/// Exposes the durable nonce advance operation code. +pub use self::intent::EX_SOLANA_CORE_SYSTEM_NONCE_ADVANCE_OPERATION; +/// Exposes the durable nonce authorize operation code. +pub use self::intent::EX_SOLANA_CORE_SYSTEM_NONCE_AUTHORIZE_OPERATION; +/// Exposes the durable nonce account creation operation code. +pub use self::intent::EX_SOLANA_CORE_SYSTEM_NONCE_CREATE_OPERATION; +/// Exposes the seeded durable nonce account creation operation code. +pub use self::intent::EX_SOLANA_CORE_SYSTEM_NONCE_CREATE_WITH_SEED_OPERATION; +/// Exposes the durable nonce initialize operation code. +pub use self::intent::EX_SOLANA_CORE_SYSTEM_NONCE_INITIALIZE_OPERATION; +/// Exposes the durable nonce upgrade operation code. +pub use self::intent::EX_SOLANA_CORE_SYSTEM_NONCE_UPGRADE_OPERATION; +/// Exposes the durable nonce withdraw operation code. +pub use self::intent::EX_SOLANA_CORE_SYSTEM_NONCE_WITHDRAW_OPERATION; +/// Exposes the System multi-transfer operation code. +pub use self::intent::EX_SOLANA_CORE_SYSTEM_TRANSFER_MANY_OPERATION; +/// Exposes the System transfer operation code. +pub use self::intent::EX_SOLANA_CORE_SYSTEM_TRANSFER_OPERATION; +/// Exposes the System seeded transfer operation code. +pub use self::intent::EX_SOLANA_CORE_SYSTEM_TRANSFER_WITH_SEED_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::intent::EX_SOLANA_CORE_VOTE_AUTHORIZE_CHECKED_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::intent::EX_SOLANA_CORE_VOTE_AUTHORIZE_CHECKED_WITH_SEED_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::intent::EX_SOLANA_CORE_VOTE_AUTHORIZE_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::intent::EX_SOLANA_CORE_VOTE_AUTHORIZE_WITH_SEED_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::intent::EX_SOLANA_CORE_VOTE_COMPACT_UPDATE_VOTE_STATE_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::intent::EX_SOLANA_CORE_VOTE_COMPACT_UPDATE_VOTE_STATE_SWITCH_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::intent::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::intent::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_V2_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::intent::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_WITH_SEED_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::intent::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_WITH_SEED_V2_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::intent::EX_SOLANA_CORE_VOTE_DEPOSIT_DELEGATOR_REWARDS_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::intent::EX_SOLANA_CORE_VOTE_INITIALIZE_ACCOUNT_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::intent::EX_SOLANA_CORE_VOTE_INITIALIZE_ACCOUNT_V2_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::intent::EX_SOLANA_CORE_VOTE_SUBMIT_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::intent::EX_SOLANA_CORE_VOTE_SWITCH_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::intent::EX_SOLANA_CORE_VOTE_TOWER_SYNC_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::intent::EX_SOLANA_CORE_VOTE_TOWER_SYNC_SWITCH_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::intent::EX_SOLANA_CORE_VOTE_UPDATE_COMMISSION_BPS_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::intent::EX_SOLANA_CORE_VOTE_UPDATE_COMMISSION_COLLECTOR_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::intent::EX_SOLANA_CORE_VOTE_UPDATE_COMMISSION_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::intent::EX_SOLANA_CORE_VOTE_UPDATE_VALIDATOR_IDENTITY_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::intent::EX_SOLANA_CORE_VOTE_UPDATE_VOTE_STATE_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::intent::EX_SOLANA_CORE_VOTE_UPDATE_VOTE_STATE_SWITCH_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::intent::EX_SOLANA_CORE_VOTE_WITHDRAW_OPERATION; +/// Exposes the ZK ElGamal context-state close operation code. +pub use self::intent::EX_SOLANA_CORE_ZK_ELGAMAL_CLOSE_CONTEXT_STATE_OPERATION; +/// Exposes the ZK ElGamal proof-account verification operation code. +pub use self::intent::EX_SOLANA_CORE_ZK_ELGAMAL_VERIFY_FROM_ACCOUNT_OPERATION; +/// Exposes the ZK ElGamal inline proof verification operation code. +pub use self::intent::EX_SOLANA_CORE_ZK_ELGAMAL_VERIFY_INLINE_OPERATION; +/// Exposes one Config Program key entry. +pub use self::intent::ExSolanaCoreConfigKey; +/// Exposes one Ed25519 verification offset record. +pub use self::intent::ExSolanaCoreEd25519VerificationOffsets; +/// Exposes the complete typed Solana core intent. +pub use self::intent::ExSolanaCoreExecutionIntent; +/// Exposes typed Solana core operation arguments. +pub use self::intent::ExSolanaCoreOperation; +/// Exposes one secp256k1 verification offset record. +pub use self::intent::ExSolanaCoreSecp256k1VerificationOffsets; +/// Exposes one secp256r1 verification offset record. +pub use self::intent::ExSolanaCoreSecp256r1VerificationOffsets; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::intent::ExSolanaCoreStakeAuthorizationKind; +/// Exposes one recipient in a System multi-transfer. +pub use self::intent::ExSolanaCoreSystemTransferRecipient; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::intent::ExSolanaCoreVoteAuthorizationKind; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::intent::ExSolanaCoreVoteCommissionKind; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::intent::ExSolanaCoreVoteLockout; +/// Exposes optional ZK ElGamal context-state accounts. +pub use self::intent::ExSolanaCoreZkElGamalContextState; +/// Exposes the official ZK ElGamal proof kinds. +pub use self::intent::ExSolanaCoreZkElGamalProofType; - fn executor_version(&self) -> &'static str { - return env!("CARGO_PKG_VERSION"); - } - - fn program_ids(&self) -> &'static [&'static str] { - return &[ - kb_program_ids::ADDRESS_LOOKUP_TABLE_PROGRAM_ID, - kb_program_ids::BPF_LOADER_DEPRECATED_PROGRAM_ID, - kb_program_ids::BPF_LOADER_PROGRAM_ID, - kb_program_ids::BPF_LOADER_UPGRADEABLE_PROGRAM_ID, - kb_program_ids::COMPUTE_BUDGET_PROGRAM_ID, - kb_program_ids::CONFIG_PROGRAM_ID, - kb_program_ids::ED25519_PROGRAM_ID, - kb_program_ids::FEATURE_PROGRAM_ID, - kb_program_ids::LOADER_V4_PROGRAM_ID, - kb_program_ids::NATIVE_LOADER_PROGRAM_ID, - kb_program_ids::SECP256K1_PROGRAM_ID, - kb_program_ids::SECP256R1_PROGRAM_ID, - kb_program_ids::SLASHING_PROGRAM_ID, - kb_program_ids::STAKE_PROGRAM_ID, - kb_program_ids::SYSTEM_PROGRAM_ID, - kb_program_ids::VOTE_PROGRAM_ID, - kb_program_ids::ZK_ELGAMAL_PROOF_PROGRAM_ID, - kb_program_ids::ZK_TOKEN_PROOF_PROGRAM_ID, - ]; - } - - fn supports_request( - &self, - request: &crate::ExApiExecutionRequest, - ) -> crate::ExApiExecutionSupport { - if crate::ExApiInstructionExecutor::handles_program_id(self, &request.program_id) { - return crate::ExApiExecutionSupport::Maybe; - } - return crate::ExApiExecutionSupport::No; - } - - fn build_plan( - &self, - _request: &crate::ExApiExecutionRequest, - ) -> kb_core::Result { - let payload_json = match crate::executor_api_serialize_payload_json( - &serde_json::json!({"status":"reserved_executor","surface":"solana_core"}), - ) { - std::result::Result::Ok(serialized) => serialized, - std::result::Result::Err(error) => return std::result::Result::Err(error), - }; - return std::result::Result::Ok(crate::ExApiExecutionPlan { - executor_name: "kb_executor_solana_core".to_string(), - instruction_count: 0, - payload_json, - }); - } -} +/// Crate-root access to `build_prepared_plan` from `address_lookup_table`. +pub(crate) use self::address_lookup_table::executor_solana_core_address_lookup_table_build_prepared_plan; +/// Crate-root access to `build_prepared_plan` from `builder`. +pub(crate) use self::builder::executor_solana_core_builder_build_prepared_plan; +/// Crate-root access to `finalize_prepared_plan` from `builder`. +pub(crate) use self::builder::executor_solana_core_finalize_prepared_plan; +/// Canonical tracing target for this crate. +pub(crate) use self::constants::EX_SOLANA_CORE_TRACING_TARGET; +/// Crate-root access to `build_prepared_plan` from `loader_v3`. +pub(crate) use self::loader_v3::executor_solana_core_loader_v3_build_prepared_plan; +/// Crate-root access to `build_prepared_plan` from `loader_v4`. +pub(crate) use self::loader_v4::executor_solana_core_loader_v4_build_prepared_plan; +/// Crate-root access to `build_prepared_plan` from `native_admin`. +pub(crate) use self::native_admin::executor_solana_core_native_admin_build_prepared_plan; +/// Crate-root access to `build_prepared_plan` from `precompiles`. +pub(crate) use self::precompiles::executor_solana_core_precompiles_build_prepared_plan; +/// Crate-root access to `build_prepared_plan` from `slashing`. +pub(crate) use self::slashing::executor_solana_core_slashing_build_prepared_plan; +/// Crate-root access to `build_prepared_plan` from `stake`. +pub(crate) use self::stake::executor_solana_core_stake_build_prepared_plan; +/// Builds one typed validation error without duplicating domain-local wrappers. +pub(crate) use self::validation::executor_solana_core_invalid; +/// Parses one canonical native program identifier. +pub(crate) use self::validation::executor_solana_core_parse_native_program_id; +/// Parses one model public key with the canonical execution error contract. +pub(crate) use self::validation::executor_solana_core_parse_pubkey; +/// Validates that two model public keys identify distinct accounts. +pub(crate) use self::validation::executor_solana_core_validate_distinct_pubkeys; +/// Validates an exact byte-slice length while preserving the caller error code. +pub(crate) use self::validation::executor_solana_core_validate_exact_length; +/// Validates that an unsigned execution amount is strictly positive. +pub(crate) use self::validation::executor_solana_core_validate_positive_u64; +/// Validates one `Pubkey::create_with_seed` derivation with caller-owned error codes. +pub(crate) use self::validation::executor_solana_core_validate_seeded_address; +/// Crate-root access to `build_prepared_plan` from `vote`. +pub(crate) use self::vote::executor_solana_core_vote_build_prepared_plan; +/// Crate-root access to `build_prepared_plan` from `zk_elgamal`. +pub(crate) use self::zk_elgamal::executor_solana_core_zk_elgamal_build_prepared_plan; diff --git a/kb-lib/src/executor/solana/core/address_lookup_table.rs b/kb-lib/src/executor/solana/core/address_lookup_table.rs new file mode 100644 index 0000000..c991d01 --- /dev/null +++ b/kb-lib/src/executor/solana/core/address_lookup_table.rs @@ -0,0 +1,594 @@ +// file: kb-lib/src/executor/solana/core/address_lookup_table.rs +// version: 1 + +//! Official Address Lookup Table instruction builders. + +pub(crate) fn executor_solana_core_address_lookup_table_build_prepared_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, +) -> kb_core::Result { + return match &intent.operation { + crate::ExSolanaCoreOperation::AddressLookupTableCreate { + authority, + payer, + recent_slot, + } => build_create_plan(intent, fee_payer, authority, payer, *recent_slot), + crate::ExSolanaCoreOperation::AddressLookupTableExtend { + lookup_table, + authority, + payer, + new_addresses, + } => build_extend_plan( + intent, + fee_payer, + lookup_table, + authority, + payer.as_ref(), + new_addresses.as_slice(), + ), + crate::ExSolanaCoreOperation::AddressLookupTableFreeze { lookup_table, authority } => { + build_freeze_plan(intent, fee_payer, lookup_table, authority) + }, + crate::ExSolanaCoreOperation::AddressLookupTableDeactivate { lookup_table, authority } => { + build_deactivate_plan(intent, fee_payer, lookup_table, authority) + }, + crate::ExSolanaCoreOperation::AddressLookupTableClose { + lookup_table, + authority, + recipient, + } => build_close_plan(intent, fee_payer, lookup_table, authority, recipient), + _ => std::result::Result::Err(kb_core::Error::new( + "execution_address_lookup_table_operation_mismatch", + "Address Lookup Table builder received a non-ALT operation", + )), + }; +} + +fn build_create_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + authority: &crate::MdPubkey, + payer: &crate::MdPubkey, + recent_slot: u64, +) -> kb_core::Result { + let authority_address = match crate::executor_solana_core_parse_pubkey(authority, "authority") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let payer_address = match crate::executor_solana_core_parse_pubkey(payer, "payer") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let (instruction, lookup_table_address) = + solana_address_lookup_table_interface::instruction::create_lookup_table( + authority_address, + payer_address, + recent_slot, + ); + if lookup_table_address == authority_address || lookup_table_address == payer_address { + return std::result::Result::Err(kb_core::Error::new( + "execution_address_lookup_table_derived_address_conflict", + "derived lookup table address must differ from its authority and payer", + )); + } + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_CREATE_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + vec![(payer.clone(), std::string::String::from("lookup_table_payer"))], + ); +} + +fn build_extend_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + lookup_table: &crate::MdPubkey, + authority: &crate::MdPubkey, + payer: std::option::Option<&crate::MdPubkey>, + new_addresses: &[crate::MdPubkey], +) -> kb_core::Result { + if new_addresses.is_empty() { + return std::result::Result::Err(kb_core::Error::new( + "execution_address_lookup_table_extend_empty", + "Address Lookup Table extension requires at least one address", + )); + } + if new_addresses.len() + > solana_address_lookup_table_interface::state::LOOKUP_TABLE_MAX_ADDRESSES + { + return std::result::Result::Err(kb_core::Error::new( + "execution_address_lookup_table_extend_too_many_addresses", + format!( + "Address Lookup Table extension cannot contain more than {} addresses", + solana_address_lookup_table_interface::state::LOOKUP_TABLE_MAX_ADDRESSES + ), + )); + } + let lookup_table_address = + match crate::executor_solana_core_parse_pubkey(lookup_table, "lookup_table") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority_address = match crate::executor_solana_core_parse_pubkey(authority, "authority") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + match crate::executor_solana_core_validate_distinct_pubkeys( + lookup_table, + authority, + "execution_address_lookup_table_authority_is_table", + "lookup table account and authority must differ", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let payer_address = match payer { + std::option::Option::Some(value) => { + match crate::executor_solana_core_validate_distinct_pubkeys( + lookup_table, + value, + "execution_address_lookup_table_payer_is_table", + "lookup table account and rent payer must differ", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match crate::executor_solana_core_parse_pubkey(value, "payer") { + std::result::Result::Ok(pubkey) => std::option::Option::Some(pubkey), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + }, + std::option::Option::None => std::option::Option::None, + }; + let mut parsed_addresses = std::vec::Vec::with_capacity(new_addresses.len()); + for (index, address) in new_addresses.iter().enumerate() { + let parsed = match crate::executor_solana_core_parse_pubkey( + address, + format!("new_addresses[{index}]").as_str(), + ) { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + parsed_addresses.push(parsed); + } + let instruction = solana_address_lookup_table_interface::instruction::extend_lookup_table( + lookup_table_address, + authority_address, + payer_address, + parsed_addresses, + ); + let mut signer_roles = + vec![(authority.clone(), std::string::String::from("lookup_table_authority"))]; + if let std::option::Option::Some(payer_pubkey) = payer { + signer_roles.push((payer_pubkey.clone(), std::string::String::from("lookup_table_payer"))); + } + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_EXTEND_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + signer_roles, + ); +} + +fn build_freeze_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + lookup_table: &crate::MdPubkey, + authority: &crate::MdPubkey, +) -> kb_core::Result { + let (lookup_table_address, authority_address) = + match parse_authority_pair(lookup_table, authority) { + std::result::Result::Ok(pair) => pair, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instruction = solana_address_lookup_table_interface::instruction::freeze_lookup_table( + lookup_table_address, + authority_address, + ); + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_FREEZE_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + vec![(authority.clone(), std::string::String::from("lookup_table_authority"))], + ); +} + +fn build_deactivate_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + lookup_table: &crate::MdPubkey, + authority: &crate::MdPubkey, +) -> kb_core::Result { + let (lookup_table_address, authority_address) = + match parse_authority_pair(lookup_table, authority) { + std::result::Result::Ok(pair) => pair, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instruction = solana_address_lookup_table_interface::instruction::deactivate_lookup_table( + lookup_table_address, + authority_address, + ); + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_DEACTIVATE_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + vec![(authority.clone(), std::string::String::from("lookup_table_authority"))], + ); +} + +fn build_close_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + lookup_table: &crate::MdPubkey, + authority: &crate::MdPubkey, + recipient: &crate::MdPubkey, +) -> kb_core::Result { + let (lookup_table_address, authority_address) = + match parse_authority_pair(lookup_table, authority) { + std::result::Result::Ok(pair) => pair, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + match crate::executor_solana_core_validate_distinct_pubkeys( + lookup_table, + recipient, + "execution_address_lookup_table_close_recipient_is_table", + "lookup table account and close recipient must differ", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let recipient_address = match crate::executor_solana_core_parse_pubkey(recipient, "recipient") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instruction = solana_address_lookup_table_interface::instruction::close_lookup_table( + lookup_table_address, + authority_address, + recipient_address, + ); + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_CLOSE_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + vec![(authority.clone(), std::string::String::from("lookup_table_authority"))], + ); +} + +fn parse_authority_pair( + lookup_table: &crate::MdPubkey, + authority: &crate::MdPubkey, +) -> kb_core::Result<(solana_pubkey::Pubkey, solana_pubkey::Pubkey)> { + match crate::executor_solana_core_validate_distinct_pubkeys( + lookup_table, + authority, + "execution_address_lookup_table_authority_is_table", + "lookup table account and authority must differ", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let lookup_table_address = + match crate::executor_solana_core_parse_pubkey(lookup_table, "lookup_table") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority_address = match crate::executor_solana_core_parse_pubkey(authority, "authority") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return std::result::Result::Ok((lookup_table_address, authority_address)); +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; // rust-rules: trait-import + + fn pubkey(value: &str) -> crate::MdPubkey { + return crate::MdPubkey(std::string::String::from(value)); + } + + fn parsed(value: &str) -> solana_pubkey::Pubkey { + return solana_pubkey::Pubkey::from_str(value) + .unwrap_or_else(|error| panic!("invalid fixture: {error}")); + } + + fn policy(signers: &[&str]) -> crate::ExApiExecutionPolicy { + return crate::ExApiExecutionPolicy { + cost_limit: crate::ExApiExecutionCostLimit { + max_spend_lamports: std::option::Option::Some(10_000_000), + max_fee_lamports: std::option::Option::Some(10_000), + max_compute_unit_price_micro_lamports: std::option::Option::Some(100), + }, + authorized_signers: signers.iter().map(|value| return pubkey(value)).collect(), + ..crate::ExApiExecutionPolicy::default() + }; + } + + fn assert_matches_official( + plan: &crate::ExApiPreparedExecutionPlan, + expected: &solana_instruction::Instruction, + ) { + assert_eq!(plan.instructions.len(), 1); + let actual = &plan.instructions[0]; + assert_eq!(actual.program_id.0, expected.program_id.to_string()); + assert_eq!(actual.data, expected.data); + assert_eq!(actual.accounts.len(), expected.accounts.len()); + for (actual_account, expected_account) in + actual.accounts.iter().zip(expected.accounts.iter()) + { + assert_eq!(actual_account.pubkey.0, expected_account.pubkey.to_string()); + assert_eq!(actual_account.is_signer, expected_account.is_signer); + assert_eq!(actual_account.is_writable, expected_account.is_writable); + } + } + + #[test] + fn create_lookup_table_matches_official_builder_and_signers() { + let authority = kb_program_ids::VOTE_PROGRAM_ID; + let payer = kb_program_ids::STAKE_PROGRAM_ID; + let recent_slot = 42; + let intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("alt-create-1"), + fee_payer: pubkey(payer), + policy: policy(&[payer]), + operation: crate::ExSolanaCoreOperation::AddressLookupTableCreate { + authority: pubkey(authority), + payer: pubkey(payer), + recent_slot, + }, + }; + let plan = crate::executor_solana_core_builder_build_prepared_plan(&intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + let (expected, expected_address) = + solana_address_lookup_table_interface::instruction::create_lookup_table( + parsed(authority), + parsed(payer), + recent_slot, + ); + assert_matches_official(&plan, &expected); + assert_eq!( + plan.operation_code, + crate::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_CREATE_OPERATION + ); + assert_eq!(plan.instructions[0].accounts[0].pubkey.0, expected_address.to_string()); + assert_eq!(plan.required_signers.len(), 1); + assert_eq!(plan.required_signers[0].pubkey.0, payer); + assert!(!plan.instructions[0].accounts[1].is_signer); + assert_eq!(plan.requested_spend_lamports, 0); + } + + #[test] + fn extend_lookup_table_matches_official_builder_with_optional_payer() { + let authority = parsed(kb_program_ids::STAKE_PROGRAM_ID); + let payer = parsed(kb_program_ids::VOTE_PROGRAM_ID); + let (lookup_table, _bump) = + solana_address_lookup_table_interface::instruction::derive_lookup_table_address( + &authority, 100, + ); + let addresses = vec![ + parsed(kb_program_ids::SYSTEM_PROGRAM_ID), + parsed(kb_program_ids::CONFIG_PROGRAM_ID), + ]; + let authority_text = authority.to_string(); + let payer_text = payer.to_string(); + let lookup_table_text = lookup_table.to_string(); + let new_addresses = addresses + .iter() + .map(|address| { + return pubkey(address.to_string().as_str()); + }) + .collect::>(); + let funded_intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("alt-extend-funded"), + fee_payer: pubkey(payer_text.as_str()), + policy: policy(&[payer_text.as_str(), authority_text.as_str()]), + operation: crate::ExSolanaCoreOperation::AddressLookupTableExtend { + lookup_table: pubkey(lookup_table_text.as_str()), + authority: pubkey(authority_text.as_str()), + payer: std::option::Option::Some(pubkey(payer_text.as_str())), + new_addresses: new_addresses.clone(), + }, + }; + let funded_plan = crate::executor_solana_core_builder_build_prepared_plan(&funded_intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + let funded_expected = + solana_address_lookup_table_interface::instruction::extend_lookup_table( + lookup_table, + authority, + std::option::Option::Some(payer), + addresses.clone(), + ); + assert_matches_official(&funded_plan, &funded_expected); + assert_eq!(funded_plan.required_signers.len(), 2); + assert_eq!( + funded_plan.operation_code, + crate::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_EXTEND_OPERATION + ); + + let unfunded_intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("alt-extend-prefunded"), + fee_payer: pubkey(authority_text.as_str()), + policy: policy(&[authority_text.as_str()]), + operation: crate::ExSolanaCoreOperation::AddressLookupTableExtend { + lookup_table: pubkey(lookup_table_text.as_str()), + authority: pubkey(authority_text.as_str()), + payer: std::option::Option::None, + new_addresses, + }, + }; + let unfunded_plan = + crate::executor_solana_core_builder_build_prepared_plan(&unfunded_intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + let unfunded_expected = + solana_address_lookup_table_interface::instruction::extend_lookup_table( + lookup_table, + authority, + std::option::Option::None, + addresses, + ); + assert_matches_official(&unfunded_plan, &unfunded_expected); + assert_eq!(unfunded_plan.required_signers.len(), 1); + } + + #[test] + fn lookup_table_authority_lifecycle_matches_official_builders() { + let authority = parsed(kb_program_ids::VOTE_PROGRAM_ID); + let recipient = parsed(kb_program_ids::CONFIG_PROGRAM_ID); + let (lookup_table, _bump) = + solana_address_lookup_table_interface::instruction::derive_lookup_table_address( + &authority, 200, + ); + let authority_text = authority.to_string(); + let recipient_text = recipient.to_string(); + let lookup_table_text = lookup_table.to_string(); + let variants = [ + ( + crate::ExSolanaCoreOperation::AddressLookupTableFreeze { + lookup_table: pubkey(lookup_table_text.as_str()), + authority: pubkey(authority_text.as_str()), + }, + crate::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_FREEZE_OPERATION, + solana_address_lookup_table_interface::instruction::freeze_lookup_table( + lookup_table, + authority, + ), + ), + ( + crate::ExSolanaCoreOperation::AddressLookupTableDeactivate { + lookup_table: pubkey(lookup_table_text.as_str()), + authority: pubkey(authority_text.as_str()), + }, + crate::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_DEACTIVATE_OPERATION, + solana_address_lookup_table_interface::instruction::deactivate_lookup_table( + lookup_table, + authority, + ), + ), + ( + crate::ExSolanaCoreOperation::AddressLookupTableClose { + lookup_table: pubkey(lookup_table_text.as_str()), + authority: pubkey(authority_text.as_str()), + recipient: pubkey(recipient_text.as_str()), + }, + crate::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_CLOSE_OPERATION, + solana_address_lookup_table_interface::instruction::close_lookup_table( + lookup_table, + authority, + recipient, + ), + ), + ]; + for (operation, operation_code, expected) in variants { + let intent = crate::ExSolanaCoreExecutionIntent { + intent_id: format!("alt-{operation_code}"), + fee_payer: pubkey(authority_text.as_str()), + policy: policy(&[authority_text.as_str()]), + operation, + }; + let plan = crate::executor_solana_core_builder_build_prepared_plan(&intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + assert_matches_official(&plan, &expected); + assert_eq!(plan.operation_code, operation_code); + assert_eq!(plan.required_signers.len(), 1); + } + } + + #[test] + fn lookup_table_invalid_contracts_are_rejected() { + let authority = parsed(kb_program_ids::VOTE_PROGRAM_ID); + let (lookup_table, _bump) = + solana_address_lookup_table_interface::instruction::derive_lookup_table_address( + &authority, 300, + ); + let authority_text = authority.to_string(); + let lookup_table_text = lookup_table.to_string(); + let empty = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("alt-empty"), + fee_payer: pubkey(authority_text.as_str()), + policy: policy(&[authority_text.as_str()]), + operation: crate::ExSolanaCoreOperation::AddressLookupTableExtend { + lookup_table: pubkey(lookup_table_text.as_str()), + authority: pubkey(authority_text.as_str()), + payer: std::option::Option::None, + new_addresses: std::vec::Vec::new(), + }, + }; + let empty_error = crate::executor_solana_core_builder_build_prepared_plan(&empty) + .expect_err("empty extension must fail"); + assert_eq!(empty_error.code(), "execution_address_lookup_table_extend_empty"); + + let too_many_addresses = (0 + ..=solana_address_lookup_table_interface::state::LOOKUP_TABLE_MAX_ADDRESSES) + .map(|_| { + return pubkey(kb_program_ids::SYSTEM_PROGRAM_ID); + }) + .collect::>(); + let too_many = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("alt-too-many"), + fee_payer: pubkey(authority_text.as_str()), + policy: policy(&[authority_text.as_str()]), + operation: crate::ExSolanaCoreOperation::AddressLookupTableExtend { + lookup_table: pubkey(lookup_table_text.as_str()), + authority: pubkey(authority_text.as_str()), + payer: std::option::Option::None, + new_addresses: too_many_addresses, + }, + }; + let too_many_error = crate::executor_solana_core_builder_build_prepared_plan(&too_many) + .expect_err("oversized extension must fail"); + assert_eq!( + too_many_error.code(), + "execution_address_lookup_table_extend_too_many_addresses" + ); + + let same_authority = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("alt-same-authority"), + fee_payer: pubkey(lookup_table_text.as_str()), + policy: policy(&[lookup_table_text.as_str()]), + operation: crate::ExSolanaCoreOperation::AddressLookupTableFreeze { + lookup_table: pubkey(lookup_table_text.as_str()), + authority: pubkey(lookup_table_text.as_str()), + }, + }; + let same_authority_error = + crate::executor_solana_core_builder_build_prepared_plan(&same_authority) + .expect_err("table cannot be its own authority signer"); + assert_eq!( + same_authority_error.code(), + "execution_address_lookup_table_authority_is_table" + ); + + let same_recipient = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("alt-same-recipient"), + fee_payer: pubkey(authority_text.as_str()), + policy: policy(&[authority_text.as_str()]), + operation: crate::ExSolanaCoreOperation::AddressLookupTableClose { + lookup_table: pubkey(lookup_table_text.as_str()), + authority: pubkey(authority_text.as_str()), + recipient: pubkey(lookup_table_text.as_str()), + }, + }; + let same_recipient_error = + crate::executor_solana_core_builder_build_prepared_plan(&same_recipient) + .expect_err("table cannot receive its own closed lamports"); + assert_eq!( + same_recipient_error.code(), + "execution_address_lookup_table_close_recipient_is_table" + ); + } +} diff --git a/kb-lib/src/executor/solana/core/builder.rs b/kb-lib/src/executor/solana/core/builder.rs new file mode 100644 index 0000000..e329871 --- /dev/null +++ b/kb-lib/src/executor/solana/core/builder.rs @@ -0,0 +1,2223 @@ +// file: kb-lib/src/executor/solana/core/builder.rs +// version: 1 + +//! Official Solana core instruction builders and shared plan finalization. + +use std::str::FromStr; // rust-rules: trait-import + +/// Conservative maximum Compute Budget unit limit accepted by this executor. +const MAX_COMPUTE_UNIT_LIMIT: u32 = 1_400_000; +/// Minimum heap-frame size accepted by the runtime. +const MIN_HEAP_FRAME_BYTES: u32 = 32 * 1_024; +/// Maximum heap-frame size accepted by the runtime. +const MAX_HEAP_FRAME_BYTES: u32 = 256 * 1_024; +/// Heap-frame alignment required by the runtime. +const HEAP_FRAME_BYTES_GRANULARITY: u32 = 1_024; +/// Conservative maximum loaded-account data-size limit accepted by this executor. +const MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES: u32 = 64 * 1_024 * 1_024; + +pub(crate) fn executor_solana_core_builder_build_prepared_plan( + intent: &crate::ExSolanaCoreExecutionIntent, +) -> kb_core::Result { + if intent.intent_id.trim().is_empty() { + return std::result::Result::Err(kb_core::Error::new( + "execution_intent_id_empty", + "Solana core execution intent id must not be empty", + )); + } + let fee_payer = match crate::executor_solana_core_parse_pubkey(&intent.fee_payer, "fee_payer") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let operation_code = intent.operation.operation_code(); + if intent.operation.program_id() == kb_program_ids::BPF_LOADER_UPGRADEABLE_PROGRAM_ID { + return crate::executor_solana_core_loader_v3_build_prepared_plan(intent, &fee_payer); + } + if intent.operation.program_id() == kb_program_ids::LOADER_V4_PROGRAM_ID { + return crate::executor_solana_core_loader_v4_build_prepared_plan(intent, &fee_payer); + } + if intent.operation.program_id() == kb_program_ids::STAKE_PROGRAM_ID { + return crate::executor_solana_core_stake_build_prepared_plan(intent, &fee_payer); + } + if intent.operation.program_id() == kb_program_ids::VOTE_PROGRAM_ID { + return crate::executor_solana_core_vote_build_prepared_plan(intent, &fee_payer); + } + match &intent.operation { + crate::ExSolanaCoreOperation::SystemTransferMany { from, recipients } => { + return build_transfer_many_plan(intent, &fee_payer, from, recipients.as_slice()); + }, + crate::ExSolanaCoreOperation::SystemCreateNonceAccount { + from, + nonce_account, + authority, + lamports, + } => { + return build_create_nonce_account_plan( + intent, + &fee_payer, + from, + nonce_account, + authority, + *lamports, + ); + }, + crate::ExSolanaCoreOperation::SystemCreateNonceAccountWithSeed { .. } => { + return build_create_nonce_account_with_seed_plan( + intent, + &fee_payer, + &intent.operation, + ); + }, + crate::ExSolanaCoreOperation::SystemInitializeNonceAccount { nonce_account, authority } => { + return build_initialize_nonce_account_plan( + intent, + &fee_payer, + nonce_account, + authority, + ); + }, + crate::ExSolanaCoreOperation::SystemAdvanceNonceAccount { nonce_account, authority } => { + return build_advance_nonce_account_plan(intent, &fee_payer, nonce_account, authority); + }, + crate::ExSolanaCoreOperation::SystemAuthorizeNonceAccount { + nonce_account, + authority, + new_authority, + } => { + return build_authorize_nonce_account_plan( + intent, + &fee_payer, + nonce_account, + authority, + new_authority, + ); + }, + crate::ExSolanaCoreOperation::SystemWithdrawNonceAccount { + nonce_account, + authority, + to, + lamports, + } => { + return build_withdraw_nonce_account_plan( + intent, + &fee_payer, + nonce_account, + authority, + to, + *lamports, + ); + }, + crate::ExSolanaCoreOperation::SystemUpgradeNonceAccount { nonce_account } => { + return build_upgrade_nonce_account_plan(intent, &fee_payer, nonce_account); + }, + crate::ExSolanaCoreOperation::AddressLookupTableCreate { .. } + | crate::ExSolanaCoreOperation::AddressLookupTableExtend { .. } + | crate::ExSolanaCoreOperation::AddressLookupTableFreeze { .. } + | crate::ExSolanaCoreOperation::AddressLookupTableDeactivate { .. } + | crate::ExSolanaCoreOperation::AddressLookupTableClose { .. } => { + return crate::executor_solana_core_address_lookup_table_build_prepared_plan( + intent, &fee_payer, + ); + }, + crate::ExSolanaCoreOperation::Ed25519VerifyInline { .. } + | crate::ExSolanaCoreOperation::Ed25519VerifyOffsets { .. } + | crate::ExSolanaCoreOperation::Secp256k1VerifyInline { .. } + | crate::ExSolanaCoreOperation::Secp256k1VerifyOffsets { .. } + | crate::ExSolanaCoreOperation::Secp256r1VerifyInline { .. } + | crate::ExSolanaCoreOperation::Secp256r1VerifyOffsets { .. } => { + return crate::executor_solana_core_precompiles_build_prepared_plan(intent, &fee_payer); + }, + crate::ExSolanaCoreOperation::ConfigCreateAccount { .. } + | crate::ExSolanaCoreOperation::ConfigStore { .. } + | crate::ExSolanaCoreOperation::FeatureActivate { .. } + | crate::ExSolanaCoreOperation::FeatureRevokePendingActivation { .. } => { + return crate::executor_solana_core_native_admin_build_prepared_plan( + intent, &fee_payer, + ); + }, + crate::ExSolanaCoreOperation::SlashingCloseViolationReport { .. } + | crate::ExSolanaCoreOperation::SlashingSubmitDuplicateBlockProof { .. } => { + return crate::executor_solana_core_slashing_build_prepared_plan(intent, &fee_payer); + }, + crate::ExSolanaCoreOperation::ZkElGamalVerifyInline { .. } + | crate::ExSolanaCoreOperation::ZkElGamalVerifyFromAccount { .. } + | crate::ExSolanaCoreOperation::ZkElGamalCloseContextState { .. } => { + return crate::executor_solana_core_zk_elgamal_build_prepared_plan(intent, &fee_payer); + }, + _ => {}, + } + let (instruction, requested_spend_lamports, requested_compute_unit_price, signer_roles) = + match &intent.operation { + crate::ExSolanaCoreOperation::SystemTransfer { from, to, lamports } => { + match crate::executor_solana_core_validate_positive_u64( + *lamports, + "execution_transfer_lamports_zero", + "System transfer requires a positive lamport amount", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => { + return std::result::Result::Err(error); + }, + } + match crate::executor_solana_core_validate_distinct_pubkeys( + from, + to, + "execution_transfer_same_account", + "System transfer source and destination must differ", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => { + return std::result::Result::Err(error); + }, + } + let from_address = match crate::executor_solana_core_parse_pubkey(from, "from") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let to_address = match crate::executor_solana_core_parse_pubkey(to, "to") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + ( + solana_system_interface::instruction::transfer( + &from_address, + &to_address, + *lamports, + ), + *lamports, + std::option::Option::None, + vec![(from.clone(), std::string::String::from("funding_account"))], + ) + }, + crate::ExSolanaCoreOperation::SystemTransferWithSeed { + from, + from_base, + from_seed, + from_owner, + to, + lamports, + } => { + match crate::executor_solana_core_validate_positive_u64( + *lamports, + "execution_transfer_lamports_zero", + "System seeded transfer requires a positive lamport amount", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => { + return std::result::Result::Err(error); + }, + } + match crate::executor_solana_core_validate_distinct_pubkeys( + from, + to, + "execution_transfer_same_account", + "System seeded transfer source and destination must differ", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => { + return std::result::Result::Err(error); + }, + } + let from_address = match crate::executor_solana_core_parse_pubkey(from, "from") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let from_base_address = + match crate::executor_solana_core_parse_pubkey(from_base, "from_base") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => { + return std::result::Result::Err(error); + }, + }; + let from_owner_address = + match crate::executor_solana_core_parse_pubkey(from_owner, "from_owner") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => { + return std::result::Result::Err(error); + }, + }; + let to_address = match crate::executor_solana_core_parse_pubkey(to, "to") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + match crate::executor_solana_core_validate_seeded_address( + &from_address, + &from_base_address, + from_seed, + &from_owner_address, + "from", + "execution_seed_invalid", + "execution_seeded_address_mismatch", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => { + return std::result::Result::Err(error); + }, + } + ( + solana_system_interface::instruction::transfer_with_seed( + &from_address, + &from_base_address, + from_seed.clone(), + &from_owner_address, + &to_address, + *lamports, + ), + *lamports, + std::option::Option::None, + vec![(from_base.clone(), std::string::String::from("seed_base_account"))], + ) + }, + crate::ExSolanaCoreOperation::SystemCreateAccount { + from, + new_account, + lamports, + space, + owner, + } => { + match crate::executor_solana_core_validate_positive_u64( + *lamports, + "execution_create_account_lamports_zero", + "System account creation requires a positive lamport amount", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => { + return std::result::Result::Err(error); + }, + } + match validate_create_space(*space) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => { + return std::result::Result::Err(error); + }, + } + match crate::executor_solana_core_validate_distinct_pubkeys( + from, + new_account, + "execution_create_account_same_account", + "funding and new account public keys must differ", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => { + return std::result::Result::Err(error); + }, + } + let from_address = match crate::executor_solana_core_parse_pubkey(from, "from") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let new_account_address = + match crate::executor_solana_core_parse_pubkey(new_account, "new_account") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => { + return std::result::Result::Err(error); + }, + }; + let owner_address = match crate::executor_solana_core_parse_pubkey(owner, "owner") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + ( + solana_system_interface::instruction::create_account( + &from_address, + &new_account_address, + *lamports, + *space, + &owner_address, + ), + *lamports, + std::option::Option::None, + vec![ + (from.clone(), std::string::String::from("funding_account")), + (new_account.clone(), std::string::String::from("new_account")), + ], + ) + }, + crate::ExSolanaCoreOperation::SystemCreateAccountWithSeed { + from, + new_account, + base, + seed, + lamports, + space, + owner, + } => { + match crate::executor_solana_core_validate_positive_u64( + *lamports, + "execution_create_account_lamports_zero", + "System seeded account creation requires a positive lamport amount", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => { + return std::result::Result::Err(error); + }, + } + match validate_create_space(*space) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => { + return std::result::Result::Err(error); + }, + } + match crate::executor_solana_core_validate_distinct_pubkeys( + from, + new_account, + "execution_create_account_same_account", + "funding and new seeded account public keys must differ", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => { + return std::result::Result::Err(error); + }, + } + let from_address = match crate::executor_solana_core_parse_pubkey(from, "from") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let new_account_address = + match crate::executor_solana_core_parse_pubkey(new_account, "new_account") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => { + return std::result::Result::Err(error); + }, + }; + let base_address = match crate::executor_solana_core_parse_pubkey(base, "base") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let owner_address = match crate::executor_solana_core_parse_pubkey(owner, "owner") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + match crate::executor_solana_core_validate_seeded_address( + &new_account_address, + &base_address, + seed, + &owner_address, + "new_account", + "execution_seed_invalid", + "execution_seeded_address_mismatch", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => { + return std::result::Result::Err(error); + }, + } + ( + solana_system_interface::instruction::create_account_with_seed( + &from_address, + &new_account_address, + &base_address, + seed, + *lamports, + *space, + &owner_address, + ), + *lamports, + std::option::Option::None, + vec![ + (from.clone(), std::string::String::from("funding_account")), + (base.clone(), std::string::String::from("seed_base_account")), + ], + ) + }, + crate::ExSolanaCoreOperation::SystemCreateAccountAllowPrefund { + new_account, + payer, + lamports, + space, + owner, + } => { + match validate_create_space(*space) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => { + return std::result::Result::Err(error); + }, + } + if payer.is_none() && *lamports != 0 { + return std::result::Result::Err(kb_core::Error::new( + "execution_prefund_payer_missing", + "create-account-allow-prefund requires a payer when additional lamports are requested", + )); + } + if let std::option::Option::Some(payer_pubkey) = payer { + match crate::executor_solana_core_validate_distinct_pubkeys( + payer_pubkey, + new_account, + "execution_prefund_payer_is_new_account", + "prefund payer and new account public keys must differ", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => { + return std::result::Result::Err(error); + }, + } + } + let new_account_address = + match crate::executor_solana_core_parse_pubkey(new_account, "new_account") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => { + return std::result::Result::Err(error); + }, + }; + let owner_address = match crate::executor_solana_core_parse_pubkey(owner, "owner") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let payer_address = match payer { + std::option::Option::Some(payer_pubkey) => { + match crate::executor_solana_core_parse_pubkey(payer_pubkey, "payer") { + std::result::Result::Ok(pubkey) => std::option::Option::Some(pubkey), + std::result::Result::Err(error) => { + return std::result::Result::Err(error); + }, + } + }, + std::option::Option::None => std::option::Option::None, + }; + let instruction = match payer_address.as_ref() { + std::option::Option::Some(address) => { + solana_system_interface::instruction::create_account_allow_prefund( + &new_account_address, + std::option::Option::Some((address, *lamports)), + *space, + &owner_address, + ) + }, + std::option::Option::None => { + solana_system_interface::instruction::create_account_allow_prefund( + &new_account_address, + std::option::Option::None, + *space, + &owner_address, + ) + }, + }; + let mut roles = + vec![(new_account.clone(), std::string::String::from("new_account"))]; + if let std::option::Option::Some(payer_pubkey) = payer { + roles + .push((payer_pubkey.clone(), std::string::String::from("funding_account"))); + } + ( + instruction, + if payer.is_some() { *lamports } else { 0 }, + std::option::Option::None, + roles, + ) + }, + crate::ExSolanaCoreOperation::SystemAllocate { account, space } => { + match validate_allocate_space(*space) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => { + return std::result::Result::Err(error); + }, + } + let account_address = + match crate::executor_solana_core_parse_pubkey(account, "account") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + ( + solana_system_interface::instruction::allocate(&account_address, *space), + 0, + std::option::Option::None, + vec![(account.clone(), std::string::String::from("allocated_account"))], + ) + }, + crate::ExSolanaCoreOperation::SystemAllocateWithSeed { + account, + base, + seed, + space, + owner, + } => { + match validate_allocate_space(*space) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => { + return std::result::Result::Err(error); + }, + } + let account_address = + match crate::executor_solana_core_parse_pubkey(account, "account") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let base_address = match crate::executor_solana_core_parse_pubkey(base, "base") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let owner_address = match crate::executor_solana_core_parse_pubkey(owner, "owner") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + match crate::executor_solana_core_validate_seeded_address( + &account_address, + &base_address, + seed, + &owner_address, + "account", + "execution_seed_invalid", + "execution_seeded_address_mismatch", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => { + return std::result::Result::Err(error); + }, + } + ( + solana_system_interface::instruction::allocate_with_seed( + &account_address, + &base_address, + seed, + *space, + &owner_address, + ), + 0, + std::option::Option::None, + vec![(base.clone(), std::string::String::from("seed_base_account"))], + ) + }, + crate::ExSolanaCoreOperation::SystemAssign { account, owner } => { + let account_address = + match crate::executor_solana_core_parse_pubkey(account, "account") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let owner_address = match crate::executor_solana_core_parse_pubkey(owner, "owner") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + ( + solana_system_interface::instruction::assign(&account_address, &owner_address), + 0, + std::option::Option::None, + vec![(account.clone(), std::string::String::from("assigned_account"))], + ) + }, + crate::ExSolanaCoreOperation::SystemAssignWithSeed { account, base, seed, owner } => { + let account_address = + match crate::executor_solana_core_parse_pubkey(account, "account") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let base_address = match crate::executor_solana_core_parse_pubkey(base, "base") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let owner_address = match crate::executor_solana_core_parse_pubkey(owner, "owner") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + match crate::executor_solana_core_validate_seeded_address( + &account_address, + &base_address, + seed, + &owner_address, + "account", + "execution_seed_invalid", + "execution_seeded_address_mismatch", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => { + return std::result::Result::Err(error); + }, + } + ( + solana_system_interface::instruction::assign_with_seed( + &account_address, + &base_address, + seed, + &owner_address, + ), + 0, + std::option::Option::None, + vec![(base.clone(), std::string::String::from("seed_base_account"))], + ) + }, + crate::ExSolanaCoreOperation::ComputeBudgetRequestHeapFrame { bytes } => { + if !(MIN_HEAP_FRAME_BYTES..=MAX_HEAP_FRAME_BYTES).contains(bytes) + || *bytes % HEAP_FRAME_BYTES_GRANULARITY != 0 + { + return std::result::Result::Err(kb_core::Error::new( + "execution_compute_budget_heap_frame_invalid", + format!( + "heap frame must be between {} and {} bytes and aligned to {} bytes", + MIN_HEAP_FRAME_BYTES, + MAX_HEAP_FRAME_BYTES, + HEAP_FRAME_BYTES_GRANULARITY + ), + )); + } + ( + solana_compute_budget_interface::ComputeBudgetInstruction::request_heap_frame( + *bytes, + ), + 0, + std::option::Option::None, + std::vec::Vec::new(), + ) + }, + crate::ExSolanaCoreOperation::ComputeBudgetSetUnitLimit { units } => { + if *units == 0 || *units > MAX_COMPUTE_UNIT_LIMIT { + return std::result::Result::Err(kb_core::Error::new( + "execution_compute_unit_limit_out_of_range", + format!( + "compute-unit limit must be between 1 and {}", + MAX_COMPUTE_UNIT_LIMIT + ), + )); + } + ( + solana_compute_budget_interface::ComputeBudgetInstruction::set_compute_unit_limit( + *units, + ), + 0, + std::option::Option::None, + std::vec::Vec::new(), + ) + }, + crate::ExSolanaCoreOperation::ComputeBudgetSetUnitPrice { micro_lamports } => ( + solana_compute_budget_interface::ComputeBudgetInstruction::set_compute_unit_price( + *micro_lamports, + ), + 0, + std::option::Option::Some(*micro_lamports), + std::vec::Vec::new(), + ), + crate::ExSolanaCoreOperation::ComputeBudgetSetLoadedAccountsDataSizeLimit { bytes } => { + if !(1..=MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES).contains(bytes) { + return std::result::Result::Err(kb_core::Error::new( + "execution_compute_budget_loaded_accounts_data_size_limit_invalid", + format!( + "loaded-account data-size limit must be between 1 and {} bytes", + MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES + ), + )); + } + ( + solana_compute_budget_interface::ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + *bytes, + ), + 0, + std::option::Option::None, + std::vec::Vec::new(), + ) + }, + _ => { + return std::result::Result::Err(kb_core::Error::new( + "execution_solana_core_internal_dispatch_failed", + format!("operation {operation_code} was not routed to its dedicated builder"), + )); + }, + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + &fee_payer, + operation_code, + vec![instruction], + requested_spend_lamports, + requested_compute_unit_price, + signer_roles, + ); +} + +fn build_transfer_many_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + from: &crate::MdPubkey, + recipients: &[crate::ExSolanaCoreSystemTransferRecipient], +) -> kb_core::Result { + if recipients.is_empty() { + return std::result::Result::Err(kb_core::Error::new( + "execution_transfer_many_empty", + "System transfer-many requires at least one recipient", + )); + } + let from_address = match crate::executor_solana_core_parse_pubkey(from, "from") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let mut to_lamports = std::vec::Vec::with_capacity(recipients.len()); + let mut requested_spend_lamports = 0_u64; + for recipient in recipients { + match crate::executor_solana_core_validate_positive_u64( + recipient.lamports, + "execution_transfer_many_lamports_zero", + "every System transfer-many recipient requires a positive lamport amount", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match crate::executor_solana_core_validate_distinct_pubkeys( + from, + &recipient.to, + "execution_transfer_many_same_account", + "System transfer-many source and every destination must differ", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let to_address = match crate::executor_solana_core_parse_pubkey(&recipient.to, "recipient") + { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + requested_spend_lamports = match requested_spend_lamports.checked_add(recipient.lamports) { + std::option::Option::Some(value) => value, + std::option::Option::None => { + return std::result::Result::Err(kb_core::Error::new( + "execution_transfer_many_lamports_overflow", + "System transfer-many total lamports exceed u64", + )); + }, + }; + to_lamports.push((to_address, recipient.lamports)); + } + let instructions = + solana_system_interface::instruction::transfer_many(&from_address, to_lamports.as_slice()); + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_SYSTEM_TRANSFER_MANY_OPERATION, + instructions, + requested_spend_lamports, + std::option::Option::None, + vec![(from.clone(), std::string::String::from("funding_account"))], + ); +} + +fn build_create_nonce_account_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + from: &crate::MdPubkey, + nonce_account: &crate::MdPubkey, + authority: &crate::MdPubkey, + lamports: u64, +) -> kb_core::Result { + match validate_nonce_management_policy(intent) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match crate::executor_solana_core_validate_positive_u64( + lamports, + "execution_nonce_create_lamports_zero", + "durable nonce account creation requires a positive rent-exempt lamport amount", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match crate::executor_solana_core_validate_distinct_pubkeys( + from, + nonce_account, + "execution_nonce_create_same_account", + "nonce funding account and nonce account must differ", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let from_address = match crate::executor_solana_core_parse_pubkey(from, "from") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let nonce_address = + match crate::executor_solana_core_parse_pubkey(nonce_account, "nonce_account") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority_address = match crate::executor_solana_core_parse_pubkey(authority, "authority") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instructions = solana_system_interface::instruction::create_nonce_account( + &from_address, + &nonce_address, + &authority_address, + lamports, + ); + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_SYSTEM_NONCE_CREATE_OPERATION, + instructions, + lamports, + std::option::Option::None, + vec![ + (from.clone(), std::string::String::from("funding_account")), + (nonce_account.clone(), std::string::String::from("new_nonce_account")), + ], + ); +} + +fn build_create_nonce_account_with_seed_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + operation: &crate::ExSolanaCoreOperation, +) -> kb_core::Result { + let (from, nonce_account, base, seed, authority, lamports) = match operation { + crate::ExSolanaCoreOperation::SystemCreateNonceAccountWithSeed { + from, + nonce_account, + base, + seed, + authority, + lamports, + } => (from, nonce_account, base, seed, authority, *lamports), + _ => { + return std::result::Result::Err(kb_core::Error::new( + "execution_solana_core_internal_dispatch_failed", + "seeded nonce builder received an incompatible operation", + )); + }, + }; + match validate_nonce_management_policy(intent) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match crate::executor_solana_core_validate_positive_u64( + lamports, + "execution_nonce_create_lamports_zero", + "seeded durable nonce account creation requires a positive rent-exempt lamport amount", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match crate::executor_solana_core_validate_distinct_pubkeys( + from, + nonce_account, + "execution_nonce_create_same_account", + "nonce funding account and nonce account must differ", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let from_address = match crate::executor_solana_core_parse_pubkey(from, "from") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let nonce_address = + match crate::executor_solana_core_parse_pubkey(nonce_account, "nonce_account") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let base_address = match crate::executor_solana_core_parse_pubkey(base, "base") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority_address = match crate::executor_solana_core_parse_pubkey(authority, "authority") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let system_program = match solana_pubkey::Pubkey::from_str(kb_program_ids::SYSTEM_PROGRAM_ID) { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => { + return std::result::Result::Err(kb_core::Error::new( + "execution_system_program_id_invalid", + error.to_string(), + )); + }, + }; + match crate::executor_solana_core_validate_seeded_address( + &nonce_address, + &base_address, + seed, + &system_program, + "nonce_account", + "execution_seed_invalid", + "execution_seeded_address_mismatch", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let instructions = solana_system_interface::instruction::create_nonce_account_with_seed( + &from_address, + &nonce_address, + &base_address, + seed, + &authority_address, + lamports, + ); + let mut signer_roles = vec![(from.clone(), std::string::String::from("funding_account"))]; + if base != from { + signer_roles.push((base.clone(), std::string::String::from("seed_base_account"))); + } + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_SYSTEM_NONCE_CREATE_WITH_SEED_OPERATION, + instructions, + lamports, + std::option::Option::None, + signer_roles, + ); +} + +fn build_initialize_nonce_account_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + nonce_account: &crate::MdPubkey, + authority: &crate::MdPubkey, +) -> kb_core::Result { + match validate_nonce_management_policy(intent) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let nonce_address = + match crate::executor_solana_core_parse_pubkey(nonce_account, "nonce_account") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority_address = match crate::executor_solana_core_parse_pubkey(authority, "authority") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instruction = match initialize_nonce_account_instruction(&nonce_address, &authority_address) + { + std::result::Result::Ok(instruction) => instruction, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_SYSTEM_NONCE_INITIALIZE_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + std::vec::Vec::new(), + ); +} + +fn build_advance_nonce_account_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + nonce_account: &crate::MdPubkey, + authority: &crate::MdPubkey, +) -> kb_core::Result { + match validate_nonce_management_policy(intent) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let nonce_address = + match crate::executor_solana_core_parse_pubkey(nonce_account, "nonce_account") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority_address = match crate::executor_solana_core_parse_pubkey(authority, "authority") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_SYSTEM_NONCE_ADVANCE_OPERATION, + vec![solana_system_interface::instruction::advance_nonce_account( + &nonce_address, + &authority_address, + )], + 0, + std::option::Option::None, + vec![(authority.clone(), std::string::String::from("nonce_authority"))], + ); +} + +fn build_authorize_nonce_account_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + nonce_account: &crate::MdPubkey, + authority: &crate::MdPubkey, + new_authority: &crate::MdPubkey, +) -> kb_core::Result { + match validate_nonce_management_policy(intent) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let nonce_address = + match crate::executor_solana_core_parse_pubkey(nonce_account, "nonce_account") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority_address = match crate::executor_solana_core_parse_pubkey(authority, "authority") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let new_authority_address = + match crate::executor_solana_core_parse_pubkey(new_authority, "new_authority") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_SYSTEM_NONCE_AUTHORIZE_OPERATION, + vec![solana_system_interface::instruction::authorize_nonce_account( + &nonce_address, + &authority_address, + &new_authority_address, + )], + 0, + std::option::Option::None, + vec![(authority.clone(), std::string::String::from("nonce_authority"))], + ); +} + +fn build_withdraw_nonce_account_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + nonce_account: &crate::MdPubkey, + authority: &crate::MdPubkey, + to: &crate::MdPubkey, + lamports: u64, +) -> kb_core::Result { + match validate_nonce_management_policy(intent) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match crate::executor_solana_core_validate_positive_u64( + lamports, + "execution_nonce_withdraw_lamports_zero", + "durable nonce withdrawal requires a positive lamport amount", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match crate::executor_solana_core_validate_distinct_pubkeys( + nonce_account, + to, + "execution_nonce_withdraw_same_account", + "nonce account and withdrawal recipient must differ", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let nonce_address = + match crate::executor_solana_core_parse_pubkey(nonce_account, "nonce_account") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority_address = match crate::executor_solana_core_parse_pubkey(authority, "authority") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let to_address = match crate::executor_solana_core_parse_pubkey(to, "to") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_SYSTEM_NONCE_WITHDRAW_OPERATION, + vec![solana_system_interface::instruction::withdraw_nonce_account( + &nonce_address, + &authority_address, + &to_address, + lamports, + )], + lamports, + std::option::Option::None, + vec![(authority.clone(), std::string::String::from("nonce_authority"))], + ); +} + +fn build_upgrade_nonce_account_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + nonce_account: &crate::MdPubkey, +) -> kb_core::Result { + match validate_nonce_management_policy(intent) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let nonce_address = + match crate::executor_solana_core_parse_pubkey(nonce_account, "nonce_account") { + std::result::Result::Ok(pubkey) => pubkey, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_SYSTEM_NONCE_UPGRADE_OPERATION, + vec![solana_system_interface::instruction::upgrade_nonce_account(nonce_address)], + 0, + std::option::Option::None, + std::vec::Vec::new(), + ); +} + +fn validate_nonce_management_policy( + intent: &crate::ExSolanaCoreExecutionIntent, +) -> kb_core::Result<()> { + if intent.policy.blockhash.kind != crate::ExApiExecutionBlockhashKind::Latest { + return std::result::Result::Err(kb_core::Error::new( + "execution_nonce_management_requires_latest_blockhash", + "nonce account lifecycle operations use a recent blockhash; durable nonce transaction assembly is a separate execution path", + )); + } + return std::result::Result::Ok(()); +} + +#[allow(deprecated)] +fn initialize_nonce_account_instruction( + nonce_account: &solana_pubkey::Pubkey, + authority: &solana_pubkey::Pubkey, +) -> kb_core::Result { + let data = match wincode::serialize( + &solana_system_interface::instruction::SystemInstruction::InitializeNonceAccount( + *authority, + ), + ) { + std::result::Result::Ok(data) => data, + std::result::Result::Err(error) => { + return std::result::Result::Err(kb_core::Error::new( + "execution_nonce_initialize_serialize_failed", + error.to_string(), + )); + }, + }; + return std::result::Result::Ok(solana_instruction::Instruction { + program_id: solana_sdk_ids::system_program::id(), + accounts: vec![ + solana_instruction::AccountMeta::new(*nonce_account, false), + solana_instruction::AccountMeta::new_readonly( + solana_sdk_ids::sysvar::recent_blockhashes::id(), + false, + ), + solana_instruction::AccountMeta::new_readonly( + solana_sdk_ids::sysvar::rent::id(), + false, + ), + ], + data, + }); +} + +pub(crate) fn executor_solana_core_finalize_prepared_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + operation_code: &str, + instructions: std::vec::Vec, + requested_spend_lamports: u64, + requested_compute_unit_price: std::option::Option, + signer_roles: std::vec::Vec<(crate::MdPubkey, std::string::String)>, +) -> kb_core::Result { + if instructions.is_empty() { + return std::result::Result::Err(kb_core::Error::new( + "execution_operation_has_no_instructions", + format!("operation {operation_code} produced no instructions"), + )); + } + let planned_instructions = instructions + .iter() + .map(|instruction| return planned_instruction(operation_code, instruction)) + .collect::>(); + let mut signer_roles = signer_roles; + if intent.policy.blockhash.kind == crate::ExApiExecutionBlockhashKind::DurableNonce { + if intent.policy.blockhash.max_age_slots.is_some() { + return std::result::Result::Err(kb_core::Error::new( + "execution_durable_nonce_has_blockhash_age", + "durable nonce plans must not declare a recent blockhash age", + )); + } + if intent.policy.blockhash.nonce_account.is_none() { + return std::result::Result::Err(kb_core::Error::new( + "execution_durable_nonce_account_missing", + "durable nonce plans require a nonce account", + )); + } + let nonce_authority = match &intent.policy.blockhash.nonce_authority { + std::option::Option::Some(authority) => authority.clone(), + std::option::Option::None => { + return std::result::Result::Err(kb_core::Error::new( + "execution_durable_nonce_authority_missing", + "durable nonce plans require a nonce authority", + )); + }, + }; + signer_roles.push((nonce_authority, std::string::String::from("durable_nonce_authority"))); + } + let required_signers = required_signers(&intent.fee_payer, signer_roles.as_slice()); + match validate_signer_contract(planned_instructions.as_slice(), &required_signers) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + tracing::debug!( + target: crate::EX_SOLANA_CORE_TRACING_TARGET, + action = "executor_solana_core_builder_build_prepared_plan", + intent_id = %intent.intent_id, + operation_code, + instruction_count = planned_instructions.len(), + signer_count = required_signers.len(), + "built Solana core execution plan" + ); + return std::result::Result::Ok(crate::ExApiPreparedExecutionPlan { + executor_name: std::string::String::from("kb_executor_solana_core"), + executor_version: std::string::String::from(env!("CARGO_PKG_VERSION")), + intent_id: intent.intent_id.clone(), + operation_code: std::string::String::from(operation_code), + fee_payer: crate::MdPubkey(fee_payer.to_string()), + instructions: planned_instructions, + required_signers, + policy: intent.policy.clone(), + requested_spend_lamports, + requested_compute_unit_price_micro_lamports: requested_compute_unit_price, + }); +} + +fn validate_create_space(space: u64) -> kb_core::Result<()> { + if space > solana_system_interface::MAX_PERMITTED_DATA_LENGTH { + return std::result::Result::Err(kb_core::Error::new( + "execution_system_space_out_of_range", + format!( + "System account space must not exceed {} bytes", + solana_system_interface::MAX_PERMITTED_DATA_LENGTH + ), + )); + } + return std::result::Result::Ok(()); +} + +fn validate_allocate_space(space: u64) -> kb_core::Result<()> { + if space == 0 || space > solana_system_interface::MAX_PERMITTED_DATA_LENGTH { + return std::result::Result::Err(kb_core::Error::new( + "execution_system_space_out_of_range", + format!( + "System allocation space must be between 1 and {} bytes", + solana_system_interface::MAX_PERMITTED_DATA_LENGTH + ), + )); + } + return std::result::Result::Ok(()); +} + +fn planned_instruction( + operation_code: &str, + instruction: &solana_instruction::Instruction, +) -> crate::ExApiPlannedInstruction { + return crate::ExApiPlannedInstruction { + program_id: crate::MdProgramId(instruction.program_id.to_string()), + operation_code: std::string::String::from(operation_code), + accounts: instruction + .accounts + .iter() + .map(|account| { + return crate::ExApiPlannedAccount { + pubkey: crate::MdPubkey(account.pubkey.to_string()), + is_signer: account.is_signer, + is_writable: account.is_writable, + }; + }) + .collect(), + data: instruction.data.clone(), + }; +} + +fn required_signers( + fee_payer: &crate::MdPubkey, + signer_roles: &[(crate::MdPubkey, std::string::String)], +) -> std::vec::Vec { + let mut required = vec![crate::ExApiRequiredSigner { + pubkey: fee_payer.clone(), + role: std::string::String::from("fee_payer"), + }]; + for (pubkey, role) in signer_roles { + if required.iter().any(|candidate| return candidate.pubkey == *pubkey) { + continue; + } + required.push(crate::ExApiRequiredSigner { + pubkey: pubkey.clone(), + role: role.clone(), + }); + } + return required; +} + +fn validate_signer_contract( + instructions: &[crate::ExApiPlannedInstruction], + required_signers: &[crate::ExApiRequiredSigner], +) -> kb_core::Result<()> { + for instruction in instructions { + for account in &instruction.accounts { + if account.is_signer + && !required_signers.iter().any(|signer| return signer.pubkey == account.pubkey) + { + return std::result::Result::Err(kb_core::Error::new( + "execution_instruction_signer_not_declared", + format!( + "instruction signer {} is missing from required signers", + account.pubkey.0 + ), + )); + } + } + } + return std::result::Result::Ok(()); +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; // rust-rules: trait-import + + fn pubkey(value: &str) -> crate::MdPubkey { + return crate::MdPubkey(std::string::String::from(value)); + } + + fn parsed(value: &str) -> solana_pubkey::Pubkey { + return solana_pubkey::Pubkey::from_str(value) + .unwrap_or_else(|error| panic!("invalid fixture: {error}")); + } + + fn policy(signers: &[&str]) -> crate::ExApiExecutionPolicy { + return crate::ExApiExecutionPolicy { + cost_limit: crate::ExApiExecutionCostLimit { + max_spend_lamports: std::option::Option::Some(10_000), + max_fee_lamports: std::option::Option::Some(10_000), + max_compute_unit_price_micro_lamports: std::option::Option::Some(100), + }, + authorized_signers: signers.iter().map(|value| return pubkey(value)).collect(), + ..crate::ExApiExecutionPolicy::default() + }; + } + + fn assert_matches_official( + plan: &crate::ExApiPreparedExecutionPlan, + expected: &solana_instruction::Instruction, + ) { + assert_eq!(plan.instructions.len(), 1); + let actual = &plan.instructions[0]; + assert_eq!(actual.program_id.0, expected.program_id.to_string()); + assert_eq!(actual.data, expected.data); + assert_eq!(actual.accounts.len(), expected.accounts.len()); + for (actual_account, expected_account) in + actual.accounts.iter().zip(expected.accounts.iter()) + { + assert_eq!(actual_account.pubkey.0, expected_account.pubkey.to_string()); + assert_eq!(actual_account.is_signer, expected_account.is_signer); + assert_eq!(actual_account.is_writable, expected_account.is_writable); + } + } + + fn assert_many_match_official( + plan: &crate::ExApiPreparedExecutionPlan, + expected: &[solana_instruction::Instruction], + ) { + assert_eq!(plan.instructions.len(), expected.len()); + for (actual, expected_instruction) in plan.instructions.iter().zip(expected.iter()) { + assert_eq!(actual.program_id.0, expected_instruction.program_id.to_string()); + assert_eq!(actual.data, expected_instruction.data); + assert_eq!(actual.accounts.len(), expected_instruction.accounts.len()); + for (actual_account, expected_account) in + actual.accounts.iter().zip(expected_instruction.accounts.iter()) + { + assert_eq!(actual_account.pubkey.0, expected_account.pubkey.to_string()); + assert_eq!(actual_account.is_signer, expected_account.is_signer); + assert_eq!(actual_account.is_writable, expected_account.is_writable); + } + } + } + + #[test] + fn transfer_matches_official_builder_and_signers() { + let from = kb_program_ids::VOTE_PROGRAM_ID; + let to = kb_program_ids::STAKE_PROGRAM_ID; + let intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("transfer-1"), + fee_payer: pubkey(from), + policy: policy(&[from]), + operation: crate::ExSolanaCoreOperation::SystemTransfer { + from: pubkey(from), + to: pubkey(to), + lamports: 500, + }, + }; + let plan = crate::executor_solana_core_builder_build_prepared_plan(&intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + let expected = + solana_system_interface::instruction::transfer(&parsed(from), &parsed(to), 500); + assert_matches_official(&plan, &expected); + assert_eq!(plan.operation_code, crate::EX_SOLANA_CORE_SYSTEM_TRANSFER_OPERATION); + assert_eq!(plan.requested_spend_lamports, 500); + assert_eq!(plan.required_signers.len(), 1); + assert_eq!(plan.required_signers[0].pubkey.0, from); + } + + #[test] + fn create_account_matches_official_builder_and_allows_zero_space() { + let from = kb_program_ids::VOTE_PROGRAM_ID; + let new_account = kb_program_ids::STAKE_PROGRAM_ID; + let owner = kb_program_ids::SYSTEM_PROGRAM_ID; + let intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("create-1"), + fee_payer: pubkey(from), + policy: policy(&[from, new_account]), + operation: crate::ExSolanaCoreOperation::SystemCreateAccount { + from: pubkey(from), + new_account: pubkey(new_account), + lamports: 5_000, + space: 0, + owner: pubkey(owner), + }, + }; + let plan = crate::executor_solana_core_builder_build_prepared_plan(&intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + let expected = solana_system_interface::instruction::create_account( + &parsed(from), + &parsed(new_account), + 5_000, + 0, + &parsed(owner), + ); + assert_matches_official(&plan, &expected); + assert_eq!(plan.operation_code, crate::EX_SOLANA_CORE_SYSTEM_CREATE_ACCOUNT_OPERATION); + assert_eq!(plan.required_signers.len(), 2); + } + + #[test] + fn allocate_and_assign_match_official_builders() { + let account = kb_program_ids::VOTE_PROGRAM_ID; + let owner = kb_program_ids::STAKE_PROGRAM_ID; + let allocate_intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("allocate-1"), + fee_payer: pubkey(account), + policy: policy(&[account]), + operation: crate::ExSolanaCoreOperation::SystemAllocate { + account: pubkey(account), + space: 256, + }, + }; + let allocate_plan = + crate::executor_solana_core_builder_build_prepared_plan(&allocate_intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + let account_address = parsed(account); + let allocate_expected = + solana_system_interface::instruction::allocate(&account_address, 256); + assert_matches_official(&allocate_plan, &allocate_expected); + assert_eq!(allocate_plan.operation_code, crate::EX_SOLANA_CORE_SYSTEM_ALLOCATE_OPERATION); + let assign_intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("assign-1"), + fee_payer: pubkey(account), + policy: policy(&[account]), + operation: crate::ExSolanaCoreOperation::SystemAssign { + account: pubkey(account), + owner: pubkey(owner), + }, + }; + let assign_plan = crate::executor_solana_core_builder_build_prepared_plan(&assign_intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + let owner_address = parsed(owner); + let assign_expected = + solana_system_interface::instruction::assign(&account_address, &owner_address); + assert_matches_official(&assign_plan, &assign_expected); + assert_eq!(assign_plan.operation_code, crate::EX_SOLANA_CORE_SYSTEM_ASSIGN_OPERATION); + } + + #[test] + fn seeded_system_operations_match_official_builders() { + let base = parsed(kb_program_ids::VOTE_PROGRAM_ID); + let owner = parsed(kb_program_ids::SYSTEM_PROGRAM_ID); + let destination = parsed(kb_program_ids::STAKE_PROGRAM_ID); + let seed = "khadhroony-seed"; + let derived = solana_pubkey::Pubkey::create_with_seed(&base, seed, &owner) + .unwrap_or_else(|error| panic!("seed fixture failed: {error}")); + let base_text = base.to_string(); + let owner_text = owner.to_string(); + let derived_text = derived.to_string(); + let destination_text = destination.to_string(); + + let create_intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("create-seed-1"), + fee_payer: pubkey(base_text.as_str()), + policy: policy(&[base_text.as_str()]), + operation: crate::ExSolanaCoreOperation::SystemCreateAccountWithSeed { + from: pubkey(base_text.as_str()), + new_account: pubkey(derived_text.as_str()), + base: pubkey(base_text.as_str()), + seed: std::string::String::from(seed), + lamports: 5_000, + space: 0, + owner: pubkey(owner_text.as_str()), + }, + }; + let create_plan = crate::executor_solana_core_builder_build_prepared_plan(&create_intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + let create_expected = solana_system_interface::instruction::create_account_with_seed( + &base, &derived, &base, seed, 5_000, 0, &owner, + ); + assert_matches_official(&create_plan, &create_expected); + assert_eq!( + create_plan.operation_code, + crate::EX_SOLANA_CORE_SYSTEM_CREATE_ACCOUNT_WITH_SEED_OPERATION + ); + assert_eq!(create_plan.required_signers.len(), 1); + + let transfer_intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("transfer-seed-1"), + fee_payer: pubkey(base_text.as_str()), + policy: policy(&[base_text.as_str()]), + operation: crate::ExSolanaCoreOperation::SystemTransferWithSeed { + from: pubkey(derived_text.as_str()), + from_base: pubkey(base_text.as_str()), + from_seed: std::string::String::from(seed), + from_owner: pubkey(owner_text.as_str()), + to: pubkey(destination_text.as_str()), + lamports: 500, + }, + }; + let transfer_plan = + crate::executor_solana_core_builder_build_prepared_plan(&transfer_intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + let transfer_expected = solana_system_interface::instruction::transfer_with_seed( + &derived, + &base, + std::string::String::from(seed), + &owner, + &destination, + 500, + ); + assert_matches_official(&transfer_plan, &transfer_expected); + assert_eq!( + transfer_plan.operation_code, + crate::EX_SOLANA_CORE_SYSTEM_TRANSFER_WITH_SEED_OPERATION + ); + + let allocate_intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("allocate-seed-1"), + fee_payer: pubkey(base_text.as_str()), + policy: policy(&[base_text.as_str()]), + operation: crate::ExSolanaCoreOperation::SystemAllocateWithSeed { + account: pubkey(derived_text.as_str()), + base: pubkey(base_text.as_str()), + seed: std::string::String::from(seed), + space: 256, + owner: pubkey(owner_text.as_str()), + }, + }; + let allocate_plan = + crate::executor_solana_core_builder_build_prepared_plan(&allocate_intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + let allocate_expected = solana_system_interface::instruction::allocate_with_seed( + &derived, &base, seed, 256, &owner, + ); + assert_matches_official(&allocate_plan, &allocate_expected); + assert_eq!( + allocate_plan.operation_code, + crate::EX_SOLANA_CORE_SYSTEM_ALLOCATE_WITH_SEED_OPERATION + ); + + let assign_intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("assign-seed-1"), + fee_payer: pubkey(base_text.as_str()), + policy: policy(&[base_text.as_str()]), + operation: crate::ExSolanaCoreOperation::SystemAssignWithSeed { + account: pubkey(derived_text.as_str()), + base: pubkey(base_text.as_str()), + seed: std::string::String::from(seed), + owner: pubkey(owner_text.as_str()), + }, + }; + let assign_plan = crate::executor_solana_core_builder_build_prepared_plan(&assign_intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + let assign_expected = + solana_system_interface::instruction::assign_with_seed(&derived, &base, seed, &owner); + assert_matches_official(&assign_plan, &assign_expected); + assert_eq!( + assign_plan.operation_code, + crate::EX_SOLANA_CORE_SYSTEM_ASSIGN_WITH_SEED_OPERATION + ); + } + + #[test] + fn seeded_create_with_distinct_funding_and_base_declares_both_signers() { + let funding = parsed(kb_program_ids::VOTE_PROGRAM_ID); + let base = parsed(kb_program_ids::CONFIG_PROGRAM_ID); + let owner = parsed(kb_program_ids::SYSTEM_PROGRAM_ID); + let seed = "khadhroony-distinct-base"; + let derived = solana_pubkey::Pubkey::create_with_seed(&base, seed, &owner) + .unwrap_or_else(|error| panic!("seed fixture failed: {error}")); + let funding_text = funding.to_string(); + let base_text = base.to_string(); + let owner_text = owner.to_string(); + let derived_text = derived.to_string(); + let intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("create-seed-distinct-base"), + fee_payer: pubkey(funding_text.as_str()), + policy: policy(&[funding_text.as_str(), base_text.as_str()]), + operation: crate::ExSolanaCoreOperation::SystemCreateAccountWithSeed { + from: pubkey(funding_text.as_str()), + new_account: pubkey(derived_text.as_str()), + base: pubkey(base_text.as_str()), + seed: std::string::String::from(seed), + lamports: 5_000, + space: 0, + owner: pubkey(owner_text.as_str()), + }, + }; + let plan = crate::executor_solana_core_builder_build_prepared_plan(&intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + let expected = solana_system_interface::instruction::create_account_with_seed( + &funding, &derived, &base, seed, 5_000, 0, &owner, + ); + assert_matches_official(&plan, &expected); + assert_eq!(plan.required_signers.len(), 2); + assert!( + plan.required_signers + .iter() + .any(|signer| return signer.pubkey.0 == funding_text) + ); + assert!(plan.required_signers.iter().any(|signer| return signer.pubkey.0 == base_text)); + } + + #[test] + fn create_account_allow_prefund_matches_official_builder() { + let payer = kb_program_ids::VOTE_PROGRAM_ID; + let new_account = kb_program_ids::STAKE_PROGRAM_ID; + let owner = kb_program_ids::SYSTEM_PROGRAM_ID; + let funded_intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("prefund-funded"), + fee_payer: pubkey(payer), + policy: policy(&[payer, new_account]), + operation: crate::ExSolanaCoreOperation::SystemCreateAccountAllowPrefund { + new_account: pubkey(new_account), + payer: std::option::Option::Some(pubkey(payer)), + lamports: 5_000, + space: 0, + owner: pubkey(owner), + }, + }; + let funded_plan = crate::executor_solana_core_builder_build_prepared_plan(&funded_intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + let payer_address = parsed(payer); + let new_account_address = parsed(new_account); + let owner_address = parsed(owner); + let funded_expected = solana_system_interface::instruction::create_account_allow_prefund( + &new_account_address, + std::option::Option::Some((&payer_address, 5_000)), + 0, + &owner_address, + ); + assert_matches_official(&funded_plan, &funded_expected); + assert_eq!( + funded_plan.operation_code, + crate::EX_SOLANA_CORE_SYSTEM_CREATE_ACCOUNT_ALLOW_PREFUND_OPERATION + ); + assert_eq!(funded_plan.requested_spend_lamports, 5_000); + assert_eq!(funded_plan.required_signers.len(), 2); + + let prefunded_account = kb_program_ids::CONFIG_PROGRAM_ID; + let prefunded_intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("prefund-existing"), + fee_payer: pubkey(payer), + policy: policy(&[payer, prefunded_account]), + operation: crate::ExSolanaCoreOperation::SystemCreateAccountAllowPrefund { + new_account: pubkey(prefunded_account), + payer: std::option::Option::None, + lamports: 0, + space: 0, + owner: pubkey(owner), + }, + }; + let prefunded_plan = + crate::executor_solana_core_builder_build_prepared_plan(&prefunded_intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + let prefunded_address = parsed(prefunded_account); + let prefunded_expected = solana_system_interface::instruction::create_account_allow_prefund( + &prefunded_address, + std::option::Option::None, + 0, + &owner_address, + ); + assert_matches_official(&prefunded_plan, &prefunded_expected); + assert_eq!(prefunded_plan.requested_spend_lamports, 0); + } + + #[test] + fn transfer_many_matches_official_builder_and_sums_spend() { + let from = kb_program_ids::VOTE_PROGRAM_ID; + let first = kb_program_ids::STAKE_PROGRAM_ID; + let second = kb_program_ids::CONFIG_PROGRAM_ID; + let intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("transfer-many-1"), + fee_payer: pubkey(from), + policy: policy(&[from]), + operation: crate::ExSolanaCoreOperation::SystemTransferMany { + from: pubkey(from), + recipients: vec![ + crate::ExSolanaCoreSystemTransferRecipient { to: pubkey(first), lamports: 400 }, + crate::ExSolanaCoreSystemTransferRecipient { + to: pubkey(second), + lamports: 600, + }, + ], + }, + }; + let plan = crate::executor_solana_core_builder_build_prepared_plan(&intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + let expected = solana_system_interface::instruction::transfer_many( + &parsed(from), + &[(parsed(first), 400), (parsed(second), 600)], + ); + assert_many_match_official(&plan, expected.as_slice()); + assert_eq!(plan.operation_code, crate::EX_SOLANA_CORE_SYSTEM_TRANSFER_MANY_OPERATION); + assert_eq!(plan.requested_spend_lamports, 1_000); + assert_eq!(plan.required_signers.len(), 1); + } + + #[test] + fn nonce_creation_helpers_match_official_builders() { + let from = kb_program_ids::VOTE_PROGRAM_ID; + let nonce = kb_program_ids::STAKE_PROGRAM_ID; + let authority = kb_program_ids::CONFIG_PROGRAM_ID; + let create_intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("nonce-create-1"), + fee_payer: pubkey(from), + policy: policy(&[from, nonce]), + operation: crate::ExSolanaCoreOperation::SystemCreateNonceAccount { + from: pubkey(from), + nonce_account: pubkey(nonce), + authority: pubkey(authority), + lamports: 5_000, + }, + }; + let create_plan = crate::executor_solana_core_builder_build_prepared_plan(&create_intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + let create_expected = solana_system_interface::instruction::create_nonce_account( + &parsed(from), + &parsed(nonce), + &parsed(authority), + 5_000, + ); + assert_many_match_official(&create_plan, create_expected.as_slice()); + assert_eq!(create_plan.required_signers.len(), 2); + assert_eq!(create_plan.requested_spend_lamports, 5_000); + + let base = parsed(kb_program_ids::STAKE_PROGRAM_ID); + let system = parsed(kb_program_ids::SYSTEM_PROGRAM_ID); + let seed = "nonce-seed"; + let seeded_nonce = solana_pubkey::Pubkey::create_with_seed(&base, seed, &system) + .unwrap_or_else(|error| panic!("seed fixture failed: {error}")); + let seeded_nonce_text = seeded_nonce.to_string(); + let seeded_intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("nonce-create-seed-1"), + fee_payer: pubkey(from), + policy: policy(&[from, kb_program_ids::STAKE_PROGRAM_ID]), + operation: crate::ExSolanaCoreOperation::SystemCreateNonceAccountWithSeed { + from: pubkey(from), + nonce_account: pubkey(seeded_nonce_text.as_str()), + base: pubkey(kb_program_ids::STAKE_PROGRAM_ID), + seed: std::string::String::from(seed), + authority: pubkey(authority), + lamports: 5_000, + }, + }; + let seeded_plan = crate::executor_solana_core_builder_build_prepared_plan(&seeded_intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + let seeded_expected = solana_system_interface::instruction::create_nonce_account_with_seed( + &parsed(from), + &seeded_nonce, + &base, + seed, + &parsed(authority), + 5_000, + ); + assert_many_match_official(&seeded_plan, seeded_expected.as_slice()); + assert_eq!(seeded_plan.required_signers.len(), 2); + } + + #[test] + fn nonce_lifecycle_operations_match_official_builders() { + let payer = kb_program_ids::VOTE_PROGRAM_ID; + let nonce = kb_program_ids::STAKE_PROGRAM_ID; + let authority = kb_program_ids::CONFIG_PROGRAM_ID; + let new_authority = kb_program_ids::FEATURE_PROGRAM_ID; + let recipient = kb_program_ids::ADDRESS_LOOKUP_TABLE_PROGRAM_ID; + let base_policy = policy(&[payer, authority]); + + let initialize_intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("nonce-initialize-1"), + fee_payer: pubkey(payer), + policy: base_policy.clone(), + operation: crate::ExSolanaCoreOperation::SystemInitializeNonceAccount { + nonce_account: pubkey(nonce), + authority: pubkey(authority), + }, + }; + let initialize_plan = + crate::executor_solana_core_builder_build_prepared_plan(&initialize_intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + let initialize_expected = solana_system_interface::instruction::create_nonce_account( + &parsed(payer), + &parsed(nonce), + &parsed(authority), + 1, + ); + assert_matches_official(&initialize_plan, &initialize_expected[1]); + assert_eq!(initialize_plan.required_signers.len(), 1); + + let advance_intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("nonce-advance-1"), + fee_payer: pubkey(payer), + policy: base_policy.clone(), + operation: crate::ExSolanaCoreOperation::SystemAdvanceNonceAccount { + nonce_account: pubkey(nonce), + authority: pubkey(authority), + }, + }; + let advance_plan = crate::executor_solana_core_builder_build_prepared_plan(&advance_intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + let advance_expected = solana_system_interface::instruction::advance_nonce_account( + &parsed(nonce), + &parsed(authority), + ); + assert_matches_official(&advance_plan, &advance_expected); + + let authorize_intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("nonce-authorize-1"), + fee_payer: pubkey(payer), + policy: base_policy.clone(), + operation: crate::ExSolanaCoreOperation::SystemAuthorizeNonceAccount { + nonce_account: pubkey(nonce), + authority: pubkey(authority), + new_authority: pubkey(new_authority), + }, + }; + let authorize_plan = + crate::executor_solana_core_builder_build_prepared_plan(&authorize_intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + let authorize_expected = solana_system_interface::instruction::authorize_nonce_account( + &parsed(nonce), + &parsed(authority), + &parsed(new_authority), + ); + assert_matches_official(&authorize_plan, &authorize_expected); + + let withdraw_intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("nonce-withdraw-1"), + fee_payer: pubkey(payer), + policy: base_policy.clone(), + operation: crate::ExSolanaCoreOperation::SystemWithdrawNonceAccount { + nonce_account: pubkey(nonce), + authority: pubkey(authority), + to: pubkey(recipient), + lamports: 1_000, + }, + }; + let withdraw_plan = + crate::executor_solana_core_builder_build_prepared_plan(&withdraw_intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + let withdraw_expected = solana_system_interface::instruction::withdraw_nonce_account( + &parsed(nonce), + &parsed(authority), + &parsed(recipient), + 1_000, + ); + assert_matches_official(&withdraw_plan, &withdraw_expected); + assert_eq!(withdraw_plan.requested_spend_lamports, 1_000); + + let upgrade_intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("nonce-upgrade-1"), + fee_payer: pubkey(payer), + policy: base_policy, + operation: crate::ExSolanaCoreOperation::SystemUpgradeNonceAccount { + nonce_account: pubkey(nonce), + }, + }; + let upgrade_plan = crate::executor_solana_core_builder_build_prepared_plan(&upgrade_intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + let upgrade_expected = + solana_system_interface::instruction::upgrade_nonce_account(parsed(nonce)); + assert_matches_official(&upgrade_plan, &upgrade_expected); + assert_eq!(upgrade_plan.required_signers.len(), 1); + } + + #[test] + fn transfer_many_and_nonce_policy_reject_invalid_contracts() { + let payer = kb_program_ids::VOTE_PROGRAM_ID; + let empty_intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("transfer-many-empty"), + fee_payer: pubkey(payer), + policy: policy(&[payer]), + operation: crate::ExSolanaCoreOperation::SystemTransferMany { + from: pubkey(payer), + recipients: std::vec::Vec::new(), + }, + }; + let empty_error = crate::executor_solana_core_builder_build_prepared_plan(&empty_intent) + .expect_err("empty transfer-many must fail"); + assert_eq!(empty_error.code(), "execution_transfer_many_empty"); + + let mut nonce_policy = policy(&[payer]); + nonce_policy.blockhash = crate::ExApiExecutionBlockhashPolicy { + kind: crate::ExApiExecutionBlockhashKind::DurableNonce, + max_age_slots: std::option::Option::None, + nonce_account: std::option::Option::Some(pubkey(kb_program_ids::STAKE_PROGRAM_ID)), + nonce_authority: std::option::Option::Some(pubkey(payer)), + }; + let nonce_intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("nonce-management-durable-policy"), + fee_payer: pubkey(payer), + policy: nonce_policy, + operation: crate::ExSolanaCoreOperation::SystemAdvanceNonceAccount { + nonce_account: pubkey(kb_program_ids::STAKE_PROGRAM_ID), + authority: pubkey(payer), + }, + }; + let nonce_error = crate::executor_solana_core_builder_build_prepared_plan(&nonce_intent) + .expect_err("nonce lifecycle builder must not impersonate durable assembly"); + assert_eq!(nonce_error.code(), "execution_nonce_management_requires_latest_blockhash"); + } + + #[test] + fn durable_nonce_policy_declares_nonce_authority_signer() { + let payer = kb_program_ids::VOTE_PROGRAM_ID; + let authority = kb_program_ids::STAKE_PROGRAM_ID; + let nonce_account = kb_program_ids::ADDRESS_LOOKUP_TABLE_PROGRAM_ID; + let mut policy = policy(&[payer, authority]); + policy.blockhash = crate::ExApiExecutionBlockhashPolicy { + kind: crate::ExApiExecutionBlockhashKind::DurableNonce, + max_age_slots: std::option::Option::None, + nonce_account: std::option::Option::Some(pubkey(nonce_account)), + nonce_authority: std::option::Option::Some(pubkey(authority)), + }; + let intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("durable-transfer-1"), + fee_payer: pubkey(payer), + policy, + operation: crate::ExSolanaCoreOperation::SystemTransfer { + from: pubkey(payer), + to: pubkey(kb_program_ids::SYSTEM_PROGRAM_ID), + lamports: 500, + }, + }; + let plan = crate::executor_solana_core_builder_build_prepared_plan(&intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + assert_eq!(plan.required_signers.len(), 2); + assert!(plan.required_signers.iter().any(|required| { + return required.pubkey.0 == authority && required.role == "durable_nonce_authority"; + })); + } + + #[test] + fn compute_budget_operations_match_official_builders() { + let payer = kb_program_ids::VOTE_PROGRAM_ID; + let heap_intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("heap-1"), + fee_payer: pubkey(payer), + policy: policy(&[payer]), + operation: crate::ExSolanaCoreOperation::ComputeBudgetRequestHeapFrame { + bytes: 64 * 1_024, + }, + }; + let heap_plan = crate::executor_solana_core_builder_build_prepared_plan(&heap_intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + let heap_expected = + solana_compute_budget_interface::ComputeBudgetInstruction::request_heap_frame( + 64 * 1_024, + ); + assert_matches_official(&heap_plan, &heap_expected); + assert_eq!( + heap_plan.operation_code, + crate::EX_SOLANA_CORE_COMPUTE_BUDGET_REQUEST_HEAP_FRAME_OPERATION + ); + let limit_intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("limit-1"), + fee_payer: pubkey(payer), + policy: policy(&[payer]), + operation: crate::ExSolanaCoreOperation::ComputeBudgetSetUnitLimit { units: 500_000 }, + }; + let limit_plan = crate::executor_solana_core_builder_build_prepared_plan(&limit_intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + let limit_expected = + solana_compute_budget_interface::ComputeBudgetInstruction::set_compute_unit_limit( + 500_000, + ); + assert_matches_official(&limit_plan, &limit_expected); + assert_eq!( + limit_plan.operation_code, + crate::EX_SOLANA_CORE_COMPUTE_BUDGET_SET_UNIT_LIMIT_OPERATION + ); + let price_intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("price-1"), + fee_payer: pubkey(payer), + policy: policy(&[payer]), + operation: crate::ExSolanaCoreOperation::ComputeBudgetSetUnitPrice { + micro_lamports: 7, + }, + }; + let price_plan = crate::executor_solana_core_builder_build_prepared_plan(&price_intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + let price_expected = + solana_compute_budget_interface::ComputeBudgetInstruction::set_compute_unit_price(7); + assert_matches_official(&price_plan, &price_expected); + assert_eq!( + price_plan.operation_code, + crate::EX_SOLANA_CORE_COMPUTE_BUDGET_SET_UNIT_PRICE_OPERATION + ); + assert_eq!( + price_plan.requested_compute_unit_price_micro_lamports, + std::option::Option::Some(7) + ); + let loaded_data_intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("loaded-data-1"), + fee_payer: pubkey(payer), + policy: policy(&[payer]), + operation: crate::ExSolanaCoreOperation::ComputeBudgetSetLoadedAccountsDataSizeLimit { + bytes: 8 * 1_024 * 1_024, + }, + }; + let loaded_data_plan = + crate::executor_solana_core_builder_build_prepared_plan(&loaded_data_intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + let loaded_data_expected = solana_compute_budget_interface::ComputeBudgetInstruction::set_loaded_accounts_data_size_limit( + 8 * 1_024 * 1_024, + ); + assert_matches_official(&loaded_data_plan, &loaded_data_expected); + assert_eq!( + loaded_data_plan.operation_code, + crate::EX_SOLANA_CORE_COMPUTE_BUDGET_SET_LOADED_ACCOUNTS_DATA_SIZE_LIMIT_OPERATION + ); + } + + #[test] + fn compute_budget_resource_limits_reject_invalid_values() { + let payer = kb_program_ids::VOTE_PROGRAM_ID; + let invalid_heap_values = [ + super::MIN_HEAP_FRAME_BYTES - 1, + super::MIN_HEAP_FRAME_BYTES + 1, + super::MAX_HEAP_FRAME_BYTES + super::HEAP_FRAME_BYTES_GRANULARITY, + ]; + for bytes in invalid_heap_values { + let intent = crate::ExSolanaCoreExecutionIntent { + intent_id: format!("invalid-heap-{bytes}"), + fee_payer: pubkey(payer), + policy: policy(&[payer]), + operation: crate::ExSolanaCoreOperation::ComputeBudgetRequestHeapFrame { bytes }, + }; + let error = crate::executor_solana_core_builder_build_prepared_plan(&intent) + .expect_err("invalid heap frame must fail"); + assert_eq!(error.code(), "execution_compute_budget_heap_frame_invalid"); + } + let invalid_loaded_data_values = [0, super::MAX_LOADED_ACCOUNTS_DATA_SIZE_BYTES + 1]; + for bytes in invalid_loaded_data_values { + let intent = crate::ExSolanaCoreExecutionIntent { + intent_id: format!("invalid-loaded-data-{bytes}"), + fee_payer: pubkey(payer), + policy: policy(&[payer]), + operation: + crate::ExSolanaCoreOperation::ComputeBudgetSetLoadedAccountsDataSizeLimit { + bytes, + }, + }; + let error = crate::executor_solana_core_builder_build_prepared_plan(&intent) + .expect_err("invalid loaded-account data-size limit must fail"); + assert_eq!( + error.code(), + "execution_compute_budget_loaded_accounts_data_size_limit_invalid" + ); + } + } + + #[test] + fn malformed_seed_and_prefund_contracts_are_rejected() { + let payer = kb_program_ids::VOTE_PROGRAM_ID; + let owner = kb_program_ids::SYSTEM_PROGRAM_ID; + let mismatch_intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("seed-mismatch"), + fee_payer: pubkey(payer), + policy: policy(&[payer]), + operation: crate::ExSolanaCoreOperation::SystemAllocateWithSeed { + account: pubkey(kb_program_ids::STAKE_PROGRAM_ID), + base: pubkey(payer), + seed: std::string::String::from("seed"), + space: 1, + owner: pubkey(owner), + }, + }; + let mismatch_error = + crate::executor_solana_core_builder_build_prepared_plan(&mismatch_intent) + .expect_err("mismatched seeded address must fail"); + assert_eq!(mismatch_error.code(), "execution_seeded_address_mismatch"); + + let long_seed = "x".repeat(33); + let invalid_seed_intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("seed-too-long"), + fee_payer: pubkey(payer), + policy: policy(&[payer]), + operation: crate::ExSolanaCoreOperation::SystemAllocateWithSeed { + account: pubkey(kb_program_ids::STAKE_PROGRAM_ID), + base: pubkey(payer), + seed: long_seed, + space: 1, + owner: pubkey(owner), + }, + }; + let seed_error = + crate::executor_solana_core_builder_build_prepared_plan(&invalid_seed_intent) + .expect_err("oversized seed must fail"); + assert_eq!(seed_error.code(), "execution_seed_invalid"); + + let missing_payer_intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("prefund-missing-payer"), + fee_payer: pubkey(payer), + policy: policy(&[payer, kb_program_ids::STAKE_PROGRAM_ID]), + operation: crate::ExSolanaCoreOperation::SystemCreateAccountAllowPrefund { + new_account: pubkey(kb_program_ids::STAKE_PROGRAM_ID), + payer: std::option::Option::None, + lamports: 1, + space: 0, + owner: pubkey(owner), + }, + }; + let payer_error = + crate::executor_solana_core_builder_build_prepared_plan(&missing_payer_intent) + .expect_err("additional lamports without payer must fail"); + assert_eq!(payer_error.code(), "execution_prefund_payer_missing"); + } + + #[test] + fn malformed_and_dangerous_values_are_rejected() { + let payer = kb_program_ids::VOTE_PROGRAM_ID; + let invalid_pubkey = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("invalid-key"), + fee_payer: pubkey("invalid"), + policy: policy(&[payer]), + operation: crate::ExSolanaCoreOperation::ComputeBudgetSetUnitLimit { units: 1 }, + }; + let invalid_key_error = + crate::executor_solana_core_builder_build_prepared_plan(&invalid_pubkey) + .expect_err("invalid public key must fail"); + assert_eq!(invalid_key_error.code(), "execution_pubkey_invalid"); + let zero_transfer = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("zero-transfer"), + fee_payer: pubkey(payer), + policy: policy(&[payer]), + operation: crate::ExSolanaCoreOperation::SystemTransfer { + from: pubkey(payer), + to: pubkey(kb_program_ids::STAKE_PROGRAM_ID), + lamports: 0, + }, + }; + let zero_error = crate::executor_solana_core_builder_build_prepared_plan(&zero_transfer) + .expect_err("zero transfer must fail"); + assert_eq!(zero_error.code(), "execution_transfer_lamports_zero"); + let zero_allocate = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("zero-allocate"), + fee_payer: pubkey(payer), + policy: policy(&[payer]), + operation: crate::ExSolanaCoreOperation::SystemAllocate { + account: pubkey(payer), + space: 0, + }, + }; + let allocate_error = + crate::executor_solana_core_builder_build_prepared_plan(&zero_allocate) + .expect_err("zero allocation must fail"); + assert_eq!(allocate_error.code(), "execution_system_space_out_of_range"); + let excessive_compute = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("excessive-compute"), + fee_payer: pubkey(payer), + policy: policy(&[payer]), + operation: crate::ExSolanaCoreOperation::ComputeBudgetSetUnitLimit { units: 1_400_001 }, + }; + let compute_error = + crate::executor_solana_core_builder_build_prepared_plan(&excessive_compute) + .expect_err("excessive compute limit must fail"); + assert_eq!(compute_error.code(), "execution_compute_unit_limit_out_of_range"); + } +} diff --git a/kb-lib/src/executor/solana/core/constants.rs b/kb-lib/src/executor/solana/core/constants.rs new file mode 100644 index 0000000..de6a3ed --- /dev/null +++ b/kb-lib/src/executor/solana/core/constants.rs @@ -0,0 +1,7 @@ +// file: kb-lib/src/executor/solana/core/constants.rs +// version: 1 + +//! Local constants for the `kb_executor_solana_core` crate. Program identifiers live in `kb_program_ids`. + +/// Canonical tracing target for this crate. +pub(crate) const EX_SOLANA_CORE_TRACING_TARGET: &str = "kb-lib.executor.solana.core"; diff --git a/kb-lib/src/executor/solana/core/executor.rs b/kb-lib/src/executor/solana/core/executor.rs new file mode 100644 index 0000000..9b0a815 --- /dev/null +++ b/kb-lib/src/executor/solana/core/executor.rs @@ -0,0 +1,658 @@ +// file: kb-lib/src/executor/solana/core/executor.rs +// version: 1 + +//! Exact capability dispatch and typed plan construction for Solana core. + +/// Solana core executor implementation. +#[derive(Clone, Debug, Default)] +pub struct ExSolanaCoreExecutor; + +impl crate::ExSolanaCoreExecutor { + fn exact_capability( + &self, + program_id: &crate::MdProgramId, + operation_code: &str, + ) -> crate::ExApiExecutionCapability { + if program_id.0.as_str() == kb_program_ids::SYSTEM_PROGRAM_ID { + return match operation_code { + crate::EX_SOLANA_CORE_SYSTEM_TRANSFER_OPERATION + | crate::EX_SOLANA_CORE_SYSTEM_TRANSFER_MANY_OPERATION + | crate::EX_SOLANA_CORE_SYSTEM_TRANSFER_WITH_SEED_OPERATION + | crate::EX_SOLANA_CORE_SYSTEM_CREATE_ACCOUNT_OPERATION + | crate::EX_SOLANA_CORE_SYSTEM_CREATE_ACCOUNT_WITH_SEED_OPERATION + | crate::EX_SOLANA_CORE_SYSTEM_CREATE_ACCOUNT_ALLOW_PREFUND_OPERATION + | crate::EX_SOLANA_CORE_SYSTEM_ALLOCATE_OPERATION + | crate::EX_SOLANA_CORE_SYSTEM_ALLOCATE_WITH_SEED_OPERATION + | crate::EX_SOLANA_CORE_SYSTEM_ASSIGN_OPERATION + | crate::EX_SOLANA_CORE_SYSTEM_ASSIGN_WITH_SEED_OPERATION + | crate::EX_SOLANA_CORE_SYSTEM_NONCE_CREATE_OPERATION + | crate::EX_SOLANA_CORE_SYSTEM_NONCE_CREATE_WITH_SEED_OPERATION + | crate::EX_SOLANA_CORE_SYSTEM_NONCE_INITIALIZE_OPERATION + | crate::EX_SOLANA_CORE_SYSTEM_NONCE_ADVANCE_OPERATION + | crate::EX_SOLANA_CORE_SYSTEM_NONCE_AUTHORIZE_OPERATION + | crate::EX_SOLANA_CORE_SYSTEM_NONCE_WITHDRAW_OPERATION + | crate::EX_SOLANA_CORE_SYSTEM_NONCE_UPGRADE_OPERATION => { + crate::ExApiExecutionCapability::supported(operation_code) + }, + _ => crate::ExApiExecutionCapability::unsupported( + "execution_system_operation_unsupported", + format!("System operation {operation_code} is not implemented"), + ), + }; + } + if program_id.0.as_str() == kb_program_ids::COMPUTE_BUDGET_PROGRAM_ID { + return match operation_code { + crate::EX_SOLANA_CORE_COMPUTE_BUDGET_REQUEST_HEAP_FRAME_OPERATION + | crate::EX_SOLANA_CORE_COMPUTE_BUDGET_SET_UNIT_LIMIT_OPERATION + | crate::EX_SOLANA_CORE_COMPUTE_BUDGET_SET_UNIT_PRICE_OPERATION + | crate::EX_SOLANA_CORE_COMPUTE_BUDGET_SET_LOADED_ACCOUNTS_DATA_SIZE_LIMIT_OPERATION => { + crate::ExApiExecutionCapability::supported(operation_code) + }, + _ => crate::ExApiExecutionCapability::unsupported( + "execution_compute_budget_operation_unsupported", + format!("Compute Budget operation {operation_code} is not implemented"), + ), + }; + } + if program_id.0.as_str() == kb_program_ids::ADDRESS_LOOKUP_TABLE_PROGRAM_ID { + return match operation_code { + crate::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_CREATE_OPERATION + | crate::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_EXTEND_OPERATION + | crate::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_FREEZE_OPERATION + | crate::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_DEACTIVATE_OPERATION + | crate::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_CLOSE_OPERATION => { + crate::ExApiExecutionCapability::supported(operation_code) + }, + _ => crate::ExApiExecutionCapability::unsupported( + "execution_address_lookup_table_operation_unsupported", + format!("Address Lookup Table operation {operation_code} is not implemented"), + ), + }; + } + if program_id.0.as_str() == kb_program_ids::ED25519_PROGRAM_ID { + return match operation_code { + crate::EX_SOLANA_CORE_ED25519_VERIFY_INLINE_OPERATION + | crate::EX_SOLANA_CORE_ED25519_VERIFY_OFFSETS_OPERATION => { + crate::ExApiExecutionCapability::supported(operation_code) + }, + _ => crate::ExApiExecutionCapability::unsupported( + "execution_ed25519_operation_unsupported", + format!("Ed25519 operation {operation_code} is not implemented"), + ), + }; + } + if program_id.0.as_str() == kb_program_ids::SECP256K1_PROGRAM_ID { + return match operation_code { + crate::EX_SOLANA_CORE_SECP256K1_VERIFY_INLINE_OPERATION + | crate::EX_SOLANA_CORE_SECP256K1_VERIFY_OFFSETS_OPERATION => { + crate::ExApiExecutionCapability::supported(operation_code) + }, + _ => crate::ExApiExecutionCapability::unsupported( + "execution_secp256k1_operation_unsupported", + format!("secp256k1 operation {operation_code} is not implemented"), + ), + }; + } + if program_id.0.as_str() == kb_program_ids::SECP256R1_PROGRAM_ID { + return match operation_code { + crate::EX_SOLANA_CORE_SECP256R1_VERIFY_INLINE_OPERATION + | crate::EX_SOLANA_CORE_SECP256R1_VERIFY_OFFSETS_OPERATION => { + crate::ExApiExecutionCapability::supported(operation_code) + }, + _ => crate::ExApiExecutionCapability::unsupported( + "execution_secp256r1_operation_unsupported", + format!("secp256r1 operation {operation_code} is not implemented"), + ), + }; + } + if program_id.0.as_str() == kb_program_ids::STAKE_PROGRAM_ID { + return match operation_code { + crate::EX_SOLANA_CORE_STAKE_INITIALIZE_OPERATION + | crate::EX_SOLANA_CORE_STAKE_INITIALIZE_CHECKED_OPERATION + | crate::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_OPERATION + | crate::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_WITH_SEED_OPERATION + | crate::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_CHECKED_OPERATION + | crate::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_WITH_SEED_CHECKED_OPERATION + | crate::EX_SOLANA_CORE_STAKE_SPLIT_OPERATION + | crate::EX_SOLANA_CORE_STAKE_SPLIT_WITH_SEED_OPERATION + | crate::EX_SOLANA_CORE_STAKE_MERGE_OPERATION + | crate::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_AND_DELEGATE_OPERATION + | crate::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_WITH_SEED_AND_DELEGATE_OPERATION + | crate::EX_SOLANA_CORE_STAKE_AUTHORIZE_OPERATION + | crate::EX_SOLANA_CORE_STAKE_AUTHORIZE_CHECKED_OPERATION + | crate::EX_SOLANA_CORE_STAKE_AUTHORIZE_WITH_SEED_OPERATION + | crate::EX_SOLANA_CORE_STAKE_AUTHORIZE_CHECKED_WITH_SEED_OPERATION + | crate::EX_SOLANA_CORE_STAKE_DELEGATE_OPERATION + | crate::EX_SOLANA_CORE_STAKE_WITHDRAW_OPERATION + | crate::EX_SOLANA_CORE_STAKE_DEACTIVATE_OPERATION + | crate::EX_SOLANA_CORE_STAKE_SET_LOCKUP_OPERATION + | crate::EX_SOLANA_CORE_STAKE_SET_LOCKUP_CHECKED_OPERATION + | crate::EX_SOLANA_CORE_STAKE_GET_MINIMUM_DELEGATION_OPERATION + | crate::EX_SOLANA_CORE_STAKE_DEACTIVATE_DELINQUENT_OPERATION + | crate::EX_SOLANA_CORE_STAKE_MOVE_STAKE_OPERATION + | crate::EX_SOLANA_CORE_STAKE_MOVE_LAMPORTS_OPERATION => { + crate::ExApiExecutionCapability::supported(operation_code) + }, + _ => crate::ExApiExecutionCapability::unsupported( + "execution_stake_operation_unsupported", + format!("Stake operation {operation_code} is not implemented"), + ), + }; + } + if program_id.0.as_str() == kb_program_ids::VOTE_PROGRAM_ID { + return match operation_code { + crate::EX_SOLANA_CORE_VOTE_INITIALIZE_ACCOUNT_OPERATION + | crate::EX_SOLANA_CORE_VOTE_INITIALIZE_ACCOUNT_V2_OPERATION + | crate::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_OPERATION + | crate::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_WITH_SEED_OPERATION + | crate::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_V2_OPERATION + | crate::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_WITH_SEED_V2_OPERATION + | crate::EX_SOLANA_CORE_VOTE_AUTHORIZE_OPERATION + | crate::EX_SOLANA_CORE_VOTE_AUTHORIZE_CHECKED_OPERATION + | crate::EX_SOLANA_CORE_VOTE_AUTHORIZE_WITH_SEED_OPERATION + | crate::EX_SOLANA_CORE_VOTE_AUTHORIZE_CHECKED_WITH_SEED_OPERATION + | crate::EX_SOLANA_CORE_VOTE_SUBMIT_OPERATION + | crate::EX_SOLANA_CORE_VOTE_SWITCH_OPERATION + | crate::EX_SOLANA_CORE_VOTE_WITHDRAW_OPERATION + | crate::EX_SOLANA_CORE_VOTE_UPDATE_VALIDATOR_IDENTITY_OPERATION + | crate::EX_SOLANA_CORE_VOTE_UPDATE_COMMISSION_OPERATION + | crate::EX_SOLANA_CORE_VOTE_UPDATE_VOTE_STATE_OPERATION + | crate::EX_SOLANA_CORE_VOTE_UPDATE_VOTE_STATE_SWITCH_OPERATION + | crate::EX_SOLANA_CORE_VOTE_COMPACT_UPDATE_VOTE_STATE_OPERATION + | crate::EX_SOLANA_CORE_VOTE_COMPACT_UPDATE_VOTE_STATE_SWITCH_OPERATION + | crate::EX_SOLANA_CORE_VOTE_TOWER_SYNC_OPERATION + | crate::EX_SOLANA_CORE_VOTE_TOWER_SYNC_SWITCH_OPERATION + | crate::EX_SOLANA_CORE_VOTE_UPDATE_COMMISSION_COLLECTOR_OPERATION + | crate::EX_SOLANA_CORE_VOTE_UPDATE_COMMISSION_BPS_OPERATION + | crate::EX_SOLANA_CORE_VOTE_DEPOSIT_DELEGATOR_REWARDS_OPERATION => { + crate::ExApiExecutionCapability::supported(operation_code) + }, + _ => crate::ExApiExecutionCapability::unsupported( + "execution_vote_operation_unsupported", + format!("Vote operation {operation_code} is not implemented"), + ), + }; + } + if program_id.0.as_str() == kb_program_ids::BPF_LOADER_UPGRADEABLE_PROGRAM_ID { + return match operation_code { + crate::EX_SOLANA_CORE_LOADER_V3_CREATE_BUFFER_OPERATION + | crate::EX_SOLANA_CORE_LOADER_V3_WRITE_OPERATION + | crate::EX_SOLANA_CORE_LOADER_V3_DEPLOY_WITH_MAX_PROGRAM_LEN_OPERATION + | crate::EX_SOLANA_CORE_LOADER_V3_UPGRADE_OPERATION + | crate::EX_SOLANA_CORE_LOADER_V3_SET_BUFFER_AUTHORITY_OPERATION + | crate::EX_SOLANA_CORE_LOADER_V3_SET_BUFFER_AUTHORITY_CHECKED_OPERATION + | crate::EX_SOLANA_CORE_LOADER_V3_SET_UPGRADE_AUTHORITY_OPERATION + | crate::EX_SOLANA_CORE_LOADER_V3_SET_UPGRADE_AUTHORITY_CHECKED_OPERATION + | crate::EX_SOLANA_CORE_LOADER_V3_CLOSE_OPERATION + | crate::EX_SOLANA_CORE_LOADER_V3_CLOSE_ANY_OPERATION + | crate::EX_SOLANA_CORE_LOADER_V3_EXTEND_PROGRAM_OPERATION => { + crate::ExApiExecutionCapability::supported(operation_code) + }, + _ => crate::ExApiExecutionCapability::unsupported( + "execution_loader_v3_operation_unsupported", + format!("Loader v3 operation {operation_code} is not implemented"), + ), + }; + } + if program_id.0.as_str() == kb_program_ids::BPF_LOADER_DEPRECATED_PROGRAM_ID + || program_id.0.as_str() == kb_program_ids::BPF_LOADER_PROGRAM_ID + { + return crate::ExApiExecutionCapability::unsupported( + "execution_legacy_bpf_loader_management_disabled", + "BPF Loader v1/v2 management is historical and disabled on current clusters; the surfaces remain decode-only", + ); + } + if program_id.0.as_str() == kb_program_ids::LOADER_V4_PROGRAM_ID { + return match operation_code { + crate::EX_SOLANA_CORE_LOADER_V4_CREATE_BUFFER_OPERATION + | crate::EX_SOLANA_CORE_LOADER_V4_SET_PROGRAM_LENGTH_OPERATION + | crate::EX_SOLANA_CORE_LOADER_V4_WRITE_OPERATION + | crate::EX_SOLANA_CORE_LOADER_V4_COPY_OPERATION + | crate::EX_SOLANA_CORE_LOADER_V4_DEPLOY_OPERATION + | crate::EX_SOLANA_CORE_LOADER_V4_DEPLOY_FROM_SOURCE_OPERATION + | crate::EX_SOLANA_CORE_LOADER_V4_RETRACT_OPERATION + | crate::EX_SOLANA_CORE_LOADER_V4_TRANSFER_AUTHORITY_OPERATION + | crate::EX_SOLANA_CORE_LOADER_V4_FINALIZE_OPERATION => { + crate::ExApiExecutionCapability::supported(operation_code) + }, + _ => crate::ExApiExecutionCapability::unsupported( + "execution_loader_v4_operation_unsupported", + format!("Loader v4 operation {operation_code} is not implemented"), + ), + }; + } + if program_id.0.as_str() == kb_program_ids::NATIVE_LOADER_PROGRAM_ID { + return crate::ExApiExecutionCapability::unsupported( + "execution_native_loader_validator_only", + "Native Loader deployment is a validator software upgrade path and has no client instruction builder", + ); + } + if program_id.0.as_str() == kb_program_ids::CONFIG_PROGRAM_ID { + return match operation_code { + crate::EX_SOLANA_CORE_CONFIG_CREATE_ACCOUNT_OPERATION + | crate::EX_SOLANA_CORE_CONFIG_STORE_OPERATION => { + crate::ExApiExecutionCapability::supported(operation_code) + }, + _ => crate::ExApiExecutionCapability::unsupported( + "execution_config_operation_unsupported", + format!("Config operation {operation_code} is not implemented"), + ), + }; + } + if program_id.0.as_str() == kb_program_ids::FEATURE_PROGRAM_ID { + return match operation_code { + crate::EX_SOLANA_CORE_FEATURE_ACTIVATE_OPERATION + | crate::EX_SOLANA_CORE_FEATURE_REVOKE_PENDING_ACTIVATION_OPERATION => { + crate::ExApiExecutionCapability::supported(operation_code) + }, + _ => crate::ExApiExecutionCapability::unsupported( + "execution_feature_operation_unsupported", + format!("Feature operation {operation_code} is not implemented"), + ), + }; + } + if program_id.0.as_str() == kb_program_ids::SLASHING_PROGRAM_ID { + return match operation_code { + crate::EX_SOLANA_CORE_SLASHING_CLOSE_VIOLATION_REPORT_OPERATION + | crate::EX_SOLANA_CORE_SLASHING_SUBMIT_DUPLICATE_BLOCK_PROOF_OPERATION => { + crate::ExApiExecutionCapability::supported(operation_code) + }, + _ => crate::ExApiExecutionCapability::unsupported( + "execution_slashing_operation_unsupported", + format!("Slashing operation {operation_code} is not implemented"), + ), + }; + } + if program_id.0.as_str() == kb_program_ids::ZK_ELGAMAL_PROOF_PROGRAM_ID { + return match operation_code { + crate::EX_SOLANA_CORE_ZK_ELGAMAL_VERIFY_INLINE_OPERATION + | crate::EX_SOLANA_CORE_ZK_ELGAMAL_VERIFY_FROM_ACCOUNT_OPERATION + | crate::EX_SOLANA_CORE_ZK_ELGAMAL_CLOSE_CONTEXT_STATE_OPERATION => { + crate::ExApiExecutionCapability::supported(operation_code) + }, + _ => crate::ExApiExecutionCapability::unsupported( + "execution_zk_elgamal_operation_unsupported", + format!("ZK ElGamal operation {operation_code} is not implemented"), + ), + }; + } + if program_id.0.as_str() == kb_program_ids::ZK_TOKEN_PROOF_PROGRAM_ID { + return crate::ExApiExecutionCapability::unsupported( + "execution_historical_zk_token_proof_decode_only", + "the historical ZK Token Proof program is retained for decoding only and has no current callable runtime surface", + ); + } + if crate::ExApiInstructionExecutor::handles_program_id(self, program_id) { + return crate::ExApiExecutionCapability::unsupported( + "execution_native_program_deferred", + format!( + "program {} is classified but intentionally has no executable operation in 0.4.2", + program_id.0 + ), + ); + } + return crate::ExApiExecutionCapability::unsupported( + "execution_program_not_owned", + format!("program {} is not owned by kb_executor_solana_core", program_id.0), + ); + } +} + +impl crate::ExApiTypedInstructionExecutor for crate::ExSolanaCoreExecutor { + type Intent = crate::ExSolanaCoreExecutionIntent; + + fn capability( + &self, + program_id: &crate::MdProgramId, + operation_code: &str, + ) -> crate::ExApiExecutionCapability { + return self.exact_capability(program_id, operation_code); + } + + fn build_prepared_plan( + &self, + intent: &Self::Intent, + ) -> kb_core::Result { + let program_id = + crate::MdProgramId(std::string::String::from(intent.operation.program_id())); + return match self.exact_capability(&program_id, intent.operation.operation_code()) { + crate::ExApiExecutionCapability::Supported { operation_code: _ } => { + crate::executor_solana_core_builder_build_prepared_plan(intent) + }, + crate::ExApiExecutionCapability::Unsupported { reason_code, reason } => { + std::result::Result::Err(kb_core::Error::new(reason_code, reason)) + }, + }; + } +} + +impl crate::ExApiInstructionExecutor for crate::ExSolanaCoreExecutor { + fn executor_name(&self) -> &'static str { + return "kb_executor_solana_core"; + } + + fn executor_version(&self) -> &'static str { + return env!("CARGO_PKG_VERSION"); + } + + fn program_ids(&self) -> &'static [&'static str] { + return &[ + kb_program_ids::ADDRESS_LOOKUP_TABLE_PROGRAM_ID, + kb_program_ids::BPF_LOADER_DEPRECATED_PROGRAM_ID, + kb_program_ids::BPF_LOADER_PROGRAM_ID, + kb_program_ids::BPF_LOADER_UPGRADEABLE_PROGRAM_ID, + kb_program_ids::COMPUTE_BUDGET_PROGRAM_ID, + kb_program_ids::CONFIG_PROGRAM_ID, + kb_program_ids::ED25519_PROGRAM_ID, + kb_program_ids::FEATURE_PROGRAM_ID, + kb_program_ids::LOADER_V4_PROGRAM_ID, + kb_program_ids::NATIVE_LOADER_PROGRAM_ID, + kb_program_ids::SLASHING_PROGRAM_ID, + kb_program_ids::SECP256K1_PROGRAM_ID, + kb_program_ids::SECP256R1_PROGRAM_ID, + kb_program_ids::STAKE_PROGRAM_ID, + kb_program_ids::SYSTEM_PROGRAM_ID, + kb_program_ids::VOTE_PROGRAM_ID, + kb_program_ids::ZK_ELGAMAL_PROOF_PROGRAM_ID, + kb_program_ids::ZK_TOKEN_PROOF_PROGRAM_ID, + ]; + } + + fn supports_request( + &self, + request: &crate::ExApiExecutionRequest, + ) -> crate::ExApiExecutionSupport { + return match self.exact_capability(&request.program_id, &request.operation_code) { + crate::ExApiExecutionCapability::Supported { operation_code: _ } => { + crate::ExApiExecutionSupport::Yes + }, + crate::ExApiExecutionCapability::Unsupported { reason_code: _, reason: _ } => { + crate::ExApiExecutionSupport::No + }, + }; + } + + fn build_plan( + &self, + request: &crate::ExApiExecutionRequest, + ) -> kb_core::Result { + match self.exact_capability(&request.program_id, &request.operation_code) { + crate::ExApiExecutionCapability::Supported { operation_code: _ } => {}, + crate::ExApiExecutionCapability::Unsupported { reason_code, reason } => { + return std::result::Result::Err(kb_core::Error::new(reason_code, reason)); + }, + } + let intent = + match serde_json::from_str::(&request.payload_json) + { + std::result::Result::Ok(intent) => intent, + std::result::Result::Err(error) => { + return std::result::Result::Err(kb_core::Error::new( + "execution_solana_core_intent_deserialize_failed", + error.to_string(), + )); + }, + }; + if intent.operation.operation_code() != request.operation_code.as_str() { + return std::result::Result::Err(kb_core::Error::new( + "execution_operation_code_mismatch", + format!( + "request operation {} does not match typed intent operation {}", + request.operation_code, + intent.operation.operation_code() + ), + )); + } + if intent.operation.program_id() != request.program_id.0.as_str() { + return std::result::Result::Err(kb_core::Error::new( + "execution_program_id_mismatch", + format!( + "request program {} does not match typed intent program {}", + request.program_id.0, + intent.operation.program_id() + ), + )); + } + let prepared = + match crate::ExApiTypedInstructionExecutor::build_prepared_plan(self, &intent) { + std::result::Result::Ok(plan) => plan, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let payload_json = match serde_json::to_value(&prepared) { + std::result::Result::Ok(value) => { + match crate::executor_api_serialize_payload_json(&value) { + std::result::Result::Ok(serialized) => serialized, + std::result::Result::Err(error) => { + return std::result::Result::Err(error); + }, + } + }, + std::result::Result::Err(error) => { + return std::result::Result::Err(kb_core::Error::new( + "execution_solana_core_plan_serialize_failed", + error.to_string(), + )); + }, + }; + return std::result::Result::Ok(crate::ExApiExecutionPlan { + executor_name: std::string::String::from("kb_executor_solana_core"), + instruction_count: prepared.instructions.len(), + payload_json, + }); + } +} + +#[cfg(test)] +mod tests { + #[test] + fn executor_programs_match_native_registry_without_spl_memo() { + let executor = crate::ExSolanaCoreExecutor; + let executor_ids = crate::ExApiInstructionExecutor::program_ids(&executor); + let native_ids = kb_program_ids::native_program_ids() + .iter() + .map(|entry| return entry.program_id()) + .collect::>(); + let actual_ids = executor_ids.iter().copied().collect::>(); + assert_eq!(actual_ids, native_ids); + assert!(!actual_ids.contains(kb_program_ids::SPL_MEMO_V1_PROGRAM_ID)); + assert!(!actual_ids.contains(kb_program_ids::SPL_MEMO_V3_PROGRAM_ID)); + assert!(!actual_ids.contains(kb_program_ids::SPL_MEMO_V4_PROGRAM_ID)); + assert!(!actual_ids.contains(kb_program_ids::STAKE_CONFIG_ACCOUNT_ID)); + } + + #[test] + fn capabilities_are_exact_and_never_maybe() { + let executor = crate::ExSolanaCoreExecutor; + let transfer = crate::ExApiTypedInstructionExecutor::capability( + &executor, + &crate::MdProgramId(std::string::String::from(kb_program_ids::SYSTEM_PROGRAM_ID)), + crate::EX_SOLANA_CORE_SYSTEM_TRANSFER_OPERATION, + ); + assert!(transfer.is_supported()); + let seeded_transfer = crate::ExApiTypedInstructionExecutor::capability( + &executor, + &crate::MdProgramId(std::string::String::from(kb_program_ids::SYSTEM_PROGRAM_ID)), + crate::EX_SOLANA_CORE_SYSTEM_TRANSFER_WITH_SEED_OPERATION, + ); + assert!(seeded_transfer.is_supported()); + let prefund = crate::ExApiTypedInstructionExecutor::capability( + &executor, + &crate::MdProgramId(std::string::String::from(kb_program_ids::SYSTEM_PROGRAM_ID)), + crate::EX_SOLANA_CORE_SYSTEM_CREATE_ACCOUNT_ALLOW_PREFUND_OPERATION, + ); + assert!(prefund.is_supported()); + let nonce = crate::ExApiTypedInstructionExecutor::capability( + &executor, + &crate::MdProgramId(std::string::String::from(kb_program_ids::SYSTEM_PROGRAM_ID)), + crate::EX_SOLANA_CORE_SYSTEM_NONCE_ADVANCE_OPERATION, + ); + assert!(nonce.is_supported()); + let transfer_many = crate::ExApiTypedInstructionExecutor::capability( + &executor, + &crate::MdProgramId(std::string::String::from(kb_program_ids::SYSTEM_PROGRAM_ID)), + crate::EX_SOLANA_CORE_SYSTEM_TRANSFER_MANY_OPERATION, + ); + assert!(transfer_many.is_supported()); + let heap_frame = crate::ExApiTypedInstructionExecutor::capability( + &executor, + &crate::MdProgramId(std::string::String::from( + kb_program_ids::COMPUTE_BUDGET_PROGRAM_ID, + )), + crate::EX_SOLANA_CORE_COMPUTE_BUDGET_REQUEST_HEAP_FRAME_OPERATION, + ); + assert!(heap_frame.is_supported()); + let loaded_accounts_data_size = crate::ExApiTypedInstructionExecutor::capability( + &executor, + &crate::MdProgramId(std::string::String::from( + kb_program_ids::COMPUTE_BUDGET_PROGRAM_ID, + )), + crate::EX_SOLANA_CORE_COMPUTE_BUDGET_SET_LOADED_ACCOUNTS_DATA_SIZE_LIMIT_OPERATION, + ); + assert!(loaded_accounts_data_size.is_supported()); + let lookup_table_create = crate::ExApiTypedInstructionExecutor::capability( + &executor, + &crate::MdProgramId(std::string::String::from( + kb_program_ids::ADDRESS_LOOKUP_TABLE_PROGRAM_ID, + )), + crate::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_CREATE_OPERATION, + ); + assert!(lookup_table_create.is_supported()); + let ed25519 = crate::ExApiTypedInstructionExecutor::capability( + &executor, + &crate::MdProgramId(std::string::String::from(kb_program_ids::ED25519_PROGRAM_ID)), + crate::EX_SOLANA_CORE_ED25519_VERIFY_INLINE_OPERATION, + ); + assert!(ed25519.is_supported()); + let secp256k1 = crate::ExApiTypedInstructionExecutor::capability( + &executor, + &crate::MdProgramId(std::string::String::from(kb_program_ids::SECP256K1_PROGRAM_ID)), + crate::EX_SOLANA_CORE_SECP256K1_VERIFY_OFFSETS_OPERATION, + ); + assert!(secp256k1.is_supported()); + let secp256r1 = crate::ExApiTypedInstructionExecutor::capability( + &executor, + &crate::MdProgramId(std::string::String::from(kb_program_ids::SECP256R1_PROGRAM_ID)), + crate::EX_SOLANA_CORE_SECP256R1_VERIFY_INLINE_OPERATION, + ); + assert!(secp256r1.is_supported()); + let config = crate::ExApiTypedInstructionExecutor::capability( + &executor, + &crate::MdProgramId(std::string::String::from(kb_program_ids::CONFIG_PROGRAM_ID)), + crate::EX_SOLANA_CORE_CONFIG_STORE_OPERATION, + ); + assert!(config.is_supported()); + let feature = crate::ExApiTypedInstructionExecutor::capability( + &executor, + &crate::MdProgramId(std::string::String::from(kb_program_ids::FEATURE_PROGRAM_ID)), + crate::EX_SOLANA_CORE_FEATURE_ACTIVATE_OPERATION, + ); + assert!(feature.is_supported()); + let slashing = crate::ExApiTypedInstructionExecutor::capability( + &executor, + &crate::MdProgramId(std::string::String::from(kb_program_ids::SLASHING_PROGRAM_ID)), + crate::EX_SOLANA_CORE_SLASHING_SUBMIT_DUPLICATE_BLOCK_PROOF_OPERATION, + ); + assert!(slashing.is_supported()); + let zk_elgamal = crate::ExApiTypedInstructionExecutor::capability( + &executor, + &crate::MdProgramId(std::string::String::from( + kb_program_ids::ZK_ELGAMAL_PROOF_PROGRAM_ID, + )), + crate::EX_SOLANA_CORE_ZK_ELGAMAL_VERIFY_INLINE_OPERATION, + ); + assert!(zk_elgamal.is_supported()); + let historical_zk = crate::ExApiTypedInstructionExecutor::capability( + &executor, + &crate::MdProgramId(std::string::String::from( + kb_program_ids::ZK_TOKEN_PROOF_PROGRAM_ID, + )), + "solana_core.zk_token.verify_zero_balance", + ); + assert!(matches!( + historical_zk, + crate::ExApiExecutionCapability::Unsupported { reason_code, reason: _ } + if reason_code == "execution_historical_zk_token_proof_decode_only" + )); + let stake = crate::ExApiTypedInstructionExecutor::capability( + &executor, + &crate::MdProgramId(std::string::String::from(kb_program_ids::STAKE_PROGRAM_ID)), + crate::EX_SOLANA_CORE_STAKE_DELEGATE_OPERATION, + ); + assert!(stake.is_supported()); + let vote = crate::ExApiTypedInstructionExecutor::capability( + &executor, + &crate::MdProgramId(std::string::String::from(kb_program_ids::VOTE_PROGRAM_ID)), + crate::EX_SOLANA_CORE_VOTE_TOWER_SYNC_SWITCH_OPERATION, + ); + assert!(vote.is_supported()); + let legacy_supported = crate::ExApiInstructionExecutor::supports_request( + &executor, + &crate::ExApiExecutionRequest { + program_id: crate::MdProgramId(std::string::String::from( + kb_program_ids::SYSTEM_PROGRAM_ID, + )), + operation_code: std::string::String::from( + crate::EX_SOLANA_CORE_SYSTEM_TRANSFER_OPERATION, + ), + payload_json: std::string::String::new(), + }, + ); + assert_eq!(legacy_supported, crate::ExApiExecutionSupport::Yes); + let legacy_nonce = crate::ExApiInstructionExecutor::supports_request( + &executor, + &crate::ExApiExecutionRequest { + program_id: crate::MdProgramId(std::string::String::from( + kb_program_ids::SYSTEM_PROGRAM_ID, + )), + operation_code: std::string::String::from( + crate::EX_SOLANA_CORE_SYSTEM_NONCE_ADVANCE_OPERATION, + ), + payload_json: std::string::String::new(), + }, + ); + assert_eq!(legacy_nonce, crate::ExApiExecutionSupport::Yes); + } + + #[test] + fn legacy_request_builds_the_same_typed_plan_payload() { + let payer = crate::MdPubkey(std::string::String::from(kb_program_ids::VOTE_PROGRAM_ID)); + let intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("legacy-transfer"), + fee_payer: payer.clone(), + policy: crate::ExApiExecutionPolicy { + cost_limit: crate::ExApiExecutionCostLimit { + max_spend_lamports: std::option::Option::Some(10), + max_fee_lamports: std::option::Option::Some(10_000), + max_compute_unit_price_micro_lamports: std::option::Option::Some(1), + }, + authorized_signers: vec![payer.clone()], + ..crate::ExApiExecutionPolicy::default() + }, + operation: crate::ExSolanaCoreOperation::SystemTransfer { + from: payer, + to: crate::MdPubkey(std::string::String::from(kb_program_ids::STAKE_PROGRAM_ID)), + lamports: 10, + }, + }; + let payload_json = serde_json::to_string(&intent) + .unwrap_or_else(|error| panic!("unexpected serialization error: {error}")); + let plan = crate::ExApiInstructionExecutor::build_plan( + &crate::ExSolanaCoreExecutor, + &crate::ExApiExecutionRequest { + program_id: crate::MdProgramId(std::string::String::from( + kb_program_ids::SYSTEM_PROGRAM_ID, + )), + operation_code: std::string::String::from( + crate::EX_SOLANA_CORE_SYSTEM_TRANSFER_OPERATION, + ), + payload_json, + }, + ) + .unwrap_or_else(|error| panic!("unexpected build error: {error}")); + assert_eq!(plan.instruction_count, 1); + let typed: crate::ExApiPreparedExecutionPlan = serde_json::from_str(&plan.payload_json) + .unwrap_or_else(|error| panic!("unexpected plan payload: {error}")); + assert_eq!(typed.intent_id, "legacy-transfer"); + assert_eq!(typed.instructions.len(), 1); + } +} diff --git a/kb-lib/src/executor/solana/core/intent.rs b/kb-lib/src/executor/solana/core/intent.rs new file mode 100644 index 0000000..3eb5f4c --- /dev/null +++ b/kb-lib/src/executor/solana/core/intent.rs @@ -0,0 +1,2604 @@ +// file: kb-lib/src/executor/solana/core/intent.rs +// version: 1 + +//! Typed Solana core execution intents. + +use ts_rs::TS; // rust-rules: derive-import + +/// Stable operation code for a System Program lamport transfer. +pub const EX_SOLANA_CORE_SYSTEM_TRANSFER_OPERATION: &str = "solana_core.system.transfer"; +/// Stable operation code for a bounded batch of System Program transfers. +pub const EX_SOLANA_CORE_SYSTEM_TRANSFER_MANY_OPERATION: &str = "solana_core.system.transfer_many"; +/// Stable operation code for controlled System Program account creation. +pub const EX_SOLANA_CORE_SYSTEM_CREATE_ACCOUNT_OPERATION: &str = + "solana_core.system.create_account"; +/// Stable operation code for System Program account creation with a seed. +pub const EX_SOLANA_CORE_SYSTEM_CREATE_ACCOUNT_WITH_SEED_OPERATION: &str = + "solana_core.system.create_account_with_seed"; +/// Stable operation code for System Program account allocation. +pub const EX_SOLANA_CORE_SYSTEM_ALLOCATE_OPERATION: &str = "solana_core.system.allocate"; +/// Stable operation code for System Program seeded account allocation. +pub const EX_SOLANA_CORE_SYSTEM_ALLOCATE_WITH_SEED_OPERATION: &str = + "solana_core.system.allocate_with_seed"; +/// Stable operation code for System Program account owner assignment. +pub const EX_SOLANA_CORE_SYSTEM_ASSIGN_OPERATION: &str = "solana_core.system.assign"; +/// Stable operation code for System Program seeded account owner assignment. +pub const EX_SOLANA_CORE_SYSTEM_ASSIGN_WITH_SEED_OPERATION: &str = + "solana_core.system.assign_with_seed"; +/// Stable operation code for a System Program transfer from a seeded account. +pub const EX_SOLANA_CORE_SYSTEM_TRANSFER_WITH_SEED_OPERATION: &str = + "solana_core.system.transfer_with_seed"; +/// Stable operation code for System Program account creation that accepts prefunding. +pub const EX_SOLANA_CORE_SYSTEM_CREATE_ACCOUNT_ALLOW_PREFUND_OPERATION: &str = + "solana_core.system.create_account_allow_prefund"; +/// Stable operation code for durable nonce account creation. +pub const EX_SOLANA_CORE_SYSTEM_NONCE_CREATE_OPERATION: &str = "solana_core.system.nonce.create"; +/// Stable operation code for seeded durable nonce account creation. +pub const EX_SOLANA_CORE_SYSTEM_NONCE_CREATE_WITH_SEED_OPERATION: &str = + "solana_core.system.nonce.create_with_seed"; +/// Stable operation code for durable nonce initialization. +pub const EX_SOLANA_CORE_SYSTEM_NONCE_INITIALIZE_OPERATION: &str = + "solana_core.system.nonce.initialize"; +/// Stable operation code for durable nonce advancement. +pub const EX_SOLANA_CORE_SYSTEM_NONCE_ADVANCE_OPERATION: &str = "solana_core.system.nonce.advance"; +/// Stable operation code for durable nonce authority changes. +pub const EX_SOLANA_CORE_SYSTEM_NONCE_AUTHORIZE_OPERATION: &str = + "solana_core.system.nonce.authorize"; +/// Stable operation code for durable nonce withdrawal. +pub const EX_SOLANA_CORE_SYSTEM_NONCE_WITHDRAW_OPERATION: &str = + "solana_core.system.nonce.withdraw"; +/// Stable operation code for durable nonce state upgrade. +pub const EX_SOLANA_CORE_SYSTEM_NONCE_UPGRADE_OPERATION: &str = "solana_core.system.nonce.upgrade"; +/// Stable operation code for a Compute Budget heap-frame request. +pub const EX_SOLANA_CORE_COMPUTE_BUDGET_REQUEST_HEAP_FRAME_OPERATION: &str = + "solana_core.compute_budget.request_heap_frame"; +/// Stable operation code for a Compute Budget unit limit. +pub const EX_SOLANA_CORE_COMPUTE_BUDGET_SET_UNIT_LIMIT_OPERATION: &str = + "solana_core.compute_budget.set_compute_unit_limit"; +/// Stable operation code for a Compute Budget unit price. +pub const EX_SOLANA_CORE_COMPUTE_BUDGET_SET_UNIT_PRICE_OPERATION: &str = + "solana_core.compute_budget.set_compute_unit_price"; +/// Stable operation code for a Compute Budget loaded-account data-size limit. +pub const EX_SOLANA_CORE_COMPUTE_BUDGET_SET_LOADED_ACCOUNTS_DATA_SIZE_LIMIT_OPERATION: &str = + "solana_core.compute_budget.set_loaded_accounts_data_size_limit"; +/// Stable operation code for Address Lookup Table creation. +pub const EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_CREATE_OPERATION: &str = + "solana_core.address_lookup_table.create"; +/// Stable operation code for Address Lookup Table extension. +pub const EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_EXTEND_OPERATION: &str = + "solana_core.address_lookup_table.extend"; +/// Stable operation code for permanently freezing an Address Lookup Table. +pub const EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_FREEZE_OPERATION: &str = + "solana_core.address_lookup_table.freeze"; +/// Stable operation code for Address Lookup Table deactivation. +pub const EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_DEACTIVATE_OPERATION: &str = + "solana_core.address_lookup_table.deactivate"; +/// Stable operation code for closing an Address Lookup Table. +pub const EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_CLOSE_OPERATION: &str = + "solana_core.address_lookup_table.close"; +/// Stable operation code for one inline Ed25519 verification. +pub const EX_SOLANA_CORE_ED25519_VERIFY_INLINE_OPERATION: &str = + "solana_core.ed25519.verify_inline"; +/// Stable operation code for an Ed25519 verification table with explicit offsets. +pub const EX_SOLANA_CORE_ED25519_VERIFY_OFFSETS_OPERATION: &str = + "solana_core.ed25519.verify_offsets"; +/// Stable operation code for one inline secp256k1 verification. +pub const EX_SOLANA_CORE_SECP256K1_VERIFY_INLINE_OPERATION: &str = + "solana_core.secp256k1.verify_inline"; +/// Stable operation code for a secp256k1 verification table with explicit offsets. +pub const EX_SOLANA_CORE_SECP256K1_VERIFY_OFFSETS_OPERATION: &str = + "solana_core.secp256k1.verify_offsets"; +/// Stable operation code for one inline secp256r1 verification. +pub const EX_SOLANA_CORE_SECP256R1_VERIFY_INLINE_OPERATION: &str = + "solana_core.secp256r1.verify_inline"; +/// Stable operation code for a secp256r1 verification table with explicit offsets. +pub const EX_SOLANA_CORE_SECP256R1_VERIFY_OFFSETS_OPERATION: &str = + "solana_core.secp256r1.verify_offsets"; +/// Stable operation code for initializing a Stake account. +pub const EX_SOLANA_CORE_STAKE_INITIALIZE_OPERATION: &str = "solana_core.stake.initialize"; +/// Stable operation code for checked Stake account initialization. +pub const EX_SOLANA_CORE_STAKE_INITIALIZE_CHECKED_OPERATION: &str = + "solana_core.stake.initialize_checked"; +/// Stable operation code for creating and initializing a Stake account. +pub const EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_OPERATION: &str = "solana_core.stake.create_account"; +/// Stable operation code for seeded Stake account creation. +pub const EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_WITH_SEED_OPERATION: &str = + "solana_core.stake.create_account_with_seed"; +/// Stable operation code for checked Stake account creation. +pub const EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_CHECKED_OPERATION: &str = + "solana_core.stake.create_account_checked"; +/// Stable operation code for checked seeded Stake account creation. +pub const EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_WITH_SEED_CHECKED_OPERATION: &str = + "solana_core.stake.create_account_with_seed_checked"; +/// Stable operation code for splitting a Stake account. +pub const EX_SOLANA_CORE_STAKE_SPLIT_OPERATION: &str = "solana_core.stake.split"; +/// Stable operation code for splitting into a seeded Stake account. +pub const EX_SOLANA_CORE_STAKE_SPLIT_WITH_SEED_OPERATION: &str = + "solana_core.stake.split_with_seed"; +/// Stable operation code for merging Stake accounts. +pub const EX_SOLANA_CORE_STAKE_MERGE_OPERATION: &str = "solana_core.stake.merge"; +/// Stable operation code for creating and delegating a Stake account. +pub const EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_AND_DELEGATE_OPERATION: &str = + "solana_core.stake.create_account_and_delegate"; +/// Stable operation code for seeded Stake account creation and delegation. +pub const EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_WITH_SEED_AND_DELEGATE_OPERATION: &str = + "solana_core.stake.create_account_with_seed_and_delegate"; +/// Stable operation code for changing a Stake authority. +pub const EX_SOLANA_CORE_STAKE_AUTHORIZE_OPERATION: &str = "solana_core.stake.authorize"; +/// Stable operation code for a checked Stake authority change. +pub const EX_SOLANA_CORE_STAKE_AUTHORIZE_CHECKED_OPERATION: &str = + "solana_core.stake.authorize_checked"; +/// Stable operation code for changing a seeded Stake authority. +pub const EX_SOLANA_CORE_STAKE_AUTHORIZE_WITH_SEED_OPERATION: &str = + "solana_core.stake.authorize_with_seed"; +/// Stable operation code for a checked seeded Stake authority change. +pub const EX_SOLANA_CORE_STAKE_AUTHORIZE_CHECKED_WITH_SEED_OPERATION: &str = + "solana_core.stake.authorize_checked_with_seed"; +/// Stable operation code for delegating Stake. +pub const EX_SOLANA_CORE_STAKE_DELEGATE_OPERATION: &str = "solana_core.stake.delegate"; +/// Stable operation code for withdrawing lamports from a Stake account. +pub const EX_SOLANA_CORE_STAKE_WITHDRAW_OPERATION: &str = "solana_core.stake.withdraw"; +/// Stable operation code for deactivating Stake. +pub const EX_SOLANA_CORE_STAKE_DEACTIVATE_OPERATION: &str = "solana_core.stake.deactivate"; +/// Stable operation code for changing a Stake lockup. +pub const EX_SOLANA_CORE_STAKE_SET_LOCKUP_OPERATION: &str = "solana_core.stake.set_lockup"; +/// Stable operation code for a checked Stake lockup change. +pub const EX_SOLANA_CORE_STAKE_SET_LOCKUP_CHECKED_OPERATION: &str = + "solana_core.stake.set_lockup_checked"; +/// Stable operation code for querying the minimum delegation. +pub const EX_SOLANA_CORE_STAKE_GET_MINIMUM_DELEGATION_OPERATION: &str = + "solana_core.stake.get_minimum_delegation"; +/// Stable operation code for deactivating delinquent Stake. +pub const EX_SOLANA_CORE_STAKE_DEACTIVATE_DELINQUENT_OPERATION: &str = + "solana_core.stake.deactivate_delinquent"; +/// Stable operation code for moving delegated Stake. +pub const EX_SOLANA_CORE_STAKE_MOVE_STAKE_OPERATION: &str = "solana_core.stake.move_stake"; +/// Stable operation code for moving lamports between Stake accounts. +pub const EX_SOLANA_CORE_STAKE_MOVE_LAMPORTS_OPERATION: &str = "solana_core.stake.move_lamports"; +/// Stable operation code for initializing a legacy Vote account. +pub const EX_SOLANA_CORE_VOTE_INITIALIZE_ACCOUNT_OPERATION: &str = + "solana_core.vote.initialize_account"; +/// Stable operation code for initializing a Vote v2 account. +pub const EX_SOLANA_CORE_VOTE_INITIALIZE_ACCOUNT_V2_OPERATION: &str = + "solana_core.vote.initialize_account_v2"; +/// Stable operation code for creating and initializing a legacy Vote account. +pub const EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_OPERATION: &str = "solana_core.vote.create_account"; +/// Stable operation code for creating a seeded legacy Vote account. +pub const EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_WITH_SEED_OPERATION: &str = + "solana_core.vote.create_account_with_seed"; +/// Stable operation code for creating and initializing a Vote v2 account. +pub const EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_V2_OPERATION: &str = + "solana_core.vote.create_account_v2"; +/// Stable operation code for creating a seeded Vote v2 account. +pub const EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_WITH_SEED_V2_OPERATION: &str = + "solana_core.vote.create_account_with_seed_v2"; +/// Stable operation code for changing a Vote authority. +pub const EX_SOLANA_CORE_VOTE_AUTHORIZE_OPERATION: &str = "solana_core.vote.authorize"; +/// Stable operation code for a checked Vote authority change. +pub const EX_SOLANA_CORE_VOTE_AUTHORIZE_CHECKED_OPERATION: &str = + "solana_core.vote.authorize_checked"; +/// Stable operation code for changing a seed-derived Vote authority. +pub const EX_SOLANA_CORE_VOTE_AUTHORIZE_WITH_SEED_OPERATION: &str = + "solana_core.vote.authorize_with_seed"; +/// Stable operation code for a checked seed-derived Vote authority change. +pub const EX_SOLANA_CORE_VOTE_AUTHORIZE_CHECKED_WITH_SEED_OPERATION: &str = + "solana_core.vote.authorize_checked_with_seed"; +/// Stable operation code for submitting a Vote instruction. +pub const EX_SOLANA_CORE_VOTE_SUBMIT_OPERATION: &str = "solana_core.vote.vote"; +/// Stable operation code for submitting a switch-proof Vote instruction. +pub const EX_SOLANA_CORE_VOTE_SWITCH_OPERATION: &str = "solana_core.vote.vote_switch"; +/// Stable operation code for withdrawing lamports from a Vote account. +pub const EX_SOLANA_CORE_VOTE_WITHDRAW_OPERATION: &str = "solana_core.vote.withdraw"; +/// Stable operation code for changing a validator identity. +pub const EX_SOLANA_CORE_VOTE_UPDATE_VALIDATOR_IDENTITY_OPERATION: &str = + "solana_core.vote.update_validator_identity"; +/// Stable operation code for changing a legacy Vote commission percentage. +pub const EX_SOLANA_CORE_VOTE_UPDATE_COMMISSION_OPERATION: &str = + "solana_core.vote.update_commission"; +/// Stable operation code for replacing Vote state. +pub const EX_SOLANA_CORE_VOTE_UPDATE_VOTE_STATE_OPERATION: &str = + "solana_core.vote.update_vote_state"; +/// Stable operation code for replacing Vote state with a switch proof. +pub const EX_SOLANA_CORE_VOTE_UPDATE_VOTE_STATE_SWITCH_OPERATION: &str = + "solana_core.vote.update_vote_state_switch"; +/// Stable operation code for compact Vote-state replacement. +pub const EX_SOLANA_CORE_VOTE_COMPACT_UPDATE_VOTE_STATE_OPERATION: &str = + "solana_core.vote.compact_update_vote_state"; +/// Stable operation code for compact Vote-state replacement with a switch proof. +pub const EX_SOLANA_CORE_VOTE_COMPACT_UPDATE_VOTE_STATE_SWITCH_OPERATION: &str = + "solana_core.vote.compact_update_vote_state_switch"; +/// Stable operation code for synchronizing a Vote tower. +pub const EX_SOLANA_CORE_VOTE_TOWER_SYNC_OPERATION: &str = "solana_core.vote.tower_sync"; +/// Stable operation code for synchronizing a Vote tower with a switch proof. +pub const EX_SOLANA_CORE_VOTE_TOWER_SYNC_SWITCH_OPERATION: &str = + "solana_core.vote.tower_sync_switch"; +/// Stable operation code for changing a Vote v2 commission collector. +pub const EX_SOLANA_CORE_VOTE_UPDATE_COMMISSION_COLLECTOR_OPERATION: &str = + "solana_core.vote.update_commission_collector"; +/// Stable operation code for changing a Vote v2 commission in basis points. +pub const EX_SOLANA_CORE_VOTE_UPDATE_COMMISSION_BPS_OPERATION: &str = + "solana_core.vote.update_commission_bps"; +/// Stable operation code for depositing delegator rewards into a Vote account. +pub const EX_SOLANA_CORE_VOTE_DEPOSIT_DELEGATOR_REWARDS_OPERATION: &str = + "solana_core.vote.deposit_delegator_rewards"; +/// Stable operation code for atomically creating and initializing a Loader v3 buffer. +pub const EX_SOLANA_CORE_LOADER_V3_CREATE_BUFFER_OPERATION: &str = + "solana_core.loader_v3.create_buffer"; +/// Stable operation code for writing one bounded chunk into a Loader v3 buffer. +pub const EX_SOLANA_CORE_LOADER_V3_WRITE_OPERATION: &str = "solana_core.loader_v3.write"; +/// Stable operation code for atomically creating and deploying a Loader v3 program. +pub const EX_SOLANA_CORE_LOADER_V3_DEPLOY_WITH_MAX_PROGRAM_LEN_OPERATION: &str = + "solana_core.loader_v3.deploy_with_max_program_len"; +/// Stable operation code for upgrading a Loader v3 program from a buffer. +pub const EX_SOLANA_CORE_LOADER_V3_UPGRADE_OPERATION: &str = "solana_core.loader_v3.upgrade"; +/// Stable operation code for changing a Loader v3 buffer authority. +pub const EX_SOLANA_CORE_LOADER_V3_SET_BUFFER_AUTHORITY_OPERATION: &str = + "solana_core.loader_v3.set_buffer_authority"; +/// Stable operation code for a checked Loader v3 buffer authority change. +pub const EX_SOLANA_CORE_LOADER_V3_SET_BUFFER_AUTHORITY_CHECKED_OPERATION: &str = + "solana_core.loader_v3.set_buffer_authority_checked"; +/// Stable operation code for changing or revoking a Loader v3 program authority. +pub const EX_SOLANA_CORE_LOADER_V3_SET_UPGRADE_AUTHORITY_OPERATION: &str = + "solana_core.loader_v3.set_upgrade_authority"; +/// Stable operation code for a checked Loader v3 program authority change. +pub const EX_SOLANA_CORE_LOADER_V3_SET_UPGRADE_AUTHORITY_CHECKED_OPERATION: &str = + "solana_core.loader_v3.set_upgrade_authority_checked"; +/// Stable operation code for closing an initialized Loader v3 buffer. +pub const EX_SOLANA_CORE_LOADER_V3_CLOSE_OPERATION: &str = "solana_core.loader_v3.close"; +/// Stable operation code for closing any supported Loader v3 account shape. +pub const EX_SOLANA_CORE_LOADER_V3_CLOSE_ANY_OPERATION: &str = "solana_core.loader_v3.close_any"; +/// Stable operation code for extending a Loader v3 ProgramData allocation. +pub const EX_SOLANA_CORE_LOADER_V3_EXTEND_PROGRAM_OPERATION: &str = + "solana_core.loader_v3.extend_program"; +/// Stable operation code for atomically creating and initializing a Loader v4 buffer. +pub const EX_SOLANA_CORE_LOADER_V4_CREATE_BUFFER_OPERATION: &str = + "solana_core.loader_v4.create_buffer"; +/// Stable operation code for changing a Loader v4 program allocation. +pub const EX_SOLANA_CORE_LOADER_V4_SET_PROGRAM_LENGTH_OPERATION: &str = + "solana_core.loader_v4.set_program_length"; +/// Stable operation code for writing one bounded chunk into a Loader v4 program account. +pub const EX_SOLANA_CORE_LOADER_V4_WRITE_OPERATION: &str = "solana_core.loader_v4.write"; +/// Stable operation code for copying bytes between Loader v4 program accounts. +pub const EX_SOLANA_CORE_LOADER_V4_COPY_OPERATION: &str = "solana_core.loader_v4.copy"; +/// Stable operation code for deploying a Loader v4 program in place. +pub const EX_SOLANA_CORE_LOADER_V4_DEPLOY_OPERATION: &str = "solana_core.loader_v4.deploy"; +/// Stable operation code for deploying a Loader v4 program from a source buffer. +pub const EX_SOLANA_CORE_LOADER_V4_DEPLOY_FROM_SOURCE_OPERATION: &str = + "solana_core.loader_v4.deploy_from_source"; +/// Stable operation code for retracting a Loader v4 program into maintenance. +pub const EX_SOLANA_CORE_LOADER_V4_RETRACT_OPERATION: &str = "solana_core.loader_v4.retract"; +/// Stable operation code for transferring Loader v4 program authority. +pub const EX_SOLANA_CORE_LOADER_V4_TRANSFER_AUTHORITY_OPERATION: &str = + "solana_core.loader_v4.transfer_authority"; +/// Stable operation code for finalizing a Loader v4 program. +pub const EX_SOLANA_CORE_LOADER_V4_FINALIZE_OPERATION: &str = "solana_core.loader_v4.finalize"; +/// Stable operation code for creating and initializing a Config Program account. +pub const EX_SOLANA_CORE_CONFIG_CREATE_ACCOUNT_OPERATION: &str = + "solana_core.config.create_account"; +/// Stable operation code for storing opaque serialized data in a Config Program account. +pub const EX_SOLANA_CORE_CONFIG_STORE_OPERATION: &str = "solana_core.config.store"; +/// Stable operation code for activating a native feature account. +pub const EX_SOLANA_CORE_FEATURE_ACTIVATE_OPERATION: &str = "solana_core.feature.activate"; +/// Stable operation code for revoking a pending native feature activation. +pub const EX_SOLANA_CORE_FEATURE_REVOKE_PENDING_ACTIVATION_OPERATION: &str = + "solana_core.feature.revoke_pending_activation"; +/// Stable operation code for closing a retained Slashing violation report. +pub const EX_SOLANA_CORE_SLASHING_CLOSE_VIOLATION_REPORT_OPERATION: &str = + "solana_core.slashing.close_violation_report"; +/// Stable operation code for the atomic duplicate-block proof submission plan. +pub const EX_SOLANA_CORE_SLASHING_SUBMIT_DUPLICATE_BLOCK_PROOF_OPERATION: &str = + "solana_core.slashing.submit_duplicate_block_proof"; +/// Stable operation code for an inline ZK ElGamal proof verification. +pub const EX_SOLANA_CORE_ZK_ELGAMAL_VERIFY_INLINE_OPERATION: &str = + "solana_core.zk_elgamal.verify_inline"; +/// Stable operation code for a ZK ElGamal proof read from an account. +pub const EX_SOLANA_CORE_ZK_ELGAMAL_VERIFY_FROM_ACCOUNT_OPERATION: &str = + "solana_core.zk_elgamal.verify_from_account"; +/// Stable operation code for closing a ZK ElGamal context-state account. +pub const EX_SOLANA_CORE_ZK_ELGAMAL_CLOSE_CONTEXT_STATE_OPERATION: &str = + "solana_core.zk_elgamal.close_context_state"; + +/// Every stable callable operation code exposed by the Solana core executor. +pub const EX_SOLANA_CORE_OPERATION_CODES: [&str; 109] = [ + crate::EX_SOLANA_CORE_SYSTEM_TRANSFER_OPERATION, + crate::EX_SOLANA_CORE_SYSTEM_TRANSFER_MANY_OPERATION, + crate::EX_SOLANA_CORE_SYSTEM_CREATE_ACCOUNT_OPERATION, + crate::EX_SOLANA_CORE_SYSTEM_CREATE_ACCOUNT_WITH_SEED_OPERATION, + crate::EX_SOLANA_CORE_SYSTEM_ALLOCATE_OPERATION, + crate::EX_SOLANA_CORE_SYSTEM_ALLOCATE_WITH_SEED_OPERATION, + crate::EX_SOLANA_CORE_SYSTEM_ASSIGN_OPERATION, + crate::EX_SOLANA_CORE_SYSTEM_ASSIGN_WITH_SEED_OPERATION, + crate::EX_SOLANA_CORE_SYSTEM_TRANSFER_WITH_SEED_OPERATION, + crate::EX_SOLANA_CORE_SYSTEM_CREATE_ACCOUNT_ALLOW_PREFUND_OPERATION, + crate::EX_SOLANA_CORE_SYSTEM_NONCE_CREATE_OPERATION, + crate::EX_SOLANA_CORE_SYSTEM_NONCE_CREATE_WITH_SEED_OPERATION, + crate::EX_SOLANA_CORE_SYSTEM_NONCE_INITIALIZE_OPERATION, + crate::EX_SOLANA_CORE_SYSTEM_NONCE_ADVANCE_OPERATION, + crate::EX_SOLANA_CORE_SYSTEM_NONCE_AUTHORIZE_OPERATION, + crate::EX_SOLANA_CORE_SYSTEM_NONCE_WITHDRAW_OPERATION, + crate::EX_SOLANA_CORE_SYSTEM_NONCE_UPGRADE_OPERATION, + crate::EX_SOLANA_CORE_COMPUTE_BUDGET_REQUEST_HEAP_FRAME_OPERATION, + crate::EX_SOLANA_CORE_COMPUTE_BUDGET_SET_UNIT_LIMIT_OPERATION, + crate::EX_SOLANA_CORE_COMPUTE_BUDGET_SET_UNIT_PRICE_OPERATION, + crate::EX_SOLANA_CORE_COMPUTE_BUDGET_SET_LOADED_ACCOUNTS_DATA_SIZE_LIMIT_OPERATION, + crate::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_CREATE_OPERATION, + crate::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_EXTEND_OPERATION, + crate::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_FREEZE_OPERATION, + crate::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_DEACTIVATE_OPERATION, + crate::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_CLOSE_OPERATION, + crate::EX_SOLANA_CORE_ED25519_VERIFY_INLINE_OPERATION, + crate::EX_SOLANA_CORE_ED25519_VERIFY_OFFSETS_OPERATION, + crate::EX_SOLANA_CORE_SECP256K1_VERIFY_INLINE_OPERATION, + crate::EX_SOLANA_CORE_SECP256K1_VERIFY_OFFSETS_OPERATION, + crate::EX_SOLANA_CORE_SECP256R1_VERIFY_INLINE_OPERATION, + crate::EX_SOLANA_CORE_SECP256R1_VERIFY_OFFSETS_OPERATION, + crate::EX_SOLANA_CORE_STAKE_INITIALIZE_OPERATION, + crate::EX_SOLANA_CORE_STAKE_INITIALIZE_CHECKED_OPERATION, + crate::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_OPERATION, + crate::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_WITH_SEED_OPERATION, + crate::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_CHECKED_OPERATION, + crate::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_WITH_SEED_CHECKED_OPERATION, + crate::EX_SOLANA_CORE_STAKE_SPLIT_OPERATION, + crate::EX_SOLANA_CORE_STAKE_SPLIT_WITH_SEED_OPERATION, + crate::EX_SOLANA_CORE_STAKE_MERGE_OPERATION, + crate::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_AND_DELEGATE_OPERATION, + crate::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_WITH_SEED_AND_DELEGATE_OPERATION, + crate::EX_SOLANA_CORE_STAKE_AUTHORIZE_OPERATION, + crate::EX_SOLANA_CORE_STAKE_AUTHORIZE_CHECKED_OPERATION, + crate::EX_SOLANA_CORE_STAKE_AUTHORIZE_WITH_SEED_OPERATION, + crate::EX_SOLANA_CORE_STAKE_AUTHORIZE_CHECKED_WITH_SEED_OPERATION, + crate::EX_SOLANA_CORE_STAKE_DELEGATE_OPERATION, + crate::EX_SOLANA_CORE_STAKE_WITHDRAW_OPERATION, + crate::EX_SOLANA_CORE_STAKE_DEACTIVATE_OPERATION, + crate::EX_SOLANA_CORE_STAKE_SET_LOCKUP_OPERATION, + crate::EX_SOLANA_CORE_STAKE_SET_LOCKUP_CHECKED_OPERATION, + crate::EX_SOLANA_CORE_STAKE_GET_MINIMUM_DELEGATION_OPERATION, + crate::EX_SOLANA_CORE_STAKE_DEACTIVATE_DELINQUENT_OPERATION, + crate::EX_SOLANA_CORE_STAKE_MOVE_STAKE_OPERATION, + crate::EX_SOLANA_CORE_STAKE_MOVE_LAMPORTS_OPERATION, + crate::EX_SOLANA_CORE_VOTE_INITIALIZE_ACCOUNT_OPERATION, + crate::EX_SOLANA_CORE_VOTE_INITIALIZE_ACCOUNT_V2_OPERATION, + crate::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_OPERATION, + crate::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_WITH_SEED_OPERATION, + crate::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_V2_OPERATION, + crate::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_WITH_SEED_V2_OPERATION, + crate::EX_SOLANA_CORE_VOTE_AUTHORIZE_OPERATION, + crate::EX_SOLANA_CORE_VOTE_AUTHORIZE_CHECKED_OPERATION, + crate::EX_SOLANA_CORE_VOTE_AUTHORIZE_WITH_SEED_OPERATION, + crate::EX_SOLANA_CORE_VOTE_AUTHORIZE_CHECKED_WITH_SEED_OPERATION, + crate::EX_SOLANA_CORE_VOTE_SUBMIT_OPERATION, + crate::EX_SOLANA_CORE_VOTE_SWITCH_OPERATION, + crate::EX_SOLANA_CORE_VOTE_WITHDRAW_OPERATION, + crate::EX_SOLANA_CORE_VOTE_UPDATE_VALIDATOR_IDENTITY_OPERATION, + crate::EX_SOLANA_CORE_VOTE_UPDATE_COMMISSION_OPERATION, + crate::EX_SOLANA_CORE_VOTE_UPDATE_VOTE_STATE_OPERATION, + crate::EX_SOLANA_CORE_VOTE_UPDATE_VOTE_STATE_SWITCH_OPERATION, + crate::EX_SOLANA_CORE_VOTE_COMPACT_UPDATE_VOTE_STATE_OPERATION, + crate::EX_SOLANA_CORE_VOTE_COMPACT_UPDATE_VOTE_STATE_SWITCH_OPERATION, + crate::EX_SOLANA_CORE_VOTE_TOWER_SYNC_OPERATION, + crate::EX_SOLANA_CORE_VOTE_TOWER_SYNC_SWITCH_OPERATION, + crate::EX_SOLANA_CORE_VOTE_UPDATE_COMMISSION_COLLECTOR_OPERATION, + crate::EX_SOLANA_CORE_VOTE_UPDATE_COMMISSION_BPS_OPERATION, + crate::EX_SOLANA_CORE_VOTE_DEPOSIT_DELEGATOR_REWARDS_OPERATION, + crate::EX_SOLANA_CORE_LOADER_V3_CREATE_BUFFER_OPERATION, + crate::EX_SOLANA_CORE_LOADER_V3_WRITE_OPERATION, + crate::EX_SOLANA_CORE_LOADER_V3_DEPLOY_WITH_MAX_PROGRAM_LEN_OPERATION, + crate::EX_SOLANA_CORE_LOADER_V3_UPGRADE_OPERATION, + crate::EX_SOLANA_CORE_LOADER_V3_SET_BUFFER_AUTHORITY_OPERATION, + crate::EX_SOLANA_CORE_LOADER_V3_SET_BUFFER_AUTHORITY_CHECKED_OPERATION, + crate::EX_SOLANA_CORE_LOADER_V3_SET_UPGRADE_AUTHORITY_OPERATION, + crate::EX_SOLANA_CORE_LOADER_V3_SET_UPGRADE_AUTHORITY_CHECKED_OPERATION, + crate::EX_SOLANA_CORE_LOADER_V3_CLOSE_OPERATION, + crate::EX_SOLANA_CORE_LOADER_V3_CLOSE_ANY_OPERATION, + crate::EX_SOLANA_CORE_LOADER_V3_EXTEND_PROGRAM_OPERATION, + crate::EX_SOLANA_CORE_LOADER_V4_CREATE_BUFFER_OPERATION, + crate::EX_SOLANA_CORE_LOADER_V4_SET_PROGRAM_LENGTH_OPERATION, + crate::EX_SOLANA_CORE_LOADER_V4_WRITE_OPERATION, + crate::EX_SOLANA_CORE_LOADER_V4_COPY_OPERATION, + crate::EX_SOLANA_CORE_LOADER_V4_DEPLOY_OPERATION, + crate::EX_SOLANA_CORE_LOADER_V4_DEPLOY_FROM_SOURCE_OPERATION, + crate::EX_SOLANA_CORE_LOADER_V4_RETRACT_OPERATION, + crate::EX_SOLANA_CORE_LOADER_V4_TRANSFER_AUTHORITY_OPERATION, + crate::EX_SOLANA_CORE_LOADER_V4_FINALIZE_OPERATION, + crate::EX_SOLANA_CORE_CONFIG_CREATE_ACCOUNT_OPERATION, + crate::EX_SOLANA_CORE_CONFIG_STORE_OPERATION, + crate::EX_SOLANA_CORE_FEATURE_ACTIVATE_OPERATION, + crate::EX_SOLANA_CORE_FEATURE_REVOKE_PENDING_ACTIVATION_OPERATION, + crate::EX_SOLANA_CORE_SLASHING_CLOSE_VIOLATION_REPORT_OPERATION, + crate::EX_SOLANA_CORE_SLASHING_SUBMIT_DUPLICATE_BLOCK_PROOF_OPERATION, + crate::EX_SOLANA_CORE_ZK_ELGAMAL_VERIFY_INLINE_OPERATION, + crate::EX_SOLANA_CORE_ZK_ELGAMAL_VERIFY_FROM_ACCOUNT_OPERATION, + crate::EX_SOLANA_CORE_ZK_ELGAMAL_CLOSE_CONTEXT_STATE_OPERATION, +]; + +/// One destination in a System Program multi-transfer. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)] +#[ts( + export, + export_to = "../frontend/ts/bindings/kb_lib/executor/solana/core/intent/ExSolanaCoreSystemTransferRecipient.ts" +)] +pub struct ExSolanaCoreSystemTransferRecipient { + /// Recipient account. + pub to: crate::MdPubkey, + /// Lamports transferred to the recipient. + pub lamports: u64, +} + +/// One Ed25519 signature verification offset record. +#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)] +#[ts( + export, + export_to = "../frontend/ts/bindings/kb_lib/executor/solana/core/intent/ExSolanaCoreEd25519VerificationOffsets.ts" +)] +pub struct ExSolanaCoreEd25519VerificationOffsets { + /// Offset to the 64-byte signature. + pub signature_offset: u16, + /// Instruction index containing the signature; `u16::MAX` means this instruction. + pub signature_instruction_index: u16, + /// Offset to the 32-byte public key. + pub public_key_offset: u16, + /// Instruction index containing the public key; `u16::MAX` means this instruction. + pub public_key_instruction_index: u16, + /// Offset to the message bytes. + pub message_data_offset: u16, + /// Message size in bytes. + pub message_data_size: u16, + /// Instruction index containing the message; `u16::MAX` means this instruction. + pub message_instruction_index: u16, +} + +/// One secp256k1 signature verification offset record. +#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)] +#[ts( + export, + export_to = "../frontend/ts/bindings/kb_lib/executor/solana/core/intent/ExSolanaCoreSecp256k1VerificationOffsets.ts" +)] +pub struct ExSolanaCoreSecp256k1VerificationOffsets { + /// Offset to the 64-byte signature followed by its one-byte recovery id. + pub signature_offset: u16, + /// Transaction instruction index containing the signature. + pub signature_instruction_index: u8, + /// Offset to the 20-byte Ethereum address. + pub ethereum_address_offset: u16, + /// Transaction instruction index containing the Ethereum address. + pub ethereum_address_instruction_index: u8, + /// Offset to the message bytes. + pub message_data_offset: u16, + /// Message size in bytes. + pub message_data_size: u16, + /// Transaction instruction index containing the message. + pub message_instruction_index: u8, +} + +/// One secp256r1 signature verification offset record. +#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)] +#[ts( + export, + export_to = "../frontend/ts/bindings/kb_lib/executor/solana/core/intent/ExSolanaCoreSecp256r1VerificationOffsets.ts" +)] +pub struct ExSolanaCoreSecp256r1VerificationOffsets { + /// Offset to the compact 64-byte signature. + pub signature_offset: u16, + /// Instruction index containing the signature; `u16::MAX` means this instruction. + pub signature_instruction_index: u16, + /// Offset to the 33-byte compressed public key. + pub public_key_offset: u16, + /// Instruction index containing the public key; `u16::MAX` means this instruction. + pub public_key_instruction_index: u16, + /// Offset to the message bytes. + pub message_data_offset: u16, + /// Message size in bytes. + pub message_data_size: u16, + /// Instruction index containing the message; `u16::MAX` means this instruction. + pub message_instruction_index: u16, +} + +/// Stake authority role changed by an authorize instruction. +#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)] +#[serde(rename_all = "snake_case")] +#[ts( + export, + export_to = "../frontend/ts/bindings/kb_lib/executor/solana/core/intent/ExSolanaCoreStakeAuthorizationKind.ts" +)] +pub enum ExSolanaCoreStakeAuthorizationKind { + /// Staker authority controlling delegation and stake movement. + Staker, + /// Withdrawer authority controlling withdrawals and authority changes. + Withdrawer, +} + +impl crate::ExSolanaCoreStakeAuthorizationKind { + pub(crate) fn as_official(self) -> solana_stake_interface::state::StakeAuthorize { + return match self { + Self::Staker => solana_stake_interface::state::StakeAuthorize::Staker, + Self::Withdrawer => solana_stake_interface::state::StakeAuthorize::Withdrawer, + }; + } +} + +/// Vote authority role changed by an authorize instruction. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)] +#[serde(tag = "kind", rename_all = "snake_case")] +#[ts( + export, + export_to = "../frontend/ts/bindings/kb_lib/executor/solana/core/intent/ExSolanaCoreVoteAuthorizationKind.ts" +)] +pub enum ExSolanaCoreVoteAuthorizationKind { + /// Vote authority using an Ed25519 public key. + Voter, + /// Authority permitted to withdraw and administer the Vote account. + Withdrawer, + /// Vote authority using a BLS public key and proof of possession. + VoterWithBls { + /// Compressed BLS public key; exactly 48 bytes. + bls_pubkey: std::vec::Vec, + /// Compressed BLS proof of possession; exactly 96 bytes. + bls_proof_of_possession: std::vec::Vec, + }, +} + +/// Vote v2 commission category. +#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)] +#[serde(rename_all = "snake_case")] +#[ts( + export, + export_to = "../frontend/ts/bindings/kb_lib/executor/solana/core/intent/ExSolanaCoreVoteCommissionKind.ts" +)] +pub enum ExSolanaCoreVoteCommissionKind { + /// Inflation-reward commission. + InflationRewards, + /// Block-revenue commission. + BlockRevenue, +} + +impl crate::ExSolanaCoreVoteCommissionKind { + pub(crate) fn as_official(self) -> solana_vote_interface::instruction::CommissionKind { + return match self { + Self::InflationRewards => { + solana_vote_interface::instruction::CommissionKind::InflationRewards + }, + Self::BlockRevenue => solana_vote_interface::instruction::CommissionKind::BlockRevenue, + }; + } +} + +/// One lockout entry used by Vote-state and tower synchronization instructions. +#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)] +#[ts( + export, + export_to = "../frontend/ts/bindings/kb_lib/executor/solana/core/intent/ExSolanaCoreVoteLockout.ts" +)] +pub struct ExSolanaCoreVoteLockout { + /// Voted slot. + pub slot: u64, + /// Lockout confirmation count, bounded by the official history size. + pub confirmation_count: u32, +} + +/// One key entry serialized in the Config Program `ConfigKeys` short vector. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)] +#[ts( + export, + export_to = "../frontend/ts/bindings/kb_lib/executor/solana/core/intent/ExSolanaCoreConfigKey.ts" +)] +pub struct ExSolanaCoreConfigKey { + /// Public key stored in the Config Program key list. + pub pubkey: crate::MdPubkey, + /// Whether the corresponding account must sign a store instruction. + pub is_signer: bool, +} + +/// Optional context-state accounts attached to a ZK ElGamal proof verification. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)] +#[ts( + export, + export_to = "../frontend/ts/bindings/kb_lib/executor/solana/core/intent/ExSolanaCoreZkElGamalContextState.ts" +)] +pub struct ExSolanaCoreZkElGamalContextState { + /// Preallocated writable context-state account. + pub account: crate::MdPubkey, + /// Read-only authority recorded as the context-state owner. + pub authority: crate::MdPubkey, +} + +/// Official proof kinds accepted by the native ZK ElGamal Proof program. +#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)] +#[serde(rename_all = "snake_case")] +#[ts( + export, + export_to = "../frontend/ts/bindings/kb_lib/executor/solana/core/intent/ExSolanaCoreZkElGamalProofType.ts" +)] +pub enum ExSolanaCoreZkElGamalProofType { + /// Zero-ciphertext proof. + ZeroCiphertext, + /// Ciphertext-ciphertext equality proof. + CiphertextCiphertextEquality, + /// Ciphertext-commitment equality proof. + CiphertextCommitmentEquality, + /// ElGamal public-key validity proof. + PubkeyValidity, + /// Percentage-with-cap proof. + PercentageWithCap, + /// Batched 64-bit range proof. + BatchedRangeProofU64, + /// Batched 128-bit range proof. + BatchedRangeProofU128, + /// Batched 256-bit range proof. + BatchedRangeProofU256, + /// Grouped ciphertext with two handles validity proof. + GroupedCiphertext2HandlesValidity, + /// Batched grouped ciphertext with two handles validity proof. + BatchedGroupedCiphertext2HandlesValidity, + /// Grouped ciphertext with three handles validity proof. + GroupedCiphertext3HandlesValidity, + /// Batched grouped ciphertext with three handles validity proof. + BatchedGroupedCiphertext3HandlesValidity, +} + +impl crate::ExSolanaCoreZkElGamalProofType { + /// Returns the official one-byte instruction discriminator. + pub fn discriminator(self) -> u8 { + return match self { + Self::ZeroCiphertext => 1, + Self::CiphertextCiphertextEquality => 2, + Self::CiphertextCommitmentEquality => 3, + Self::PubkeyValidity => 4, + Self::PercentageWithCap => 5, + Self::BatchedRangeProofU64 => 6, + Self::BatchedRangeProofU128 => 7, + Self::BatchedRangeProofU256 => 8, + Self::GroupedCiphertext2HandlesValidity => 9, + Self::BatchedGroupedCiphertext2HandlesValidity => 10, + Self::GroupedCiphertext3HandlesValidity => 11, + Self::BatchedGroupedCiphertext3HandlesValidity => 12, + }; + } + + /// Returns the exact official POD proof-data size for this proof kind. + pub fn proof_data_size(self) -> usize { + return match self { + Self::ZeroCiphertext => std::mem::size_of::(), + Self::CiphertextCiphertextEquality => std::mem::size_of::(), + Self::CiphertextCommitmentEquality => std::mem::size_of::(), + Self::PubkeyValidity => std::mem::size_of::(), + Self::PercentageWithCap => std::mem::size_of::(), + Self::BatchedRangeProofU64 => std::mem::size_of::(), + Self::BatchedRangeProofU128 => std::mem::size_of::(), + Self::BatchedRangeProofU256 => std::mem::size_of::(), + Self::GroupedCiphertext2HandlesValidity => std::mem::size_of::(), + Self::BatchedGroupedCiphertext2HandlesValidity => std::mem::size_of::(), + Self::GroupedCiphertext3HandlesValidity => std::mem::size_of::(), + Self::BatchedGroupedCiphertext3HandlesValidity => std::mem::size_of::(), + }; + } + + /// Returns the exact official context-state account size for this proof kind. + pub fn context_state_size(self) -> usize { + return match self { + Self::ZeroCiphertext => std::mem::size_of::< + solana_zk_elgamal_proof_interface::state::ProofContextState< + solana_zk_elgamal_proof_interface::proof_data::ZeroCiphertextProofContext, + >, + >(), + Self::CiphertextCiphertextEquality => std::mem::size_of::< + solana_zk_elgamal_proof_interface::state::ProofContextState< + solana_zk_elgamal_proof_interface::proof_data::CiphertextCiphertextEqualityProofContext, + >, + >(), + Self::CiphertextCommitmentEquality => std::mem::size_of::< + solana_zk_elgamal_proof_interface::state::ProofContextState< + solana_zk_elgamal_proof_interface::proof_data::CiphertextCommitmentEqualityProofContext, + >, + >(), + Self::PubkeyValidity => std::mem::size_of::< + solana_zk_elgamal_proof_interface::state::ProofContextState< + solana_zk_elgamal_proof_interface::proof_data::PubkeyValidityProofContext, + >, + >(), + Self::PercentageWithCap => std::mem::size_of::< + solana_zk_elgamal_proof_interface::state::ProofContextState< + solana_zk_elgamal_proof_interface::proof_data::PercentageWithCapProofContext, + >, + >(), + Self::BatchedRangeProofU64 + | Self::BatchedRangeProofU128 + | Self::BatchedRangeProofU256 => std::mem::size_of::< + solana_zk_elgamal_proof_interface::state::ProofContextState< + solana_zk_elgamal_proof_interface::proof_data::BatchedRangeProofContext, + >, + >(), + Self::GroupedCiphertext2HandlesValidity => std::mem::size_of::< + solana_zk_elgamal_proof_interface::state::ProofContextState< + solana_zk_elgamal_proof_interface::proof_data::GroupedCiphertext2HandlesValidityProofContext, + >, + >(), + Self::BatchedGroupedCiphertext2HandlesValidity => std::mem::size_of::< + solana_zk_elgamal_proof_interface::state::ProofContextState< + solana_zk_elgamal_proof_interface::proof_data::BatchedGroupedCiphertext2HandlesValidityProofContext, + >, + >(), + Self::GroupedCiphertext3HandlesValidity => std::mem::size_of::< + solana_zk_elgamal_proof_interface::state::ProofContextState< + solana_zk_elgamal_proof_interface::proof_data::GroupedCiphertext3HandlesValidityProofContext, + >, + >(), + Self::BatchedGroupedCiphertext3HandlesValidity => std::mem::size_of::< + solana_zk_elgamal_proof_interface::state::ProofContextState< + solana_zk_elgamal_proof_interface::proof_data::BatchedGroupedCiphertext3HandlesValidityProofContext, + >, + >(), + }; + } +} + +/// Typed Solana core operation arguments. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)] +#[serde(tag = "operation", rename_all = "snake_case")] +#[ts( + export, + export_to = "../frontend/ts/bindings/kb_lib/executor/solana/core/intent/ExSolanaCoreOperation.ts" +)] +pub enum ExSolanaCoreOperation { + /// Transfer lamports between System Program accounts. + SystemTransfer { + /// Funding account and required signer. + from: crate::MdPubkey, + /// Recipient account. + to: crate::MdPubkey, + /// Lamports to transfer. + lamports: u64, + }, + /// Transfer lamports from one System Program account to several recipients. + SystemTransferMany { + /// Funding account and required signer. + from: crate::MdPubkey, + /// Ordered recipients; each entry becomes one official transfer instruction. + recipients: std::vec::Vec, + }, + /// Transfer lamports from a System Program account derived with a seed. + SystemTransferWithSeed { + /// Seed-derived source account. + from: crate::MdPubkey, + /// Base account and required signer. + from_base: crate::MdPubkey, + /// Seed used to derive the source account. + from_seed: std::string::String, + /// Owner used to derive the source account. + from_owner: crate::MdPubkey, + /// Recipient account. + to: crate::MdPubkey, + /// Lamports to transfer. + lamports: u64, + }, + /// Create, fund, allocate and assign a new account. + SystemCreateAccount { + /// Funding account and required signer. + from: crate::MdPubkey, + /// New account and required signer. + new_account: crate::MdPubkey, + /// Lamports transferred into the new account. + lamports: u64, + /// Data space allocated to the new account. + space: u64, + /// Program that will own the new account. + owner: crate::MdPubkey, + }, + /// Create, fund, allocate and assign an account derived with a seed. + SystemCreateAccountWithSeed { + /// Funding account and required signer. + from: crate::MdPubkey, + /// Seed-derived account address. + new_account: crate::MdPubkey, + /// Base account and required signer when distinct from `from`. + base: crate::MdPubkey, + /// Seed used to derive the account. + seed: std::string::String, + /// Lamports transferred into the new account. + lamports: u64, + /// Data space allocated to the new account. + space: u64, + /// Program that will own the new account. + owner: crate::MdPubkey, + }, + /// Create an account while allowing an already prefunded destination. + SystemCreateAccountAllowPrefund { + /// New account and required signer. + new_account: crate::MdPubkey, + /// Optional payer and signer for additional lamports. + payer: std::option::Option, + /// Additional lamports transferred by `payer`; must be zero without a payer. + lamports: u64, + /// Data space allocated to the account. + space: u64, + /// Program that will own the account. + owner: crate::MdPubkey, + }, + /// Allocate data space for a System Program owned account. + SystemAllocate { + /// Account to allocate and required signer. + account: crate::MdPubkey, + /// Data space to allocate. + space: u64, + }, + /// Allocate data space for a System Program account derived with a seed. + SystemAllocateWithSeed { + /// Seed-derived account address. + account: crate::MdPubkey, + /// Base account and required signer. + base: crate::MdPubkey, + /// Seed used to derive the account. + seed: std::string::String, + /// Data space to allocate. + space: u64, + /// Owner used to derive the account. + owner: crate::MdPubkey, + }, + /// Assign a System Program owned account to another program. + SystemAssign { + /// Account to assign and required signer. + account: crate::MdPubkey, + /// New owner program. + owner: crate::MdPubkey, + }, + /// Assign a System Program account derived with a seed. + SystemAssignWithSeed { + /// Seed-derived account address. + account: crate::MdPubkey, + /// Base account and required signer. + base: crate::MdPubkey, + /// Seed used to derive the account. + seed: std::string::String, + /// New owner program, also used in the seeded derivation contract. + owner: crate::MdPubkey, + }, + /// Create and initialize a durable nonce account with a new keypair address. + SystemCreateNonceAccount { + /// Funding account and required signer. + from: crate::MdPubkey, + /// New nonce account and required signer. + nonce_account: crate::MdPubkey, + /// Authority stored in the initialized nonce state. + authority: crate::MdPubkey, + /// Rent-exempt lamports transferred into the nonce account. + lamports: u64, + }, + /// Create and initialize a durable nonce account at a seeded address. + SystemCreateNonceAccountWithSeed { + /// Funding account and required signer. + from: crate::MdPubkey, + /// Seed-derived nonce account address. + nonce_account: crate::MdPubkey, + /// Base account and required signer when distinct from `from`. + base: crate::MdPubkey, + /// Seed used to derive the nonce account under the System Program. + seed: std::string::String, + /// Authority stored in the initialized nonce state. + authority: crate::MdPubkey, + /// Rent-exempt lamports transferred into the nonce account. + lamports: u64, + }, + /// Initialize an existing uninitialized, rent-exempt System nonce account. + SystemInitializeNonceAccount { + /// Writable nonce account. + nonce_account: crate::MdPubkey, + /// Authority stored in the initialized nonce state. + authority: crate::MdPubkey, + }, + /// Advance a durable nonce value using its current authority. + SystemAdvanceNonceAccount { + /// Writable nonce account. + nonce_account: crate::MdPubkey, + /// Current nonce authority and required signer. + authority: crate::MdPubkey, + }, + /// Change the authority of a durable nonce account. + SystemAuthorizeNonceAccount { + /// Writable nonce account. + nonce_account: crate::MdPubkey, + /// Current nonce authority and required signer. + authority: crate::MdPubkey, + /// New nonce authority. + new_authority: crate::MdPubkey, + }, + /// Withdraw lamports from a durable nonce account. + SystemWithdrawNonceAccount { + /// Writable nonce account. + nonce_account: crate::MdPubkey, + /// Current nonce authority and required signer. + authority: crate::MdPubkey, + /// Writable recipient account. + to: crate::MdPubkey, + /// Lamports to withdraw. + lamports: u64, + }, + /// Upgrade a legacy durable nonce state once and idempotently. + SystemUpgradeNonceAccount { + /// Writable nonce account. + nonce_account: crate::MdPubkey, + }, + /// Request a transaction-wide heap frame for every invoked program. + ComputeBudgetRequestHeapFrame { + /// Requested heap-frame size in bytes. + bytes: u32, + }, + /// Set the transaction compute-unit limit. + ComputeBudgetSetUnitLimit { + /// Requested compute-unit limit. + units: u32, + }, + /// Set the transaction compute-unit price. + ComputeBudgetSetUnitPrice { + /// Requested price in micro-lamports per compute unit. + micro_lamports: u64, + }, + /// Set the transaction-wide loaded-account data-size limit. + ComputeBudgetSetLoadedAccountsDataSizeLimit { + /// Requested loaded-account data-size limit in bytes. + bytes: u32, + }, + /// Create an Address Lookup Table at the PDA derived from an authority and recent slot. + AddressLookupTableCreate { + /// Authority recorded in the new lookup table metadata. + authority: crate::MdPubkey, + /// Writable payer and required signer for runtime-computed rent funding. + payer: crate::MdPubkey, + /// Recent slot used by the official PDA derivation. + recent_slot: u64, + }, + /// Extend an Address Lookup Table with an ordered list of addresses. + AddressLookupTableExtend { + /// Writable lookup table account. + lookup_table: crate::MdPubkey, + /// Current lookup table authority and required signer. + authority: crate::MdPubkey, + /// Optional writable payer and signer for a rent top-up. + payer: std::option::Option, + /// Ordered addresses appended to the table. + new_addresses: std::vec::Vec, + }, + /// Permanently freeze an Address Lookup Table. + AddressLookupTableFreeze { + /// Writable lookup table account. + lookup_table: crate::MdPubkey, + /// Current lookup table authority and required signer. + authority: crate::MdPubkey, + }, + /// Deactivate an Address Lookup Table before its cooldown and closure. + AddressLookupTableDeactivate { + /// Writable lookup table account. + lookup_table: crate::MdPubkey, + /// Current lookup table authority and required signer. + authority: crate::MdPubkey, + }, + /// Close a fully deactivated Address Lookup Table and drain its lamports. + AddressLookupTableClose { + /// Writable lookup table account. + lookup_table: crate::MdPubkey, + /// Current lookup table authority and required signer. + authority: crate::MdPubkey, + /// Writable recipient of the closed account lamports. + recipient: crate::MdPubkey, + }, + /// Verify one Ed25519 signature with all bytes embedded in the precompile instruction. + Ed25519VerifyInline { + /// Original message bytes. + message: std::vec::Vec, + /// Raw 64-byte Ed25519 signature. + signature: std::vec::Vec, + /// Raw 32-byte Ed25519 public key. + public_key: std::vec::Vec, + }, + /// Verify one or more Ed25519 signatures through explicit instruction offsets. + Ed25519VerifyOffsets { + /// Ordered offset records. + offsets: std::vec::Vec, + /// Optional bytes appended after the offset table. + data: std::vec::Vec, + }, + /// Verify one secp256k1 signature with all bytes embedded in instruction zero. + Secp256k1VerifyInline { + /// Original message bytes; the runtime hashes them with Keccak-256. + message: std::vec::Vec, + /// Raw compact 64-byte secp256k1 signature. + signature: std::vec::Vec, + /// Recovery id accepted by the runtime. + recovery_id: u8, + /// Twenty-byte Ethereum address expected from the recovered key. + ethereum_address: std::vec::Vec, + }, + /// Verify one or more secp256k1 signatures through explicit transaction offsets. + Secp256k1VerifyOffsets { + /// Ordered offset records; index zero refers to this standalone precompile plan. + offsets: std::vec::Vec, + /// Optional bytes appended after the offset table. + data: std::vec::Vec, + }, + /// Verify one secp256r1 signature with all bytes embedded in the precompile instruction. + Secp256r1VerifyInline { + /// Original message bytes; the runtime hashes them with SHA-256. + message: std::vec::Vec, + /// Raw compact 64-byte low-S secp256r1 signature. + signature: std::vec::Vec, + /// Compressed 33-byte secp256r1 public key. + public_key: std::vec::Vec, + }, + /// Verify one or more secp256r1 signatures through explicit instruction offsets. + Secp256r1VerifyOffsets { + /// Ordered offset records. + offsets: std::vec::Vec, + /// Optional bytes appended after the offset table. + data: std::vec::Vec, + }, + /// Initialize an existing rent-exempt Stake account. + StakeInitialize { + /// Writable uninitialized Stake account. + stake_account: crate::MdPubkey, + /// Staker authority recorded in the account. + staker: crate::MdPubkey, + /// Withdraw authority recorded in the account. + withdrawer: crate::MdPubkey, + /// Initial lockup Unix timestamp. + lockup_unix_timestamp: i64, + /// Initial lockup epoch. + lockup_epoch: u64, + /// Initial lockup custodian. + lockup_custodian: crate::MdPubkey, + }, + /// Initialize a Stake account while requiring the withdraw authority signature. + StakeInitializeChecked { + /// Writable uninitialized Stake account. + stake_account: crate::MdPubkey, + /// Staker authority recorded in the account. + staker: crate::MdPubkey, + /// Withdraw authority and required signer. + withdrawer: crate::MdPubkey, + }, + /// Create and initialize a new Stake account. + StakeCreateAccount { + /// Funding account and required signer. + from: crate::MdPubkey, + /// New Stake account and required signer. + stake_account: crate::MdPubkey, + /// Staker authority recorded in the account. + staker: crate::MdPubkey, + /// Withdraw authority recorded in the account. + withdrawer: crate::MdPubkey, + /// Initial lockup Unix timestamp. + lockup_unix_timestamp: i64, + /// Initial lockup epoch. + lockup_epoch: u64, + /// Initial lockup custodian. + lockup_custodian: crate::MdPubkey, + /// Exact measured rent-exempt funding amount. + lamports: u64, + }, + /// Create and initialize a Stake account at a seeded address. + StakeCreateAccountWithSeed { + /// Funding account and required signer. + from: crate::MdPubkey, + /// Seed-derived Stake account. + stake_account: crate::MdPubkey, + /// Seed base and required signer. + base: crate::MdPubkey, + /// Seed used for derivation. + seed: std::string::String, + /// Staker authority recorded in the account. + staker: crate::MdPubkey, + /// Withdraw authority recorded in the account. + withdrawer: crate::MdPubkey, + /// Initial lockup Unix timestamp. + lockup_unix_timestamp: i64, + /// Initial lockup epoch. + lockup_epoch: u64, + /// Initial lockup custodian. + lockup_custodian: crate::MdPubkey, + /// Exact measured rent-exempt funding amount. + lamports: u64, + }, + /// Create and checked-initialize a new Stake account. + StakeCreateAccountChecked { + /// Funding account and required signer. + from: crate::MdPubkey, + /// New Stake account and required signer. + stake_account: crate::MdPubkey, + /// Staker authority recorded in the account. + staker: crate::MdPubkey, + /// Withdraw authority and required signer. + withdrawer: crate::MdPubkey, + /// Exact measured rent-exempt funding amount. + lamports: u64, + }, + /// Create and checked-initialize a seeded Stake account. + StakeCreateAccountWithSeedChecked { + /// Funding account and required signer. + from: crate::MdPubkey, + /// Seed-derived Stake account. + stake_account: crate::MdPubkey, + /// Seed base and required signer. + base: crate::MdPubkey, + /// Seed used for derivation. + seed: std::string::String, + /// Staker authority recorded in the account. + staker: crate::MdPubkey, + /// Withdraw authority and required signer. + withdrawer: crate::MdPubkey, + /// Exact measured rent-exempt funding amount. + lamports: u64, + }, + /// Allocate and split lamports into a new Stake account. + StakeSplit { + /// Writable source Stake account. + stake_account: crate::MdPubkey, + /// Current staker authority and required signer. + stake_authority: crate::MdPubkey, + /// Lamports assigned to the split account. + lamports: u64, + /// New split Stake account and required signer. + split_stake_account: crate::MdPubkey, + }, + /// Allocate and split into a seeded Stake account. + StakeSplitWithSeed { + /// Writable source Stake account. + stake_account: crate::MdPubkey, + /// Current staker authority and required signer. + stake_authority: crate::MdPubkey, + /// Lamports assigned to the split account. + lamports: u64, + /// Seed-derived split Stake account. + split_stake_account: crate::MdPubkey, + /// Seed base and required signer. + base: crate::MdPubkey, + /// Seed used for derivation. + seed: std::string::String, + }, + /// Merge one compatible Stake account into another. + StakeMerge { + /// Writable destination Stake account. + destination_stake_account: crate::MdPubkey, + /// Writable source Stake account consumed by the merge. + source_stake_account: crate::MdPubkey, + /// Current staker authority and required signer. + stake_authority: crate::MdPubkey, + }, + /// Create, initialize and delegate a new Stake account. + StakeCreateAccountAndDelegate { + /// Funding account and required signer. + from: crate::MdPubkey, + /// New Stake account and required signer. + stake_account: crate::MdPubkey, + /// Vote account receiving the delegation. + vote_account: crate::MdPubkey, + /// Staker authority and required signer for delegation. + staker: crate::MdPubkey, + /// Withdraw authority recorded in the account. + withdrawer: crate::MdPubkey, + /// Initial lockup Unix timestamp. + lockup_unix_timestamp: i64, + /// Initial lockup epoch. + lockup_epoch: u64, + /// Initial lockup custodian. + lockup_custodian: crate::MdPubkey, + /// Exact measured funding amount. + lamports: u64, + }, + /// Create, initialize and delegate a seeded Stake account. + StakeCreateAccountWithSeedAndDelegate { + /// Funding account and required signer. + from: crate::MdPubkey, + /// Seed-derived Stake account. + stake_account: crate::MdPubkey, + /// Seed base and required signer. + base: crate::MdPubkey, + /// Seed used for derivation. + seed: std::string::String, + /// Vote account receiving the delegation. + vote_account: crate::MdPubkey, + /// Staker authority and required signer for delegation. + staker: crate::MdPubkey, + /// Withdraw authority recorded in the account. + withdrawer: crate::MdPubkey, + /// Initial lockup Unix timestamp. + lockup_unix_timestamp: i64, + /// Initial lockup epoch. + lockup_epoch: u64, + /// Initial lockup custodian. + lockup_custodian: crate::MdPubkey, + /// Exact measured funding amount. + lamports: u64, + }, + /// Change a staker or withdraw authority. + StakeAuthorize { + /// Writable Stake account. + stake_account: crate::MdPubkey, + /// Current authority and required signer. + authority: crate::MdPubkey, + /// New authority. + new_authority: crate::MdPubkey, + /// Authority role being changed. + authorization: crate::ExSolanaCoreStakeAuthorizationKind, + /// Optional current lockup custodian and required signer. + lockup_authority: std::option::Option, + }, + /// Change a Stake authority while requiring the new authority signature. + StakeAuthorizeChecked { + /// Writable Stake account. + stake_account: crate::MdPubkey, + /// Current authority and required signer. + authority: crate::MdPubkey, + /// New authority and required signer. + new_authority: crate::MdPubkey, + /// Authority role being changed. + authorization: crate::ExSolanaCoreStakeAuthorizationKind, + /// Optional current lockup custodian and required signer. + lockup_authority: std::option::Option, + }, + /// Change a Stake authority derived from a base, seed and owner. + StakeAuthorizeWithSeed { + /// Writable Stake account. + stake_account: crate::MdPubkey, + /// Authority base and required signer. + authority_base: crate::MdPubkey, + /// Authority derivation seed. + authority_seed: std::string::String, + /// Authority derivation owner. + authority_owner: crate::MdPubkey, + /// New authority. + new_authority: crate::MdPubkey, + /// Authority role being changed. + authorization: crate::ExSolanaCoreStakeAuthorizationKind, + /// Optional current lockup custodian and required signer. + lockup_authority: std::option::Option, + }, + /// Change a seeded Stake authority while requiring the new authority signature. + StakeAuthorizeCheckedWithSeed { + /// Writable Stake account. + stake_account: crate::MdPubkey, + /// Authority base and required signer. + authority_base: crate::MdPubkey, + /// Authority derivation seed. + authority_seed: std::string::String, + /// Authority derivation owner. + authority_owner: crate::MdPubkey, + /// New authority and required signer. + new_authority: crate::MdPubkey, + /// Authority role being changed. + authorization: crate::ExSolanaCoreStakeAuthorizationKind, + /// Optional current lockup custodian and required signer. + lockup_authority: std::option::Option, + }, + /// Delegate a Stake account to a Vote account. + StakeDelegate { + /// Writable Stake account. + stake_account: crate::MdPubkey, + /// Current staker authority and required signer. + stake_authority: crate::MdPubkey, + /// Vote account receiving the delegation. + vote_account: crate::MdPubkey, + }, + /// Withdraw lamports from a Stake account. + StakeWithdraw { + /// Writable Stake account. + stake_account: crate::MdPubkey, + /// Current withdraw authority and required signer. + withdraw_authority: crate::MdPubkey, + /// Writable recipient. + to: crate::MdPubkey, + /// Lamports to withdraw. + lamports: u64, + /// Optional lockup custodian and required signer. + lockup_authority: std::option::Option, + }, + /// Deactivate a delegated Stake account. + StakeDeactivate { + /// Writable Stake account. + stake_account: crate::MdPubkey, + /// Current staker authority and required signer. + stake_authority: crate::MdPubkey, + }, + /// Change lockup fields using the current custodian. + StakeSetLockup { + /// Writable Stake account. + stake_account: crate::MdPubkey, + /// Current custodian and required signer. + authority: crate::MdPubkey, + /// Optional replacement Unix timestamp. + unix_timestamp: std::option::Option, + /// Optional replacement epoch. + epoch: std::option::Option, + /// Optional replacement custodian encoded in the payload. + custodian: std::option::Option, + }, + /// Change lockup fields and require a new custodian signature when supplied. + StakeSetLockupChecked { + /// Writable Stake account. + stake_account: crate::MdPubkey, + /// Current custodian and required signer. + authority: crate::MdPubkey, + /// Optional replacement Unix timestamp. + unix_timestamp: std::option::Option, + /// Optional replacement epoch. + epoch: std::option::Option, + /// Optional new custodian and required signer. + new_custodian: std::option::Option, + }, + /// Request the current minimum Stake delegation from return data. + StakeGetMinimumDelegation, + /// Deactivate Stake delegated to a delinquent Vote account. + StakeDeactivateDelinquent { + /// Writable delegated Stake account. + stake_account: crate::MdPubkey, + /// Delinquent Vote account. + delinquent_vote_account: crate::MdPubkey, + /// Reference Vote account satisfying the runtime activity rule. + reference_vote_account: crate::MdPubkey, + }, + /// Move delegated Stake between compatible Stake accounts. + StakeMoveStake { + /// Writable source Stake account. + source_stake_account: crate::MdPubkey, + /// Writable destination Stake account. + destination_stake_account: crate::MdPubkey, + /// Current staker authority and required signer. + stake_authority: crate::MdPubkey, + /// Stake lamports to move. + lamports: u64, + }, + /// Move free lamports between compatible Stake accounts. + StakeMoveLamports { + /// Writable source Stake account. + source_stake_account: crate::MdPubkey, + /// Writable destination Stake account. + destination_stake_account: crate::MdPubkey, + /// Current staker authority and required signer. + stake_authority: crate::MdPubkey, + /// Lamports to move. + lamports: u64, + }, + /// Initialize an already allocated legacy Vote account. + VoteInitializeAccount { + /// Writable uninitialized Vote account. + vote_account: crate::MdPubkey, + /// Validator identity and required signer. + node_pubkey: crate::MdPubkey, + /// Initial vote authority. + authorized_voter: crate::MdPubkey, + /// Initial withdraw authority. + authorized_withdrawer: crate::MdPubkey, + /// Legacy commission percentage in the inclusive range 0..=100. + commission_percent: u8, + }, + /// Initialize an already allocated Vote v2 account. + VoteInitializeAccountV2 { + /// Writable uninitialized Vote account. + vote_account: crate::MdPubkey, + /// Validator identity and required signer. + node_pubkey: crate::MdPubkey, + /// Initial vote authority. + authorized_voter: crate::MdPubkey, + /// Compressed BLS vote-authority public key. + authorized_voter_bls_pubkey: std::vec::Vec, + /// Compressed BLS proof of possession. + authorized_voter_bls_proof_of_possession: std::vec::Vec, + /// Initial withdraw authority. + authorized_withdrawer: crate::MdPubkey, + /// Inflation-reward commission in basis points. + inflation_rewards_commission_bps: u16, + /// Block-revenue commission in basis points. + block_revenue_commission_bps: u16, + /// Writable inflation-reward collector. + inflation_rewards_collector: crate::MdPubkey, + /// Writable block-revenue collector. + block_revenue_collector: crate::MdPubkey, + }, + /// Create and initialize a legacy Vote account. + VoteCreateAccount { + /// Funding account and required signer. + from: crate::MdPubkey, + /// New Vote account and required signer. + vote_account: crate::MdPubkey, + /// Validator identity and required signer. + node_pubkey: crate::MdPubkey, + /// Initial vote authority. + authorized_voter: crate::MdPubkey, + /// Initial withdraw authority. + authorized_withdrawer: crate::MdPubkey, + /// Legacy commission percentage. + commission_percent: u8, + /// Exact measured rent-exempt funding amount. + lamports: u64, + }, + /// Create and initialize a seeded legacy Vote account. + VoteCreateAccountWithSeed { + /// Funding account and required signer. + from: crate::MdPubkey, + /// Seed-derived Vote account. + vote_account: crate::MdPubkey, + /// Seed base and required signer when distinct from `from`. + base: crate::MdPubkey, + /// Vote-account derivation seed. + seed: std::string::String, + /// Validator identity and required signer. + node_pubkey: crate::MdPubkey, + /// Initial vote authority. + authorized_voter: crate::MdPubkey, + /// Initial withdraw authority. + authorized_withdrawer: crate::MdPubkey, + /// Legacy commission percentage. + commission_percent: u8, + /// Exact measured rent-exempt funding amount. + lamports: u64, + }, + /// Create and initialize a Vote v2 account. + VoteCreateAccountV2 { + /// Funding account and required signer. + from: crate::MdPubkey, + /// New Vote account and required signer. + vote_account: crate::MdPubkey, + /// Validator identity and required signer. + node_pubkey: crate::MdPubkey, + /// Initial vote authority. + authorized_voter: crate::MdPubkey, + /// Compressed BLS vote-authority public key. + authorized_voter_bls_pubkey: std::vec::Vec, + /// Compressed BLS proof of possession. + authorized_voter_bls_proof_of_possession: std::vec::Vec, + /// Initial withdraw authority. + authorized_withdrawer: crate::MdPubkey, + /// Inflation-reward commission in basis points. + inflation_rewards_commission_bps: u16, + /// Block-revenue commission in basis points. + block_revenue_commission_bps: u16, + /// Writable inflation-reward collector. + inflation_rewards_collector: crate::MdPubkey, + /// Writable block-revenue collector. + block_revenue_collector: crate::MdPubkey, + /// Exact measured rent-exempt funding amount. + lamports: u64, + }, + /// Create and initialize a seeded Vote v2 account. + VoteCreateAccountWithSeedV2 { + /// Funding account and required signer. + from: crate::MdPubkey, + /// Seed-derived Vote account. + vote_account: crate::MdPubkey, + /// Seed base and required signer when distinct from `from`. + base: crate::MdPubkey, + /// Vote-account derivation seed. + seed: std::string::String, + /// Validator identity and required signer. + node_pubkey: crate::MdPubkey, + /// Initial vote authority. + authorized_voter: crate::MdPubkey, + /// Compressed BLS vote-authority public key. + authorized_voter_bls_pubkey: std::vec::Vec, + /// Compressed BLS proof of possession. + authorized_voter_bls_proof_of_possession: std::vec::Vec, + /// Initial withdraw authority. + authorized_withdrawer: crate::MdPubkey, + /// Inflation-reward commission in basis points. + inflation_rewards_commission_bps: u16, + /// Block-revenue commission in basis points. + block_revenue_commission_bps: u16, + /// Writable inflation-reward collector. + inflation_rewards_collector: crate::MdPubkey, + /// Writable block-revenue collector. + block_revenue_collector: crate::MdPubkey, + /// Exact measured rent-exempt funding amount. + lamports: u64, + }, + /// Change a Vote authority. + VoteAuthorize { + /// Writable Vote account. + vote_account: crate::MdPubkey, + /// Current authority and required signer. + current_authority: crate::MdPubkey, + /// New authority recorded in the payload. + new_authority: crate::MdPubkey, + /// Authority role and optional BLS material. + authorization: crate::ExSolanaCoreVoteAuthorizationKind, + }, + /// Change a Vote authority while requiring the new authority signature. + VoteAuthorizeChecked { + /// Writable Vote account. + vote_account: crate::MdPubkey, + /// Current authority and required signer. + current_authority: crate::MdPubkey, + /// New authority and required signer. + new_authority: crate::MdPubkey, + /// Authority role and optional BLS material. + authorization: crate::ExSolanaCoreVoteAuthorizationKind, + }, + /// Change a Vote authority derived from a base, seed and owner. + VoteAuthorizeWithSeed { + /// Writable Vote account. + vote_account: crate::MdPubkey, + /// Current authority base and required signer. + current_authority_base: crate::MdPubkey, + /// Owner used to derive the current authority. + current_authority_derived_key_owner: crate::MdPubkey, + /// Seed used to derive the current authority. + current_authority_derived_key_seed: std::string::String, + /// New authority recorded in the payload. + new_authority: crate::MdPubkey, + /// Authority role and optional BLS material. + authorization: crate::ExSolanaCoreVoteAuthorizationKind, + }, + /// Change a seeded Vote authority while requiring the new authority signature. + VoteAuthorizeCheckedWithSeed { + /// Writable Vote account. + vote_account: crate::MdPubkey, + /// Current authority base and required signer. + current_authority_base: crate::MdPubkey, + /// Owner used to derive the current authority. + current_authority_derived_key_owner: crate::MdPubkey, + /// Seed used to derive the current authority. + current_authority_derived_key_seed: std::string::String, + /// New authority and required signer. + new_authority: crate::MdPubkey, + /// Authority role and optional BLS material. + authorization: crate::ExSolanaCoreVoteAuthorizationKind, + }, + /// Submit recent voted slots. + VoteSubmit { + /// Writable Vote account. + vote_account: crate::MdPubkey, + /// Vote authority and required signer. + authorized_voter: crate::MdPubkey, + /// Strictly increasing voted slots. + slots: std::vec::Vec, + /// Bank hash for the last voted slot. + bank_hash: std::string::String, + /// Optional vote timestamp. + timestamp: std::option::Option, + }, + /// Submit recent voted slots with a fork-switch proof. + VoteSwitch { + /// Writable Vote account. + vote_account: crate::MdPubkey, + /// Vote authority and required signer. + authorized_voter: crate::MdPubkey, + /// Strictly increasing voted slots. + slots: std::vec::Vec, + /// Bank hash for the last voted slot. + bank_hash: std::string::String, + /// Optional vote timestamp. + timestamp: std::option::Option, + /// Fork-switch proof hash. + switch_proof_hash: std::string::String, + }, + /// Withdraw lamports from a Vote account. + VoteWithdraw { + /// Writable Vote account. + vote_account: crate::MdPubkey, + /// Withdraw authority and required signer. + withdraw_authority: crate::MdPubkey, + /// Writable recipient account. + to: crate::MdPubkey, + /// Lamports to withdraw. + lamports: u64, + }, + /// Change the validator identity recorded by a Vote account. + VoteUpdateValidatorIdentity { + /// Writable Vote account. + vote_account: crate::MdPubkey, + /// Withdraw authority and required signer. + withdraw_authority: crate::MdPubkey, + /// New validator identity and required signer. + new_validator_identity: crate::MdPubkey, + }, + /// Change the legacy commission percentage. + VoteUpdateCommission { + /// Writable Vote account. + vote_account: crate::MdPubkey, + /// Withdraw authority and required signer. + withdraw_authority: crate::MdPubkey, + /// Commission percentage in the inclusive range 0..=100. + commission_percent: u8, + }, + /// Replace the proposed Vote state. + VoteUpdateVoteState { + /// Writable Vote account. + vote_account: crate::MdPubkey, + /// Vote authority and required signer. + authorized_voter: crate::MdPubkey, + /// Strictly increasing lockout tower. + lockouts: std::vec::Vec, + /// Optional root at or below the first lockout slot. + root: std::option::Option, + /// Bank hash for the last voted slot. + bank_hash: std::string::String, + /// Optional vote timestamp. + timestamp: std::option::Option, + }, + /// Replace the proposed Vote state with a fork-switch proof. + VoteUpdateVoteStateSwitch { + /// Writable Vote account. + vote_account: crate::MdPubkey, + /// Vote authority and required signer. + authorized_voter: crate::MdPubkey, + /// Strictly increasing lockout tower. + lockouts: std::vec::Vec, + /// Optional root at or below the first lockout slot. + root: std::option::Option, + /// Bank hash for the last voted slot. + bank_hash: std::string::String, + /// Optional vote timestamp. + timestamp: std::option::Option, + /// Fork-switch proof hash. + switch_proof_hash: std::string::String, + }, + /// Replace Vote state through the compact wire representation. + VoteCompactUpdateVoteState { + /// Writable Vote account. + vote_account: crate::MdPubkey, + /// Vote authority and required signer. + authorized_voter: crate::MdPubkey, + /// Strictly increasing lockout tower. + lockouts: std::vec::Vec, + /// Optional root at or below the first lockout slot. + root: std::option::Option, + /// Bank hash for the last voted slot. + bank_hash: std::string::String, + /// Optional vote timestamp. + timestamp: std::option::Option, + }, + /// Replace compact Vote state with a fork-switch proof. + VoteCompactUpdateVoteStateSwitch { + /// Writable Vote account. + vote_account: crate::MdPubkey, + /// Vote authority and required signer. + authorized_voter: crate::MdPubkey, + /// Strictly increasing lockout tower. + lockouts: std::vec::Vec, + /// Optional root at or below the first lockout slot. + root: std::option::Option, + /// Bank hash for the last voted slot. + bank_hash: std::string::String, + /// Optional vote timestamp. + timestamp: std::option::Option, + /// Fork-switch proof hash. + switch_proof_hash: std::string::String, + }, + /// Synchronize the complete Vote tower. + VoteTowerSync { + /// Writable Vote account. + vote_account: crate::MdPubkey, + /// Vote authority and required signer. + authorized_voter: crate::MdPubkey, + /// Strictly increasing lockout tower. + lockouts: std::vec::Vec, + /// Optional root at or below the first lockout slot. + root: std::option::Option, + /// Bank hash for the last voted slot. + bank_hash: std::string::String, + /// Optional vote timestamp. + timestamp: std::option::Option, + /// Chain block identifier. + block_id: std::string::String, + }, + /// Synchronize the complete Vote tower with a fork-switch proof. + VoteTowerSyncSwitch { + /// Writable Vote account. + vote_account: crate::MdPubkey, + /// Vote authority and required signer. + authorized_voter: crate::MdPubkey, + /// Strictly increasing lockout tower. + lockouts: std::vec::Vec, + /// Optional root at or below the first lockout slot. + root: std::option::Option, + /// Bank hash for the last voted slot. + bank_hash: std::string::String, + /// Optional vote timestamp. + timestamp: std::option::Option, + /// Chain block identifier. + block_id: std::string::String, + /// Fork-switch proof hash. + switch_proof_hash: std::string::String, + }, + /// Change one Vote v2 commission collector. + VoteUpdateCommissionCollector { + /// Writable Vote account. + vote_account: crate::MdPubkey, + /// Withdraw authority and required signer. + withdraw_authority: crate::MdPubkey, + /// Writable replacement collector. + new_collector: crate::MdPubkey, + /// Commission category whose collector is changed. + commission_kind: crate::ExSolanaCoreVoteCommissionKind, + }, + /// Change one Vote v2 commission in basis points. + VoteUpdateCommissionBps { + /// Writable Vote account. + vote_account: crate::MdPubkey, + /// Withdraw authority and required signer. + withdraw_authority: crate::MdPubkey, + /// Commission category being changed. + commission_kind: crate::ExSolanaCoreVoteCommissionKind, + /// Commission in the inclusive range 0..=10_000 basis points. + commission_bps: u16, + }, + /// Deposit delegator rewards into a Vote account. + VoteDepositDelegatorRewards { + /// Writable Vote account. + vote_account: crate::MdPubkey, + /// Writable source and required signer. + source: crate::MdPubkey, + /// Lamports deposited from the source account. + deposit_lamports: u64, + }, + /// Atomically create and initialize a mutable Loader v3 buffer. + LoaderV3CreateBuffer { + /// Funding account and required signer. + payer: crate::MdPubkey, + /// New buffer account and required signer. + buffer: crate::MdPubkey, + /// Initial buffer authority. + authority: crate::MdPubkey, + /// Rent-exempt lamports supplied by the caller. + lamports: u64, + /// Maximum program bytes stored after the Loader v3 buffer metadata. + program_len: u64, + }, + /// Write one bounded chunk into a Loader v3 buffer. + LoaderV3Write { + /// Writable buffer account. + buffer: crate::MdPubkey, + /// Buffer authority and required signer. + authority: crate::MdPubkey, + /// Byte offset inside the buffer program-data region. + offset: u32, + /// Program bytes to write. + bytes: std::vec::Vec, + }, + /// Atomically create a Program account and deploy from a Loader v3 buffer. + LoaderV3DeployWithMaxProgramLen { + /// Funding account and required signer. + payer: crate::MdPubkey, + /// New Program account and required signer. + program: crate::MdPubkey, + /// Populated writable buffer account. + buffer: crate::MdPubkey, + /// Upgrade authority and required signer. + upgrade_authority: crate::MdPubkey, + /// Rent-exempt lamports for the Program account. + program_lamports: u64, + /// Maximum future ProgramData bytecode length. + max_data_len: u64, + /// Whether the runtime should close the buffer after deployment. + close_buffer: bool, + }, + /// Upgrade one Loader v3 program from a populated buffer. + LoaderV3Upgrade { + /// Writable executable Program account. + program: crate::MdPubkey, + /// Writable source buffer. + buffer: crate::MdPubkey, + /// Upgrade authority and required signer. + authority: crate::MdPubkey, + /// Writable account receiving excess buffer lamports. + spill: crate::MdPubkey, + /// Whether the runtime should close the buffer after upgrade. + close_buffer: bool, + }, + /// Change a Loader v3 buffer authority without requiring the new authority to sign. + LoaderV3SetBufferAuthority { + /// Writable buffer account. + buffer: crate::MdPubkey, + /// Current authority and required signer. + current_authority: crate::MdPubkey, + /// Replacement buffer authority. + new_authority: crate::MdPubkey, + }, + /// Change a Loader v3 buffer authority while requiring the new authority to sign. + LoaderV3SetBufferAuthorityChecked { + /// Writable buffer account. + buffer: crate::MdPubkey, + /// Current authority and required signer. + current_authority: crate::MdPubkey, + /// Replacement authority and required signer. + new_authority: crate::MdPubkey, + }, + /// Change or permanently revoke a Loader v3 program upgrade authority. + LoaderV3SetUpgradeAuthority { + /// Executable Program account whose ProgramData authority is changed. + program: crate::MdPubkey, + /// Current authority and required signer. + current_authority: crate::MdPubkey, + /// Replacement authority, or `None` to make the program immutable. + new_authority: std::option::Option, + }, + /// Change a Loader v3 program authority while requiring the new authority to sign. + LoaderV3SetUpgradeAuthorityChecked { + /// Executable Program account whose ProgramData authority is changed. + program: crate::MdPubkey, + /// Current authority and required signer. + current_authority: crate::MdPubkey, + /// Replacement authority and required signer. + new_authority: crate::MdPubkey, + }, + /// Close an initialized Loader v3 buffer. + LoaderV3Close { + /// Writable Loader v3 account to close. + close_account: crate::MdPubkey, + /// Writable lamport recipient. + recipient: crate::MdPubkey, + /// Account authority and required signer. + authority: crate::MdPubkey, + /// Whether to request the current tombstone wire flag. + tombstone: bool, + }, + /// Close a Loader v3 ProgramData, buffer or uninitialized account. + LoaderV3CloseAny { + /// Writable Loader v3 account to close. + close_account: crate::MdPubkey, + /// Writable lamport recipient. + recipient: crate::MdPubkey, + /// Optional authority signer required for initialized accounts. + authority: std::option::Option, + /// Optional writable associated Program account for ProgramData closure. + program: std::option::Option, + /// Whether to tombstone the associated program cache entry. + tombstone: bool, + }, + /// Extend one Loader v3 ProgramData allocation. + LoaderV3ExtendProgram { + /// Writable executable Program account. + program: crate::MdPubkey, + /// Optional writable payer signer when a rent top-up is required. + payer: std::option::Option, + /// Positive number of bytes added to ProgramData. + additional_bytes: u32, + }, + /// Atomically create and initialize one Loader v4 buffer account. + LoaderV4CreateBuffer { + /// Funding account and required signer. + payer: crate::MdPubkey, + /// New Loader v4 buffer account and required signer. + buffer: crate::MdPubkey, + /// Program authority and required signer for initial sizing. + authority: crate::MdPubkey, + /// Writable account receiving superfluous lamports or closure proceeds. + recipient: crate::MdPubkey, + /// Lamports transferred by the System Program account creation. + lamports: u64, + /// Positive initial byte length assigned by Loader v4. + new_size: u32, + }, + /// Change the allocation of an undeployed Loader v4 program account. + LoaderV4SetProgramLength { + /// Writable Loader v4 program account. + program: crate::MdPubkey, + /// Program authority and required signer. + authority: crate::MdPubkey, + /// Writable account receiving superfluous lamports or closure proceeds. + recipient: crate::MdPubkey, + /// New program length; zero closes and resets the account. + new_size: u32, + }, + /// Write one bounded chunk into an undeployed Loader v4 program account. + LoaderV4Write { + /// Writable Loader v4 program account. + program: crate::MdPubkey, + /// Program authority and required signer. + authority: crate::MdPubkey, + /// Byte offset inside the program account. + offset: u32, + /// Program bytes to write. + bytes: std::vec::Vec, + }, + /// Copy one bounded range between Loader v4 program accounts. + LoaderV4Copy { + /// Writable destination Loader v4 program account. + program: crate::MdPubkey, + /// Destination authority and required signer. + authority: crate::MdPubkey, + /// Read-only source Loader v4 program account. + source_program: crate::MdPubkey, + /// Destination byte offset. + destination_offset: u32, + /// Source byte offset. + source_offset: u32, + /// Positive number of bytes copied. + length: u32, + }, + /// Deploy an undeployed Loader v4 program account in place. + LoaderV4Deploy { + /// Writable Loader v4 program account. + program: crate::MdPubkey, + /// Program authority and required signer. + authority: crate::MdPubkey, + }, + /// Deploy a Loader v4 program from another undeployed source account. + LoaderV4DeployFromSource { + /// Writable Loader v4 destination program account. + program: crate::MdPubkey, + /// Destination authority and required signer. + authority: crate::MdPubkey, + /// Writable source account consumed by deployment. + source_program: crate::MdPubkey, + }, + /// Retract a deployed Loader v4 program into maintenance. + LoaderV4Retract { + /// Writable Loader v4 program account. + program: crate::MdPubkey, + /// Program authority and required signer. + authority: crate::MdPubkey, + }, + /// Transfer Loader v4 program authority. + LoaderV4TransferAuthority { + /// Writable Loader v4 program account. + program: crate::MdPubkey, + /// Current authority and required signer. + current_authority: crate::MdPubkey, + /// Replacement authority and required signer. + new_authority: crate::MdPubkey, + }, + /// Permanently finalize a Loader v4 program account. + LoaderV4Finalize { + /// Writable Loader v4 program account. + program: crate::MdPubkey, + /// Current authority and required signer. + authority: crate::MdPubkey, + /// Read-only next-version program; the same program is accepted. + next_version_program: crate::MdPubkey, + }, + /// Create and initialize a Config Program account with opaque serialized default data. + ConfigCreateAccount { + /// Funding account and required signer. + from: crate::MdPubkey, + /// New Config Program account and required signer. + config_account: crate::MdPubkey, + /// Rent-exempt lamports transferred to the new account. + lamports: u64, + /// Maximum serialized application-data bytes reserved after `ConfigKeys`. + max_config_data_space: u64, + /// Maximum key list used only to size the account allocation. + max_keys: std::vec::Vec, + /// Interface-compatible serialized `T::default()` bytes written by initialization. + initial_data: std::vec::Vec, + }, + /// Store opaque serialized application data and a Config Program signer-key list. + ConfigStore { + /// Writable Config Program account. + config_account: crate::MdPubkey, + /// Whether the Config Program account itself must sign. + config_account_is_signer: bool, + /// Ordered key list serialized with Solana compact-u16 length encoding. + keys: std::vec::Vec, + /// Already serialized application data appended after `ConfigKeys`. + data: std::vec::Vec, + }, + /// Activate a feature account through transfer, allocate and assign instructions. + FeatureActivate { + /// New feature account and required signer. + feature_account: crate::MdPubkey, + /// Funding account and required signer. + funding_account: crate::MdPubkey, + /// Rent-exempt lamports measured by the caller. + lamports: u64, + }, + /// Revoke a feature account whose activation is still pending. + FeatureRevokePendingActivation { + /// Writable feature account and required signer. + feature_account: crate::MdPubkey, + }, + /// Close a Slashing violation report after its runtime retention period. + SlashingCloseViolationReport { + /// Writable violation-report PDA. + report_account: crate::MdPubkey, + /// Writable destination encoded in the retained report state. + destination: crate::MdPubkey, + }, + /// Submit an atomic prefunded duplicate-block proof with Ed25519 verification. + SlashingSubmitDuplicateBlockProof { + /// Read-only account containing the duplicate-block proof bytes. + proof_account: crate::MdPubkey, + /// Expected report PDA derived from node, slot and proof type. + report_account: crate::MdPubkey, + /// Optional transfer signer; the reporter funds the PDA when absent. + funder: std::option::Option, + /// Exact measured rent-exempt lamports transferred to the report PDA. + report_lamports: u64, + /// Byte offset into the proof account. + proof_offset: u64, + /// Slot containing the duplicate block. + slot: u64, + /// Validator identity whose Ed25519 key signed both roots. + node_pubkey: crate::MdPubkey, + /// Reporter credited by the runtime. + reporter: crate::MdPubkey, + /// Destination credited when the report is later closed. + destination: crate::MdPubkey, + /// First 32-byte shred Merkle root. + shred_1_merkle_root: std::vec::Vec, + /// First 64-byte Ed25519 signature. + shred_1_signature: std::vec::Vec, + /// Second 32-byte shred Merkle root. + shred_2_merkle_root: std::vec::Vec, + /// Second 64-byte Ed25519 signature. + shred_2_signature: std::vec::Vec, + }, + /// Verify an inline ZK ElGamal proof with exact official POD size. + ZkElGamalVerifyInline { + /// Proof kind and discriminator. + proof_type: crate::ExSolanaCoreZkElGamalProofType, + /// Exact official POD proof bytes. + proof_data: std::vec::Vec, + /// Optional preallocated context-state target and owner. + context_state: std::option::Option, + }, + /// Verify ZK ElGamal proof bytes stored in a read-only account. + ZkElGamalVerifyFromAccount { + /// Proof kind and discriminator. + proof_type: crate::ExSolanaCoreZkElGamalProofType, + /// Read-only account containing the proof POD bytes. + proof_account: crate::MdPubkey, + /// Byte offset to the proof data inside the account. + proof_offset: u32, + /// Optional preallocated context-state target and owner. + context_state: std::option::Option, + }, + /// Close a ZK ElGamal context-state account and reclaim its lamports. + ZkElGamalCloseContextState { + /// Writable context-state account. + context_state: crate::MdPubkey, + /// Writable lamport destination. + destination: crate::MdPubkey, + /// Context-state authority and required signer. + authority: crate::MdPubkey, + }, +} + +impl crate::ExSolanaCoreOperation { + /// Returns the stable operation code. + pub fn operation_code(&self) -> &'static str { + return match self { + Self::SystemTransfer { .. } => crate::EX_SOLANA_CORE_SYSTEM_TRANSFER_OPERATION, + Self::SystemTransferMany { .. } => crate::EX_SOLANA_CORE_SYSTEM_TRANSFER_MANY_OPERATION, + Self::SystemTransferWithSeed { .. } => { + crate::EX_SOLANA_CORE_SYSTEM_TRANSFER_WITH_SEED_OPERATION + }, + Self::SystemCreateAccount { .. } => { + crate::EX_SOLANA_CORE_SYSTEM_CREATE_ACCOUNT_OPERATION + }, + Self::SystemCreateAccountWithSeed { .. } => { + crate::EX_SOLANA_CORE_SYSTEM_CREATE_ACCOUNT_WITH_SEED_OPERATION + }, + Self::SystemCreateAccountAllowPrefund { .. } => { + crate::EX_SOLANA_CORE_SYSTEM_CREATE_ACCOUNT_ALLOW_PREFUND_OPERATION + }, + Self::SystemAllocate { .. } => crate::EX_SOLANA_CORE_SYSTEM_ALLOCATE_OPERATION, + Self::SystemAllocateWithSeed { .. } => { + crate::EX_SOLANA_CORE_SYSTEM_ALLOCATE_WITH_SEED_OPERATION + }, + Self::SystemAssign { .. } => crate::EX_SOLANA_CORE_SYSTEM_ASSIGN_OPERATION, + Self::SystemAssignWithSeed { .. } => { + crate::EX_SOLANA_CORE_SYSTEM_ASSIGN_WITH_SEED_OPERATION + }, + Self::SystemCreateNonceAccount { .. } => { + crate::EX_SOLANA_CORE_SYSTEM_NONCE_CREATE_OPERATION + }, + Self::SystemCreateNonceAccountWithSeed { .. } => { + crate::EX_SOLANA_CORE_SYSTEM_NONCE_CREATE_WITH_SEED_OPERATION + }, + Self::SystemInitializeNonceAccount { .. } => { + crate::EX_SOLANA_CORE_SYSTEM_NONCE_INITIALIZE_OPERATION + }, + Self::SystemAdvanceNonceAccount { .. } => { + crate::EX_SOLANA_CORE_SYSTEM_NONCE_ADVANCE_OPERATION + }, + Self::SystemAuthorizeNonceAccount { .. } => { + crate::EX_SOLANA_CORE_SYSTEM_NONCE_AUTHORIZE_OPERATION + }, + Self::SystemWithdrawNonceAccount { .. } => { + crate::EX_SOLANA_CORE_SYSTEM_NONCE_WITHDRAW_OPERATION + }, + Self::SystemUpgradeNonceAccount { .. } => { + crate::EX_SOLANA_CORE_SYSTEM_NONCE_UPGRADE_OPERATION + }, + Self::ComputeBudgetRequestHeapFrame { .. } => { + crate::EX_SOLANA_CORE_COMPUTE_BUDGET_REQUEST_HEAP_FRAME_OPERATION + }, + Self::ComputeBudgetSetUnitLimit { .. } => { + crate::EX_SOLANA_CORE_COMPUTE_BUDGET_SET_UNIT_LIMIT_OPERATION + }, + Self::ComputeBudgetSetUnitPrice { .. } => { + crate::EX_SOLANA_CORE_COMPUTE_BUDGET_SET_UNIT_PRICE_OPERATION + }, + Self::ComputeBudgetSetLoadedAccountsDataSizeLimit { .. } => { + crate::EX_SOLANA_CORE_COMPUTE_BUDGET_SET_LOADED_ACCOUNTS_DATA_SIZE_LIMIT_OPERATION + }, + Self::AddressLookupTableCreate { .. } => { + crate::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_CREATE_OPERATION + }, + Self::AddressLookupTableExtend { .. } => { + crate::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_EXTEND_OPERATION + }, + Self::AddressLookupTableFreeze { .. } => { + crate::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_FREEZE_OPERATION + }, + Self::AddressLookupTableDeactivate { .. } => { + crate::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_DEACTIVATE_OPERATION + }, + Self::AddressLookupTableClose { .. } => { + crate::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_CLOSE_OPERATION + }, + Self::Ed25519VerifyInline { .. } => { + crate::EX_SOLANA_CORE_ED25519_VERIFY_INLINE_OPERATION + }, + Self::Ed25519VerifyOffsets { .. } => { + crate::EX_SOLANA_CORE_ED25519_VERIFY_OFFSETS_OPERATION + }, + Self::Secp256k1VerifyInline { .. } => { + crate::EX_SOLANA_CORE_SECP256K1_VERIFY_INLINE_OPERATION + }, + Self::Secp256k1VerifyOffsets { .. } => { + crate::EX_SOLANA_CORE_SECP256K1_VERIFY_OFFSETS_OPERATION + }, + Self::Secp256r1VerifyInline { .. } => { + crate::EX_SOLANA_CORE_SECP256R1_VERIFY_INLINE_OPERATION + }, + Self::Secp256r1VerifyOffsets { .. } => { + crate::EX_SOLANA_CORE_SECP256R1_VERIFY_OFFSETS_OPERATION + }, + Self::StakeInitialize { .. } => crate::EX_SOLANA_CORE_STAKE_INITIALIZE_OPERATION, + Self::StakeInitializeChecked { .. } => { + crate::EX_SOLANA_CORE_STAKE_INITIALIZE_CHECKED_OPERATION + }, + Self::StakeCreateAccount { .. } => crate::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_OPERATION, + Self::StakeCreateAccountWithSeed { .. } => { + crate::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_WITH_SEED_OPERATION + }, + Self::StakeCreateAccountChecked { .. } => { + crate::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_CHECKED_OPERATION + }, + Self::StakeCreateAccountWithSeedChecked { .. } => { + crate::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_WITH_SEED_CHECKED_OPERATION + }, + Self::StakeSplit { .. } => crate::EX_SOLANA_CORE_STAKE_SPLIT_OPERATION, + Self::StakeSplitWithSeed { .. } => { + crate::EX_SOLANA_CORE_STAKE_SPLIT_WITH_SEED_OPERATION + }, + Self::StakeMerge { .. } => crate::EX_SOLANA_CORE_STAKE_MERGE_OPERATION, + Self::StakeCreateAccountAndDelegate { .. } => { + crate::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_AND_DELEGATE_OPERATION + }, + Self::StakeCreateAccountWithSeedAndDelegate { .. } => { + crate::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_WITH_SEED_AND_DELEGATE_OPERATION + }, + Self::StakeAuthorize { .. } => crate::EX_SOLANA_CORE_STAKE_AUTHORIZE_OPERATION, + Self::StakeAuthorizeChecked { .. } => { + crate::EX_SOLANA_CORE_STAKE_AUTHORIZE_CHECKED_OPERATION + }, + Self::StakeAuthorizeWithSeed { .. } => { + crate::EX_SOLANA_CORE_STAKE_AUTHORIZE_WITH_SEED_OPERATION + }, + Self::StakeAuthorizeCheckedWithSeed { .. } => { + crate::EX_SOLANA_CORE_STAKE_AUTHORIZE_CHECKED_WITH_SEED_OPERATION + }, + Self::StakeDelegate { .. } => crate::EX_SOLANA_CORE_STAKE_DELEGATE_OPERATION, + Self::StakeWithdraw { .. } => crate::EX_SOLANA_CORE_STAKE_WITHDRAW_OPERATION, + Self::StakeDeactivate { .. } => crate::EX_SOLANA_CORE_STAKE_DEACTIVATE_OPERATION, + Self::StakeSetLockup { .. } => crate::EX_SOLANA_CORE_STAKE_SET_LOCKUP_OPERATION, + Self::StakeSetLockupChecked { .. } => { + crate::EX_SOLANA_CORE_STAKE_SET_LOCKUP_CHECKED_OPERATION + }, + Self::StakeGetMinimumDelegation => { + crate::EX_SOLANA_CORE_STAKE_GET_MINIMUM_DELEGATION_OPERATION + }, + Self::StakeDeactivateDelinquent { .. } => { + crate::EX_SOLANA_CORE_STAKE_DEACTIVATE_DELINQUENT_OPERATION + }, + Self::StakeMoveStake { .. } => crate::EX_SOLANA_CORE_STAKE_MOVE_STAKE_OPERATION, + Self::StakeMoveLamports { .. } => crate::EX_SOLANA_CORE_STAKE_MOVE_LAMPORTS_OPERATION, + Self::VoteInitializeAccount { .. } => { + crate::EX_SOLANA_CORE_VOTE_INITIALIZE_ACCOUNT_OPERATION + }, + Self::VoteInitializeAccountV2 { .. } => { + crate::EX_SOLANA_CORE_VOTE_INITIALIZE_ACCOUNT_V2_OPERATION + }, + Self::VoteCreateAccount { .. } => crate::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_OPERATION, + Self::VoteCreateAccountWithSeed { .. } => { + crate::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_WITH_SEED_OPERATION + }, + Self::VoteCreateAccountV2 { .. } => { + crate::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_V2_OPERATION + }, + Self::VoteCreateAccountWithSeedV2 { .. } => { + crate::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_WITH_SEED_V2_OPERATION + }, + Self::VoteAuthorize { .. } => crate::EX_SOLANA_CORE_VOTE_AUTHORIZE_OPERATION, + Self::VoteAuthorizeChecked { .. } => { + crate::EX_SOLANA_CORE_VOTE_AUTHORIZE_CHECKED_OPERATION + }, + Self::VoteAuthorizeWithSeed { .. } => { + crate::EX_SOLANA_CORE_VOTE_AUTHORIZE_WITH_SEED_OPERATION + }, + Self::VoteAuthorizeCheckedWithSeed { .. } => { + crate::EX_SOLANA_CORE_VOTE_AUTHORIZE_CHECKED_WITH_SEED_OPERATION + }, + Self::VoteSubmit { .. } => crate::EX_SOLANA_CORE_VOTE_SUBMIT_OPERATION, + Self::VoteSwitch { .. } => crate::EX_SOLANA_CORE_VOTE_SWITCH_OPERATION, + Self::VoteWithdraw { .. } => crate::EX_SOLANA_CORE_VOTE_WITHDRAW_OPERATION, + Self::VoteUpdateValidatorIdentity { .. } => { + crate::EX_SOLANA_CORE_VOTE_UPDATE_VALIDATOR_IDENTITY_OPERATION + }, + Self::VoteUpdateCommission { .. } => { + crate::EX_SOLANA_CORE_VOTE_UPDATE_COMMISSION_OPERATION + }, + Self::VoteUpdateVoteState { .. } => { + crate::EX_SOLANA_CORE_VOTE_UPDATE_VOTE_STATE_OPERATION + }, + Self::VoteUpdateVoteStateSwitch { .. } => { + crate::EX_SOLANA_CORE_VOTE_UPDATE_VOTE_STATE_SWITCH_OPERATION + }, + Self::VoteCompactUpdateVoteState { .. } => { + crate::EX_SOLANA_CORE_VOTE_COMPACT_UPDATE_VOTE_STATE_OPERATION + }, + Self::VoteCompactUpdateVoteStateSwitch { .. } => { + crate::EX_SOLANA_CORE_VOTE_COMPACT_UPDATE_VOTE_STATE_SWITCH_OPERATION + }, + Self::VoteTowerSync { .. } => crate::EX_SOLANA_CORE_VOTE_TOWER_SYNC_OPERATION, + Self::VoteTowerSyncSwitch { .. } => { + crate::EX_SOLANA_CORE_VOTE_TOWER_SYNC_SWITCH_OPERATION + }, + Self::VoteUpdateCommissionCollector { .. } => { + crate::EX_SOLANA_CORE_VOTE_UPDATE_COMMISSION_COLLECTOR_OPERATION + }, + Self::VoteUpdateCommissionBps { .. } => { + crate::EX_SOLANA_CORE_VOTE_UPDATE_COMMISSION_BPS_OPERATION + }, + Self::VoteDepositDelegatorRewards { .. } => { + crate::EX_SOLANA_CORE_VOTE_DEPOSIT_DELEGATOR_REWARDS_OPERATION + }, + Self::LoaderV3CreateBuffer { .. } => { + crate::EX_SOLANA_CORE_LOADER_V3_CREATE_BUFFER_OPERATION + }, + Self::LoaderV3Write { .. } => crate::EX_SOLANA_CORE_LOADER_V3_WRITE_OPERATION, + Self::LoaderV3DeployWithMaxProgramLen { .. } => { + crate::EX_SOLANA_CORE_LOADER_V3_DEPLOY_WITH_MAX_PROGRAM_LEN_OPERATION + }, + Self::LoaderV3Upgrade { .. } => crate::EX_SOLANA_CORE_LOADER_V3_UPGRADE_OPERATION, + Self::LoaderV3SetBufferAuthority { .. } => { + crate::EX_SOLANA_CORE_LOADER_V3_SET_BUFFER_AUTHORITY_OPERATION + }, + Self::LoaderV3SetBufferAuthorityChecked { .. } => { + crate::EX_SOLANA_CORE_LOADER_V3_SET_BUFFER_AUTHORITY_CHECKED_OPERATION + }, + Self::LoaderV3SetUpgradeAuthority { .. } => { + crate::EX_SOLANA_CORE_LOADER_V3_SET_UPGRADE_AUTHORITY_OPERATION + }, + Self::LoaderV3SetUpgradeAuthorityChecked { .. } => { + crate::EX_SOLANA_CORE_LOADER_V3_SET_UPGRADE_AUTHORITY_CHECKED_OPERATION + }, + Self::LoaderV3Close { .. } => crate::EX_SOLANA_CORE_LOADER_V3_CLOSE_OPERATION, + Self::LoaderV3CloseAny { .. } => crate::EX_SOLANA_CORE_LOADER_V3_CLOSE_ANY_OPERATION, + Self::LoaderV3ExtendProgram { .. } => { + crate::EX_SOLANA_CORE_LOADER_V3_EXTEND_PROGRAM_OPERATION + }, + Self::LoaderV4CreateBuffer { .. } => { + crate::EX_SOLANA_CORE_LOADER_V4_CREATE_BUFFER_OPERATION + }, + Self::LoaderV4SetProgramLength { .. } => { + crate::EX_SOLANA_CORE_LOADER_V4_SET_PROGRAM_LENGTH_OPERATION + }, + Self::LoaderV4Write { .. } => crate::EX_SOLANA_CORE_LOADER_V4_WRITE_OPERATION, + Self::LoaderV4Copy { .. } => crate::EX_SOLANA_CORE_LOADER_V4_COPY_OPERATION, + Self::LoaderV4Deploy { .. } => crate::EX_SOLANA_CORE_LOADER_V4_DEPLOY_OPERATION, + Self::LoaderV4DeployFromSource { .. } => { + crate::EX_SOLANA_CORE_LOADER_V4_DEPLOY_FROM_SOURCE_OPERATION + }, + Self::LoaderV4Retract { .. } => crate::EX_SOLANA_CORE_LOADER_V4_RETRACT_OPERATION, + Self::LoaderV4TransferAuthority { .. } => { + crate::EX_SOLANA_CORE_LOADER_V4_TRANSFER_AUTHORITY_OPERATION + }, + Self::LoaderV4Finalize { .. } => crate::EX_SOLANA_CORE_LOADER_V4_FINALIZE_OPERATION, + Self::ConfigCreateAccount { .. } => { + crate::EX_SOLANA_CORE_CONFIG_CREATE_ACCOUNT_OPERATION + }, + Self::ConfigStore { .. } => crate::EX_SOLANA_CORE_CONFIG_STORE_OPERATION, + Self::FeatureActivate { .. } => crate::EX_SOLANA_CORE_FEATURE_ACTIVATE_OPERATION, + Self::FeatureRevokePendingActivation { .. } => { + crate::EX_SOLANA_CORE_FEATURE_REVOKE_PENDING_ACTIVATION_OPERATION + }, + Self::SlashingCloseViolationReport { .. } => { + crate::EX_SOLANA_CORE_SLASHING_CLOSE_VIOLATION_REPORT_OPERATION + }, + Self::SlashingSubmitDuplicateBlockProof { .. } => { + crate::EX_SOLANA_CORE_SLASHING_SUBMIT_DUPLICATE_BLOCK_PROOF_OPERATION + }, + Self::ZkElGamalVerifyInline { .. } => { + crate::EX_SOLANA_CORE_ZK_ELGAMAL_VERIFY_INLINE_OPERATION + }, + Self::ZkElGamalVerifyFromAccount { .. } => { + crate::EX_SOLANA_CORE_ZK_ELGAMAL_VERIFY_FROM_ACCOUNT_OPERATION + }, + Self::ZkElGamalCloseContextState { .. } => { + crate::EX_SOLANA_CORE_ZK_ELGAMAL_CLOSE_CONTEXT_STATE_OPERATION + }, + }; + } + + /// Returns the program id expected for this operation. + pub fn program_id(&self) -> &'static str { + return match self { + Self::SystemTransfer { .. } + | Self::SystemTransferMany { .. } + | Self::SystemTransferWithSeed { .. } + | Self::SystemCreateAccount { .. } + | Self::SystemCreateAccountWithSeed { .. } + | Self::SystemCreateAccountAllowPrefund { .. } + | Self::SystemAllocate { .. } + | Self::SystemAllocateWithSeed { .. } + | Self::SystemAssign { .. } + | Self::SystemAssignWithSeed { .. } + | Self::SystemCreateNonceAccount { .. } + | Self::SystemCreateNonceAccountWithSeed { .. } + | Self::SystemInitializeNonceAccount { .. } + | Self::SystemAdvanceNonceAccount { .. } + | Self::SystemAuthorizeNonceAccount { .. } + | Self::SystemWithdrawNonceAccount { .. } + | Self::SystemUpgradeNonceAccount { .. } => kb_program_ids::SYSTEM_PROGRAM_ID, + Self::ComputeBudgetRequestHeapFrame { .. } + | Self::ComputeBudgetSetUnitLimit { .. } + | Self::ComputeBudgetSetUnitPrice { .. } + | Self::ComputeBudgetSetLoadedAccountsDataSizeLimit { .. } => { + kb_program_ids::COMPUTE_BUDGET_PROGRAM_ID + }, + Self::AddressLookupTableCreate { .. } + | Self::AddressLookupTableExtend { .. } + | Self::AddressLookupTableFreeze { .. } + | Self::AddressLookupTableDeactivate { .. } + | Self::AddressLookupTableClose { .. } => { + kb_program_ids::ADDRESS_LOOKUP_TABLE_PROGRAM_ID + }, + Self::Ed25519VerifyInline { .. } | Self::Ed25519VerifyOffsets { .. } => { + kb_program_ids::ED25519_PROGRAM_ID + }, + Self::Secp256k1VerifyInline { .. } | Self::Secp256k1VerifyOffsets { .. } => { + kb_program_ids::SECP256K1_PROGRAM_ID + }, + Self::Secp256r1VerifyInline { .. } | Self::Secp256r1VerifyOffsets { .. } => { + kb_program_ids::SECP256R1_PROGRAM_ID + }, + Self::StakeInitialize { .. } + | Self::StakeInitializeChecked { .. } + | Self::StakeCreateAccount { .. } + | Self::StakeCreateAccountWithSeed { .. } + | Self::StakeCreateAccountChecked { .. } + | Self::StakeCreateAccountWithSeedChecked { .. } + | Self::StakeSplit { .. } + | Self::StakeSplitWithSeed { .. } + | Self::StakeMerge { .. } + | Self::StakeCreateAccountAndDelegate { .. } + | Self::StakeCreateAccountWithSeedAndDelegate { .. } + | Self::StakeAuthorize { .. } + | Self::StakeAuthorizeChecked { .. } + | Self::StakeAuthorizeWithSeed { .. } + | Self::StakeAuthorizeCheckedWithSeed { .. } + | Self::StakeDelegate { .. } + | Self::StakeWithdraw { .. } + | Self::StakeDeactivate { .. } + | Self::StakeSetLockup { .. } + | Self::StakeSetLockupChecked { .. } + | Self::StakeGetMinimumDelegation + | Self::StakeDeactivateDelinquent { .. } + | Self::StakeMoveStake { .. } + | Self::StakeMoveLamports { .. } => kb_program_ids::STAKE_PROGRAM_ID, + Self::VoteInitializeAccount { .. } + | Self::VoteInitializeAccountV2 { .. } + | Self::VoteCreateAccount { .. } + | Self::VoteCreateAccountWithSeed { .. } + | Self::VoteCreateAccountV2 { .. } + | Self::VoteCreateAccountWithSeedV2 { .. } + | Self::VoteAuthorize { .. } + | Self::VoteAuthorizeChecked { .. } + | Self::VoteAuthorizeWithSeed { .. } + | Self::VoteAuthorizeCheckedWithSeed { .. } + | Self::VoteSubmit { .. } + | Self::VoteSwitch { .. } + | Self::VoteWithdraw { .. } + | Self::VoteUpdateValidatorIdentity { .. } + | Self::VoteUpdateCommission { .. } + | Self::VoteUpdateVoteState { .. } + | Self::VoteUpdateVoteStateSwitch { .. } + | Self::VoteCompactUpdateVoteState { .. } + | Self::VoteCompactUpdateVoteStateSwitch { .. } + | Self::VoteTowerSync { .. } + | Self::VoteTowerSyncSwitch { .. } + | Self::VoteUpdateCommissionCollector { .. } + | Self::VoteUpdateCommissionBps { .. } + | Self::VoteDepositDelegatorRewards { .. } => kb_program_ids::VOTE_PROGRAM_ID, + Self::LoaderV3CreateBuffer { .. } + | Self::LoaderV3Write { .. } + | Self::LoaderV3DeployWithMaxProgramLen { .. } + | Self::LoaderV3Upgrade { .. } + | Self::LoaderV3SetBufferAuthority { .. } + | Self::LoaderV3SetBufferAuthorityChecked { .. } + | Self::LoaderV3SetUpgradeAuthority { .. } + | Self::LoaderV3SetUpgradeAuthorityChecked { .. } + | Self::LoaderV3Close { .. } + | Self::LoaderV3CloseAny { .. } + | Self::LoaderV3ExtendProgram { .. } => { + kb_program_ids::BPF_LOADER_UPGRADEABLE_PROGRAM_ID + }, + Self::LoaderV4CreateBuffer { .. } + | Self::LoaderV4SetProgramLength { .. } + | Self::LoaderV4Write { .. } + | Self::LoaderV4Copy { .. } + | Self::LoaderV4Deploy { .. } + | Self::LoaderV4DeployFromSource { .. } + | Self::LoaderV4Retract { .. } + | Self::LoaderV4TransferAuthority { .. } + | Self::LoaderV4Finalize { .. } => kb_program_ids::LOADER_V4_PROGRAM_ID, + Self::ConfigCreateAccount { .. } | Self::ConfigStore { .. } => { + kb_program_ids::CONFIG_PROGRAM_ID + }, + Self::FeatureActivate { .. } | Self::FeatureRevokePendingActivation { .. } => { + kb_program_ids::FEATURE_PROGRAM_ID + }, + Self::SlashingCloseViolationReport { .. } + | Self::SlashingSubmitDuplicateBlockProof { .. } => kb_program_ids::SLASHING_PROGRAM_ID, + Self::ZkElGamalVerifyInline { .. } + | Self::ZkElGamalVerifyFromAccount { .. } + | Self::ZkElGamalCloseContextState { .. } => { + kb_program_ids::ZK_ELGAMAL_PROOF_PROGRAM_ID + }, + }; + } +} + +/// Complete typed intent accepted by the Solana core executor. +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, TS)] +#[ts( + export, + export_to = "../frontend/ts/bindings/kb_lib/executor/solana/core/intent/ExSolanaCoreExecutionIntent.ts" +)] +pub struct ExSolanaCoreExecutionIntent { + /// Stable caller-provided identifier used for logs and replay correlation. + pub intent_id: std::string::String, + /// Transaction fee payer. + pub fee_payer: crate::MdPubkey, + /// Conservative execution policy. + pub policy: crate::ExApiExecutionPolicy, + /// Typed operation and arguments. + pub operation: crate::ExSolanaCoreOperation, +} + +#[cfg(test)] +mod matrix_tests { + fn required_text<'a>(value: &'a serde_json::Value, field: &str) -> &'a str { + return match value.get(field).and_then(serde_json::Value::as_str) { + std::option::Option::Some(text) if !text.trim().is_empty() => text, + _ => panic!("native execution matrix requires non-empty {field}"), + }; + } + + fn expected_preflight(surface: &str) -> &'static str { + return match surface { + "address_lookup_table" | "config" | "feature" | "slashing" | "zk_elgamal" => "required", + "stake" | "vote" | "loader_v3" | "loader_v4" => "future", + _ => "none", + }; + } + + #[test] + fn native_execution_matrix_matches_compiled_operation_registry() { + let raw = include_str!("../../../../../docs/NATIVE_SOLANA_EXECUTION_MATRIX.json"); + let parsed = match serde_json::from_str::(raw) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => { + panic!("native execution matrix parsing failed: {error}") + }, + }; + assert_eq!( + parsed.get("surface_count").and_then(serde_json::Value::as_u64), + std::option::Option::Some(18) + ); + assert_eq!( + parsed.get("callable_operation_count").and_then(serde_json::Value::as_u64), + std::option::Option::Some(109) + ); + let surfaces = match parsed.get("surfaces").and_then(serde_json::Value::as_array) { + std::option::Option::Some(value) => value, + std::option::Option::None => panic!("native execution matrix surfaces missing"), + }; + assert_eq!(surfaces.len(), 18); + + let registered = kb_program_ids::registered_program_ids() + .iter() + .map(kb_program_ids::ProgramIdEntry::program_id) + .collect::>(); + let mut surface_codes = std::collections::BTreeSet::new(); + let mut matrix_program_ids = std::collections::BTreeSet::new(); + let mut operation_codes = std::collections::BTreeSet::new(); + let mut preflight_counts = std::collections::BTreeMap::from([ + ("none", 0_usize), + ("required", 0_usize), + ("future", 0_usize), + ]); + let mut callable_surface_count = 0_usize; + let mut non_invocable_surface_count = 0_usize; + + for surface in surfaces { + let surface_code = required_text(surface, "surface"); + assert!(surface_codes.insert(surface_code)); + let program_ids = match surface.get("program_ids").and_then(serde_json::Value::as_array) + { + std::option::Option::Some(value) if !value.is_empty() => value, + _ => panic!("native execution matrix program_ids missing"), + }; + for program_id in program_ids { + let value = match program_id.as_str() { + std::option::Option::Some(value) if !value.is_empty() => value, + _ => panic!("native execution matrix program id is not text"), + }; + assert!(registered.contains(value)); + assert!(matrix_program_ids.insert(value)); + } + let operations = match surface.get("operations").and_then(serde_json::Value::as_array) { + std::option::Option::Some(value) => value, + std::option::Option::None => { + panic!("native execution matrix operations missing") + }, + }; + let execution_status = required_text(surface, "execution_status"); + if execution_status == "non_invocable" { + non_invocable_surface_count = non_invocable_surface_count.saturating_add(1); + assert!(operations.is_empty()); + required_text(surface, "official_reason"); + continue; + } + assert_eq!(execution_status, "callable"); + callable_surface_count = callable_surface_count.saturating_add(1); + assert!(!operations.is_empty()); + + for operation in operations { + let operation_code = required_text(operation, "operation_code"); + assert!(operation_codes.insert(operation_code)); + let construction_kind = required_text(operation, "construction_kind"); + assert!(matches!( + construction_kind, + "official_builder" + | "official_builder_or_wire" + | "official_layout" + | "official_wire" + )); + let ui_exposure = required_text(operation, "ui_exposure"); + assert!(matches!(ui_exposure, "hidden" | "safe_subset")); + let preflight = required_text(operation, "stateful_preflight"); + assert_eq!(preflight, expected_preflight(surface_code)); + let count = match preflight_counts.get_mut(preflight) { + std::option::Option::Some(value) => value, + std::option::Option::None => { + panic!("native execution matrix preflight class unsupported") + }, + }; + *count = count.saturating_add(1); + for field in + ["construction_source", "policy", "unit_test_contract", "cluster_validation"] + { + required_text(operation, field); + } + } + } + + assert_eq!(callable_surface_count, 14); + assert_eq!(non_invocable_surface_count, 4); + assert_eq!( + preflight_counts, + std::collections::BTreeMap::from([ + ("none", 27_usize), + ("required", 14_usize), + ("future", 68_usize), + ]) + ); + let compiled = crate::EX_SOLANA_CORE_OPERATION_CODES + .iter() + .copied() + .collect::>(); + assert_eq!(compiled.len(), 109); + assert_eq!(operation_codes, compiled); + assert_eq!(matrix_program_ids.len(), 18); + } +} diff --git a/kb-lib/src/executor/solana/core/loader_v3.rs b/kb-lib/src/executor/solana/core/loader_v3.rs new file mode 100644 index 0000000..54cd3c5 --- /dev/null +++ b/kb-lib/src/executor/solana/core/loader_v3.rs @@ -0,0 +1,1139 @@ +// file: kb-lib/src/executor/solana/core/loader_v3.rs +// version: 1 + +//! Exact client-constructible Loader v3 plans backed by `solana-loader-v3-interface`. + +const SOURCE: &str = "solana-loader-v3-interface@8.0.1"; +const MAX_WRITE_BYTES: usize = 1_024; + +pub(crate) fn executor_solana_core_loader_v3_build_prepared_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, +) -> kb_core::Result { + return match &intent.operation { + crate::ExSolanaCoreOperation::LoaderV3CreateBuffer { + payer, + buffer, + authority, + lamports, + program_len, + } => build_create_buffer_plan( + intent, + fee_payer, + payer, + buffer, + authority, + *lamports, + *program_len, + ), + crate::ExSolanaCoreOperation::LoaderV3Write { buffer, authority, offset, bytes } => { + build_write_plan(intent, fee_payer, buffer, authority, *offset, bytes.as_slice()) + }, + crate::ExSolanaCoreOperation::LoaderV3DeployWithMaxProgramLen { + payer, + program, + buffer, + upgrade_authority, + program_lamports, + max_data_len, + close_buffer, + } => build_deploy_plan( + intent, + fee_payer, + payer, + program, + buffer, + upgrade_authority, + *program_lamports, + *max_data_len, + *close_buffer, + ), + crate::ExSolanaCoreOperation::LoaderV3Upgrade { + program, + buffer, + authority, + spill, + close_buffer, + } => { + build_upgrade_plan(intent, fee_payer, program, buffer, authority, spill, *close_buffer) + }, + crate::ExSolanaCoreOperation::LoaderV3SetBufferAuthority { + buffer, + current_authority, + new_authority, + } => build_set_buffer_authority_plan( + intent, + fee_payer, + buffer, + current_authority, + new_authority, + false, + ), + crate::ExSolanaCoreOperation::LoaderV3SetBufferAuthorityChecked { + buffer, + current_authority, + new_authority, + } => build_set_buffer_authority_plan( + intent, + fee_payer, + buffer, + current_authority, + new_authority, + true, + ), + crate::ExSolanaCoreOperation::LoaderV3SetUpgradeAuthority { + program, + current_authority, + new_authority, + } => build_set_upgrade_authority_plan( + intent, + fee_payer, + program, + current_authority, + new_authority.as_ref(), + false, + ), + crate::ExSolanaCoreOperation::LoaderV3SetUpgradeAuthorityChecked { + program, + current_authority, + new_authority, + } => build_set_upgrade_authority_plan( + intent, + fee_payer, + program, + current_authority, + std::option::Option::Some(new_authority), + true, + ), + crate::ExSolanaCoreOperation::LoaderV3Close { + close_account, + recipient, + authority, + tombstone, + } => build_close_plan(intent, fee_payer, close_account, recipient, authority, *tombstone), + crate::ExSolanaCoreOperation::LoaderV3CloseAny { + close_account, + recipient, + authority, + program, + tombstone, + } => build_close_any_plan( + intent, + fee_payer, + close_account, + recipient, + authority.as_ref(), + program.as_ref(), + *tombstone, + ), + crate::ExSolanaCoreOperation::LoaderV3ExtendProgram { + program, + payer, + additional_bytes, + } => { + build_extend_program_plan(intent, fee_payer, program, payer.as_ref(), *additional_bytes) + }, + _ => std::result::Result::Err(kb_core::Error::new( + "execution_loader_v3_operation_dispatch_invalid", + format!( + "operation {} was dispatched to the Loader v3 builder", + intent.operation.operation_code() + ), + )), + }; +} + +fn build_create_buffer_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + payer: &crate::MdPubkey, + buffer: &crate::MdPubkey, + authority: &crate::MdPubkey, + lamports: u64, + program_len: u64, +) -> kb_core::Result { + if lamports == 0 { + return crate::executor_solana_core_invalid( + "execution_loader_v3_buffer_lamports_zero", + "Loader v3 buffer creation requires positive rent-exempt lamports", + ); + } + let program_len = match bounded_data_length(program_len, "program_len") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let total_len = + solana_loader_v3_interface::state::UpgradeableLoaderState::size_of_buffer(program_len); + if total_len > solana_system_interface::MAX_PERMITTED_DATA_LENGTH as usize { + return crate::executor_solana_core_invalid( + "execution_loader_v3_buffer_space_out_of_range", + format!( + "Loader v3 buffer allocation {total_len} exceeds System Program maximum {}", + solana_system_interface::MAX_PERMITTED_DATA_LENGTH + ), + ); + } + match distinct(payer, buffer, "payer", "buffer") { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let payer_address = match crate::executor_solana_core_parse_pubkey(payer, "payer") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let buffer_address = match crate::executor_solana_core_parse_pubkey(buffer, "buffer") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority_address = match crate::executor_solana_core_parse_pubkey(authority, "authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instructions = match solana_loader_v3_interface::instruction::create_buffer( + &payer_address, + &buffer_address, + &authority_address, + lamports, + program_len, + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => { + return official_error("create_buffer", error); + }, + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_LOADER_V3_CREATE_BUFFER_OPERATION, + instructions, + lamports, + std::option::Option::None, + vec![ + (payer.clone(), std::string::String::from("buffer_payer")), + (buffer.clone(), std::string::String::from("new_buffer_account")), + ], + ); +} + +fn build_write_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + buffer: &crate::MdPubkey, + authority: &crate::MdPubkey, + offset: u32, + bytes: &[u8], +) -> kb_core::Result { + if bytes.is_empty() || bytes.len() > MAX_WRITE_BYTES { + return crate::executor_solana_core_invalid( + "execution_loader_v3_write_size_invalid", + format!("Loader v3 write payload must contain between 1 and {} bytes", MAX_WRITE_BYTES), + ); + } + let byte_len = match u32::try_from(bytes.len()) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_error) => { + return crate::executor_solana_core_invalid( + "execution_loader_v3_write_size_overflow", + "Loader v3 write payload length does not fit u32", + ); + }, + }; + if offset.checked_add(byte_len).is_none() { + return crate::executor_solana_core_invalid( + "execution_loader_v3_write_range_overflow", + "Loader v3 write offset plus payload length exceeds u32", + ); + } + let buffer_address = match crate::executor_solana_core_parse_pubkey(buffer, "buffer") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority_address = match crate::executor_solana_core_parse_pubkey(authority, "authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instruction = solana_loader_v3_interface::instruction::write( + &buffer_address, + &authority_address, + offset, + bytes.to_vec(), + ); + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_LOADER_V3_WRITE_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + vec![(authority.clone(), std::string::String::from("buffer_authority"))], + ); +} + +#[allow(clippy::too_many_arguments)] +fn build_deploy_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + payer: &crate::MdPubkey, + program: &crate::MdPubkey, + buffer: &crate::MdPubkey, + upgrade_authority: &crate::MdPubkey, + program_lamports: u64, + max_data_len: u64, + close_buffer: bool, +) -> kb_core::Result { + if program_lamports == 0 { + return crate::executor_solana_core_invalid( + "execution_loader_v3_program_lamports_zero", + "Loader v3 deployment requires positive rent-exempt program lamports", + ); + } + let max_data_len = match bounded_data_length(max_data_len, "max_data_len") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let programdata_len = + solana_loader_v3_interface::state::UpgradeableLoaderState::size_of_programdata( + max_data_len, + ); + if programdata_len > solana_system_interface::MAX_PERMITTED_DATA_LENGTH as usize { + return crate::executor_solana_core_invalid( + "execution_loader_v3_programdata_space_out_of_range", + format!( + "Loader v3 ProgramData allocation {programdata_len} exceeds System Program maximum {}", + solana_system_interface::MAX_PERMITTED_DATA_LENGTH + ), + ); + } + for (first, second, first_name, second_name) in + [(payer, program, "payer", "program"), (program, buffer, "program", "buffer")] + { + match distinct(first, second, first_name, second_name) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + let payer_address = match crate::executor_solana_core_parse_pubkey(payer, "payer") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let program_address = match crate::executor_solana_core_parse_pubkey(program, "program") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let buffer_address = match crate::executor_solana_core_parse_pubkey(buffer, "buffer") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority_address = + match crate::executor_solana_core_parse_pubkey(upgrade_authority, "upgrade_authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instructions = match solana_loader_v3_interface::instruction::deploy_with_max_program_len( + &payer_address, + &program_address, + &buffer_address, + &authority_address, + program_lamports, + max_data_len, + close_buffer, + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => { + return official_error("deploy_with_max_program_len", error); + }, + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_LOADER_V3_DEPLOY_WITH_MAX_PROGRAM_LEN_OPERATION, + instructions, + program_lamports, + std::option::Option::None, + vec![ + (payer.clone(), std::string::String::from("programdata_payer")), + (program.clone(), std::string::String::from("new_program_account")), + (upgrade_authority.clone(), std::string::String::from("upgrade_authority")), + ], + ); +} + +fn build_upgrade_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + program: &crate::MdPubkey, + buffer: &crate::MdPubkey, + authority: &crate::MdPubkey, + spill: &crate::MdPubkey, + close_buffer: bool, +) -> kb_core::Result { + for (first, second, first_name, second_name) in [ + (program, buffer, "program", "buffer"), + (program, spill, "program", "spill"), + (buffer, spill, "buffer", "spill"), + ] { + match distinct(first, second, first_name, second_name) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + let program_address = match crate::executor_solana_core_parse_pubkey(program, "program") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let buffer_address = match crate::executor_solana_core_parse_pubkey(buffer, "buffer") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority_address = match crate::executor_solana_core_parse_pubkey(authority, "authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let spill_address = match crate::executor_solana_core_parse_pubkey(spill, "spill") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instruction = solana_loader_v3_interface::instruction::upgrade( + &program_address, + &buffer_address, + &authority_address, + &spill_address, + close_buffer, + ); + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_LOADER_V3_UPGRADE_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + vec![(authority.clone(), std::string::String::from("upgrade_authority"))], + ); +} + +fn build_set_buffer_authority_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + buffer: &crate::MdPubkey, + current_authority: &crate::MdPubkey, + new_authority: &crate::MdPubkey, + checked: bool, +) -> kb_core::Result { + match distinct(current_authority, new_authority, "current_authority", "new_authority") { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let buffer_address = match crate::executor_solana_core_parse_pubkey(buffer, "buffer") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let current_address = + match crate::executor_solana_core_parse_pubkey(current_authority, "current_authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let new_address = match crate::executor_solana_core_parse_pubkey(new_authority, "new_authority") + { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let (operation_code, instruction, signer_roles) = if checked { + ( + crate::EX_SOLANA_CORE_LOADER_V3_SET_BUFFER_AUTHORITY_CHECKED_OPERATION, + solana_loader_v3_interface::instruction::set_buffer_authority_checked( + &buffer_address, + ¤t_address, + &new_address, + ), + vec![ + (current_authority.clone(), std::string::String::from("current_buffer_authority")), + (new_authority.clone(), std::string::String::from("new_buffer_authority")), + ], + ) + } else { + ( + crate::EX_SOLANA_CORE_LOADER_V3_SET_BUFFER_AUTHORITY_OPERATION, + solana_loader_v3_interface::instruction::set_buffer_authority( + &buffer_address, + ¤t_address, + &new_address, + ), + vec![( + current_authority.clone(), + std::string::String::from("current_buffer_authority"), + )], + ) + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + operation_code, + vec![instruction], + 0, + std::option::Option::None, + signer_roles, + ); +} + +fn build_set_upgrade_authority_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + program: &crate::MdPubkey, + current_authority: &crate::MdPubkey, + new_authority: std::option::Option<&crate::MdPubkey>, + checked: bool, +) -> kb_core::Result { + if checked && new_authority.is_none() { + return crate::executor_solana_core_invalid( + "execution_loader_v3_checked_authority_missing", + "checked Loader v3 authority changes require a new authority", + ); + } + if let std::option::Option::Some(new_authority) = new_authority { + match distinct(current_authority, new_authority, "current_authority", "new_authority") { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + let program_address = match crate::executor_solana_core_parse_pubkey(program, "program") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let current_address = + match crate::executor_solana_core_parse_pubkey(current_authority, "current_authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let new_address = match new_authority { + std::option::Option::Some(value) => { + match crate::executor_solana_core_parse_pubkey(value, "new_authority") { + std::result::Result::Ok(parsed) => std::option::Option::Some(parsed), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + }, + std::option::Option::None => std::option::Option::None, + }; + let (operation_code, instruction, signer_roles) = if checked { + let new_address = match new_address.as_ref() { + std::option::Option::Some(value) => value, + std::option::Option::None => { + return crate::executor_solana_core_invalid( + "execution_loader_v3_checked_authority_missing", + "checked Loader v3 authority changes require a new authority", + ); + }, + }; + let new_authority = match new_authority { + std::option::Option::Some(value) => value, + std::option::Option::None => { + return crate::executor_solana_core_invalid( + "execution_loader_v3_checked_authority_missing", + "checked Loader v3 authority changes require a new authority", + ); + }, + }; + ( + crate::EX_SOLANA_CORE_LOADER_V3_SET_UPGRADE_AUTHORITY_CHECKED_OPERATION, + solana_loader_v3_interface::instruction::set_upgrade_authority_checked( + &program_address, + ¤t_address, + new_address, + ), + vec![ + ( + current_authority.clone(), + std::string::String::from("current_upgrade_authority"), + ), + (new_authority.clone(), std::string::String::from("new_upgrade_authority")), + ], + ) + } else { + ( + crate::EX_SOLANA_CORE_LOADER_V3_SET_UPGRADE_AUTHORITY_OPERATION, + solana_loader_v3_interface::instruction::set_upgrade_authority( + &program_address, + ¤t_address, + new_address.as_ref(), + ), + vec![( + current_authority.clone(), + std::string::String::from("current_upgrade_authority"), + )], + ) + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + operation_code, + vec![instruction], + 0, + std::option::Option::None, + signer_roles, + ); +} + +fn build_close_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + close_account: &crate::MdPubkey, + recipient: &crate::MdPubkey, + authority: &crate::MdPubkey, + tombstone: bool, +) -> kb_core::Result { + match distinct(close_account, recipient, "close_account", "recipient") { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let close_address = + match crate::executor_solana_core_parse_pubkey(close_account, "close_account") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let recipient_address = match crate::executor_solana_core_parse_pubkey(recipient, "recipient") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority_address = match crate::executor_solana_core_parse_pubkey(authority, "authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instruction = solana_loader_v3_interface::instruction::close( + &close_address, + &recipient_address, + &authority_address, + tombstone, + ); + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_LOADER_V3_CLOSE_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + vec![(authority.clone(), std::string::String::from("close_authority"))], + ); +} + +fn build_close_any_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + close_account: &crate::MdPubkey, + recipient: &crate::MdPubkey, + authority: std::option::Option<&crate::MdPubkey>, + program: std::option::Option<&crate::MdPubkey>, + tombstone: bool, +) -> kb_core::Result { + match distinct(close_account, recipient, "close_account", "recipient") { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + if let std::option::Option::Some(program) = program { + match distinct(close_account, program, "close_account", "program") { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + let close_address = + match crate::executor_solana_core_parse_pubkey(close_account, "close_account") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let recipient_address = match crate::executor_solana_core_parse_pubkey(recipient, "recipient") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority_address = match authority { + std::option::Option::Some(value) => { + match crate::executor_solana_core_parse_pubkey(value, "authority") { + std::result::Result::Ok(parsed) => std::option::Option::Some(parsed), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + }, + std::option::Option::None => std::option::Option::None, + }; + let program_address = match program { + std::option::Option::Some(value) => { + match crate::executor_solana_core_parse_pubkey(value, "program") { + std::result::Result::Ok(parsed) => std::option::Option::Some(parsed), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + }, + std::option::Option::None => std::option::Option::None, + }; + let instruction = solana_loader_v3_interface::instruction::close_any( + &close_address, + &recipient_address, + authority_address.as_ref(), + program_address.as_ref(), + tombstone, + ); + let signer_roles = match authority { + std::option::Option::Some(value) => { + vec![(value.clone(), std::string::String::from("close_authority"))] + }, + std::option::Option::None => std::vec::Vec::new(), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_LOADER_V3_CLOSE_ANY_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + signer_roles, + ); +} + +fn build_extend_program_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + program: &crate::MdPubkey, + payer: std::option::Option<&crate::MdPubkey>, + additional_bytes: u32, +) -> kb_core::Result { + if additional_bytes < solana_loader_v3_interface::instruction::MINIMUM_EXTEND_PROGRAM_BYTES { + return crate::executor_solana_core_invalid( + "execution_loader_v3_extend_below_current_minimum", + format!( + "Loader v3 program extension requires at least {} bytes without stateful proof of the near-maximum exception", + solana_loader_v3_interface::instruction::MINIMUM_EXTEND_PROGRAM_BYTES + ), + ); + } + if let std::option::Option::Some(payer) = payer { + match distinct(program, payer, "program", "payer") { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + let program_address = match crate::executor_solana_core_parse_pubkey(program, "program") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let payer_address = match payer { + std::option::Option::Some(value) => { + match crate::executor_solana_core_parse_pubkey(value, "payer") { + std::result::Result::Ok(parsed) => std::option::Option::Some(parsed), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + }, + std::option::Option::None => std::option::Option::None, + }; + let instruction = solana_loader_v3_interface::instruction::extend_program( + &program_address, + payer_address.as_ref(), + additional_bytes, + ); + let signer_roles = match payer { + std::option::Option::Some(value) => { + vec![(value.clone(), std::string::String::from("programdata_rent_payer"))] + }, + std::option::Option::None => std::vec::Vec::new(), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_LOADER_V3_EXTEND_PROGRAM_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + signer_roles, + ); +} + +fn bounded_data_length(value: u64, field_name: &str) -> kb_core::Result { + if value == 0 || value > solana_system_interface::MAX_PERMITTED_DATA_LENGTH { + return crate::executor_solana_core_invalid( + "execution_loader_v3_data_length_out_of_range", + format!( + "Loader v3 {field_name} must be between 1 and {} bytes", + solana_system_interface::MAX_PERMITTED_DATA_LENGTH + ), + ); + } + return match usize::try_from(value) { + std::result::Result::Ok(parsed) => std::result::Result::Ok(parsed), + std::result::Result::Err(_error) => crate::executor_solana_core_invalid( + "execution_loader_v3_data_length_overflow", + format!("Loader v3 {field_name} does not fit usize"), + ), + }; +} + +fn distinct( + first: &crate::MdPubkey, + second: &crate::MdPubkey, + first_name: &str, + second_name: &str, +) -> kb_core::Result<()> { + return crate::executor_solana_core_validate_distinct_pubkeys( + first, + second, + "execution_loader_v3_accounts_not_distinct", + format!("Loader v3 {first_name} and {second_name} accounts must differ"), + ); +} + +fn official_error( + operation: &str, + error: solana_instruction::error::InstructionError, +) -> kb_core::Result { + return crate::executor_solana_core_invalid( + "execution_loader_v3_official_builder_failed", + format!("official {operation} builder from {} failed: {error:?}", SOURCE), + ); +} + +#[cfg(test)] +mod tests { + fn model(value: solana_pubkey::Pubkey) -> crate::MdPubkey { + return crate::MdPubkey(value.to_string()); + } + + fn policy(signers: &[solana_pubkey::Pubkey]) -> crate::ExApiExecutionPolicy { + return crate::ExApiExecutionPolicy { + cost_limit: crate::ExApiExecutionCostLimit { + max_spend_lamports: std::option::Option::Some(20_000_000), + max_fee_lamports: std::option::Option::Some(1_000_000), + max_compute_unit_price_micro_lamports: std::option::Option::Some(100), + }, + authorized_signers: signers.iter().map(|value| return model(*value)).collect(), + ..crate::ExApiExecutionPolicy::default() + }; + } + + fn intent( + operation: crate::ExSolanaCoreOperation, + fee_payer: solana_pubkey::Pubkey, + signers: &[solana_pubkey::Pubkey], + ) -> crate::ExSolanaCoreExecutionIntent { + return crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("loader-v3-test"), + fee_payer: model(fee_payer), + policy: policy(signers), + operation, + }; + } + + fn build( + intent: &crate::ExSolanaCoreExecutionIntent, + ) -> kb_core::Result { + let executor = crate::ExSolanaCoreExecutor; + return crate::ExApiTypedInstructionExecutor::build_prepared_plan(&executor, intent); + } + + fn assert_plan_matches( + plan: &crate::ExApiPreparedExecutionPlan, + expected: &[solana_instruction::Instruction], + ) { + assert_eq!(plan.instructions.len(), expected.len()); + for (actual, expected) in plan.instructions.iter().zip(expected.iter()) { + assert_eq!(actual.program_id.0, expected.program_id.to_string()); + assert_eq!(actual.data, expected.data); + assert_eq!(actual.accounts.len(), expected.accounts.len()); + for (actual_account, expected_account) in + actual.accounts.iter().zip(expected.accounts.iter()) + { + assert_eq!(actual_account.pubkey.0, expected_account.pubkey.to_string()); + assert_eq!(actual_account.is_signer, expected_account.is_signer); + assert_eq!(actual_account.is_writable, expected_account.is_writable); + } + } + } + + fn build_or_panic( + intent: &crate::ExSolanaCoreExecutionIntent, + ) -> crate::ExApiPreparedExecutionPlan { + return match build(intent) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => panic!("Loader v3 test plan failed: {error}"), + }; + } + + fn official_or_panic( + result: std::result::Result< + std::vec::Vec, + solana_instruction::error::InstructionError, + >, + ) -> std::vec::Vec { + return match result { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => { + panic!("official Loader v3 builder failed: {error:?}") + }, + }; + } + + #[test] + fn create_buffer_matches_official_compound_plan_and_spend() { + let payer = solana_pubkey::Pubkey::new_unique(); + let buffer = solana_pubkey::Pubkey::new_unique(); + let authority = solana_pubkey::Pubkey::new_unique(); + let intent = intent( + crate::ExSolanaCoreOperation::LoaderV3CreateBuffer { + payer: model(payer), + buffer: model(buffer), + authority: model(authority), + lamports: 1_000_000, + program_len: 4096, + }, + payer, + &[payer, buffer], + ); + let plan = build_or_panic(&intent); + let expected = official_or_panic(solana_loader_v3_interface::instruction::create_buffer( + &payer, &buffer, &authority, 1_000_000, 4096, + )); + assert_plan_matches(&plan, expected.as_slice()); + assert_eq!(plan.requested_spend_lamports, 1_000_000); + assert_eq!(plan.required_signers.len(), 2); + } + + #[test] + fn write_matches_official_builder_and_signer() { + let fee_payer = solana_pubkey::Pubkey::new_unique(); + let buffer = solana_pubkey::Pubkey::new_unique(); + let authority = solana_pubkey::Pubkey::new_unique(); + let bytes = vec![1, 2, 3, 4]; + let intent = intent( + crate::ExSolanaCoreOperation::LoaderV3Write { + buffer: model(buffer), + authority: model(authority), + offset: 32, + bytes: bytes.clone(), + }, + fee_payer, + &[fee_payer, authority], + ); + let plan = build_or_panic(&intent); + let expected = + solana_loader_v3_interface::instruction::write(&buffer, &authority, 32, bytes); + assert_plan_matches(&plan, std::slice::from_ref(&expected)); + assert_eq!(plan.required_signers.len(), 2); + } + + #[test] + fn deploy_and_upgrade_match_official_builders() { + let payer = solana_pubkey::Pubkey::new_unique(); + let program = solana_pubkey::Pubkey::new_unique(); + let buffer = solana_pubkey::Pubkey::new_unique(); + let authority = solana_pubkey::Pubkey::new_unique(); + let spill = solana_pubkey::Pubkey::new_unique(); + let deploy_intent = intent( + crate::ExSolanaCoreOperation::LoaderV3DeployWithMaxProgramLen { + payer: model(payer), + program: model(program), + buffer: model(buffer), + upgrade_authority: model(authority), + program_lamports: 2_000_000, + max_data_len: 65_536, + close_buffer: true, + }, + payer, + &[payer, program, authority], + ); + let deploy_plan = build_or_panic(&deploy_intent); + let expected_deploy = official_or_panic( + solana_loader_v3_interface::instruction::deploy_with_max_program_len( + &payer, &program, &buffer, &authority, 2_000_000, 65_536, true, + ), + ); + assert_plan_matches(&deploy_plan, expected_deploy.as_slice()); + assert_eq!(deploy_plan.requested_spend_lamports, 2_000_000); + + let upgrade_intent = intent( + crate::ExSolanaCoreOperation::LoaderV3Upgrade { + program: model(program), + buffer: model(buffer), + authority: model(authority), + spill: model(spill), + close_buffer: false, + }, + payer, + &[payer, authority], + ); + let upgrade_plan = build_or_panic(&upgrade_intent); + let expected_upgrade = solana_loader_v3_interface::instruction::upgrade( + &program, &buffer, &authority, &spill, false, + ); + assert_plan_matches(&upgrade_plan, std::slice::from_ref(&expected_upgrade)); + } + + #[test] + fn authority_helpers_match_official_accounts_and_signers() { + let fee_payer = solana_pubkey::Pubkey::new_unique(); + let target = solana_pubkey::Pubkey::new_unique(); + let current = solana_pubkey::Pubkey::new_unique(); + let new = solana_pubkey::Pubkey::new_unique(); + let fixtures = [ + ( + crate::ExSolanaCoreOperation::LoaderV3SetBufferAuthority { + buffer: model(target), + current_authority: model(current), + new_authority: model(new), + }, + solana_loader_v3_interface::instruction::set_buffer_authority( + &target, ¤t, &new, + ), + vec![fee_payer, current], + ), + ( + crate::ExSolanaCoreOperation::LoaderV3SetBufferAuthorityChecked { + buffer: model(target), + current_authority: model(current), + new_authority: model(new), + }, + solana_loader_v3_interface::instruction::set_buffer_authority_checked( + &target, ¤t, &new, + ), + vec![fee_payer, current, new], + ), + ( + crate::ExSolanaCoreOperation::LoaderV3SetUpgradeAuthority { + program: model(target), + current_authority: model(current), + new_authority: std::option::Option::Some(model(new)), + }, + solana_loader_v3_interface::instruction::set_upgrade_authority( + &target, + ¤t, + std::option::Option::Some(&new), + ), + vec![fee_payer, current], + ), + ( + crate::ExSolanaCoreOperation::LoaderV3SetUpgradeAuthorityChecked { + program: model(target), + current_authority: model(current), + new_authority: model(new), + }, + solana_loader_v3_interface::instruction::set_upgrade_authority_checked( + &target, ¤t, &new, + ), + vec![fee_payer, current, new], + ), + ]; + for (operation, expected, signers) in fixtures { + let intent = intent(operation, fee_payer, signers.as_slice()); + let plan = build_or_panic(&intent); + assert_plan_matches(&plan, std::slice::from_ref(&expected)); + } + } + + #[test] + fn close_and_extend_match_official_builders() { + let fee_payer = solana_pubkey::Pubkey::new_unique(); + let close_account = solana_pubkey::Pubkey::new_unique(); + let recipient = solana_pubkey::Pubkey::new_unique(); + let authority = solana_pubkey::Pubkey::new_unique(); + let program = solana_pubkey::Pubkey::new_unique(); + let close_intent = intent( + crate::ExSolanaCoreOperation::LoaderV3Close { + close_account: model(close_account), + recipient: model(recipient), + authority: model(authority), + tombstone: false, + }, + fee_payer, + &[fee_payer, authority], + ); + let close_plan = build_or_panic(&close_intent); + let expected_close = solana_loader_v3_interface::instruction::close( + &close_account, + &recipient, + &authority, + false, + ); + assert_plan_matches(&close_plan, std::slice::from_ref(&expected_close)); + + let close_any_intent = intent( + crate::ExSolanaCoreOperation::LoaderV3CloseAny { + close_account: model(close_account), + recipient: model(recipient), + authority: std::option::Option::Some(model(authority)), + program: std::option::Option::Some(model(program)), + tombstone: true, + }, + fee_payer, + &[fee_payer, authority], + ); + let close_any_plan = build_or_panic(&close_any_intent); + let expected_close_any = solana_loader_v3_interface::instruction::close_any( + &close_account, + &recipient, + std::option::Option::Some(&authority), + std::option::Option::Some(&program), + true, + ); + assert_plan_matches(&close_any_plan, std::slice::from_ref(&expected_close_any)); + + let extend_intent = intent( + crate::ExSolanaCoreOperation::LoaderV3ExtendProgram { + program: model(program), + payer: std::option::Option::Some(model(fee_payer)), + additional_bytes: + solana_loader_v3_interface::instruction::MINIMUM_EXTEND_PROGRAM_BYTES, + }, + fee_payer, + &[fee_payer], + ); + let extend_plan = build_or_panic(&extend_intent); + let expected_extend = solana_loader_v3_interface::instruction::extend_program( + &program, + std::option::Option::Some(&fee_payer), + solana_loader_v3_interface::instruction::MINIMUM_EXTEND_PROGRAM_BYTES, + ); + assert_plan_matches(&extend_plan, std::slice::from_ref(&expected_extend)); + } + + #[test] + fn malformed_loader_contracts_are_rejected() { + let payer = solana_pubkey::Pubkey::new_unique(); + let other = solana_pubkey::Pubkey::new_unique(); + let empty_write = intent( + crate::ExSolanaCoreOperation::LoaderV3Write { + buffer: model(other), + authority: model(payer), + offset: 0, + bytes: std::vec::Vec::new(), + }, + payer, + &[payer], + ); + assert!(build(&empty_write).is_err()); + let same_accounts = intent( + crate::ExSolanaCoreOperation::LoaderV3CreateBuffer { + payer: model(payer), + buffer: model(payer), + authority: model(other), + lamports: 1, + program_len: 1, + }, + payer, + &[payer], + ); + assert!(build(&same_accounts).is_err()); + let same_close_accounts = intent( + crate::ExSolanaCoreOperation::LoaderV3CloseAny { + close_account: model(other), + recipient: model(other), + authority: std::option::Option::None, + program: std::option::Option::None, + tombstone: true, + }, + payer, + &[payer], + ); + assert!(build(&same_close_accounts).is_err()); + let zero_extend = intent( + crate::ExSolanaCoreOperation::LoaderV3ExtendProgram { + program: model(other), + payer: std::option::Option::None, + additional_bytes: 0, + }, + payer, + &[payer], + ); + assert!(build(&zero_extend).is_err()); + } +} diff --git a/kb-lib/src/executor/solana/core/loader_v4.rs b/kb-lib/src/executor/solana/core/loader_v4.rs new file mode 100644 index 0000000..2059764 --- /dev/null +++ b/kb-lib/src/executor/solana/core/loader_v4.rs @@ -0,0 +1,890 @@ +// file: kb-lib/src/executor/solana/core/loader_v4.rs +// version: 1 + +//! Exact Loader v4 client plans without activating the interface's `bincode` helpers. + +#[cfg(test)] +const SOURCE: &str = "solana-loader-v4-interface@3.1.0"; +const MAX_WRITE_BYTES: usize = 1_024; +const WRITE_TAG: u32 = 0; +const COPY_TAG: u32 = 1; +const SET_PROGRAM_LENGTH_TAG: u32 = 2; +const DEPLOY_TAG: u32 = 3; +const RETRACT_TAG: u32 = 4; +const TRANSFER_AUTHORITY_TAG: u32 = 5; +const FINALIZE_TAG: u32 = 6; + +pub(crate) fn executor_solana_core_loader_v4_build_prepared_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, +) -> kb_core::Result { + return match &intent.operation { + crate::ExSolanaCoreOperation::LoaderV4CreateBuffer { + payer, + buffer, + authority, + recipient, + lamports, + new_size, + } => build_create_buffer_plan( + intent, fee_payer, payer, buffer, authority, recipient, *lamports, *new_size, + ), + crate::ExSolanaCoreOperation::LoaderV4SetProgramLength { + program, + authority, + recipient, + new_size, + } => build_set_program_length_plan( + intent, fee_payer, program, authority, recipient, *new_size, + ), + crate::ExSolanaCoreOperation::LoaderV4Write { program, authority, offset, bytes } => { + build_write_plan(intent, fee_payer, program, authority, *offset, bytes.as_slice()) + }, + crate::ExSolanaCoreOperation::LoaderV4Copy { + program, + authority, + source_program, + destination_offset, + source_offset, + length, + } => build_copy_plan( + intent, + fee_payer, + program, + authority, + source_program, + *destination_offset, + *source_offset, + *length, + ), + crate::ExSolanaCoreOperation::LoaderV4Deploy { program, authority } => { + build_authority_unit_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_LOADER_V4_DEPLOY_OPERATION, + program, + authority, + DEPLOY_TAG, + ) + }, + crate::ExSolanaCoreOperation::LoaderV4DeployFromSource { + program, + authority, + source_program, + } => build_deploy_from_source_plan(intent, fee_payer, program, authority, source_program), + crate::ExSolanaCoreOperation::LoaderV4Retract { program, authority } => { + build_authority_unit_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_LOADER_V4_RETRACT_OPERATION, + program, + authority, + RETRACT_TAG, + ) + }, + crate::ExSolanaCoreOperation::LoaderV4TransferAuthority { + program, + current_authority, + new_authority, + } => build_transfer_authority_plan( + intent, + fee_payer, + program, + current_authority, + new_authority, + ), + crate::ExSolanaCoreOperation::LoaderV4Finalize { + program, + authority, + next_version_program, + } => build_finalize_plan(intent, fee_payer, program, authority, next_version_program), + _ => std::result::Result::Err(kb_core::Error::new( + "execution_loader_v4_operation_dispatch_invalid", + format!( + "operation {} was dispatched to the Loader v4 builder", + intent.operation.operation_code() + ), + )), + }; +} + +fn build_create_buffer_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + payer: &crate::MdPubkey, + buffer: &crate::MdPubkey, + authority: &crate::MdPubkey, + recipient: &crate::MdPubkey, + lamports: u64, + new_size: u32, +) -> kb_core::Result { + if lamports == 0 { + return crate::executor_solana_core_invalid( + "execution_loader_v4_buffer_lamports_zero", + "Loader v4 buffer creation requires positive lamports", + ); + } + if new_size == 0 { + return crate::executor_solana_core_invalid( + "execution_loader_v4_buffer_size_zero", + "Loader v4 buffer creation requires a positive initial size", + ); + } + match validate_size(new_size) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match distinct(payer, buffer, "payer", "buffer") { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match distinct(buffer, recipient, "buffer", "recipient") { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let payer_address = match crate::executor_solana_core_parse_pubkey(payer, "payer") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let buffer_address = match crate::executor_solana_core_parse_pubkey(buffer, "buffer") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority_address = match crate::executor_solana_core_parse_pubkey(authority, "authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let recipient_address = match crate::executor_solana_core_parse_pubkey(recipient, "recipient") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instructions = vec![ + solana_system_interface::instruction::create_account( + &payer_address, + &buffer_address, + lamports, + 0, + &solana_sdk_ids::loader_v4::ID, + ), + set_program_length_instruction( + &buffer_address, + &authority_address, + &recipient_address, + new_size, + ), + ]; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_LOADER_V4_CREATE_BUFFER_OPERATION, + instructions, + lamports, + std::option::Option::None, + vec![ + (payer.clone(), std::string::String::from("buffer_payer")), + (buffer.clone(), std::string::String::from("new_buffer_account")), + (authority.clone(), std::string::String::from("program_authority")), + ], + ); +} + +fn build_set_program_length_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + program: &crate::MdPubkey, + authority: &crate::MdPubkey, + recipient: &crate::MdPubkey, + new_size: u32, +) -> kb_core::Result { + match validate_size(new_size) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match distinct(program, recipient, "program", "recipient") { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let program_address = match crate::executor_solana_core_parse_pubkey(program, "program") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority_address = match crate::executor_solana_core_parse_pubkey(authority, "authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let recipient_address = match crate::executor_solana_core_parse_pubkey(recipient, "recipient") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instruction = set_program_length_instruction( + &program_address, + &authority_address, + &recipient_address, + new_size, + ); + return finalize_single( + intent, + fee_payer, + crate::EX_SOLANA_CORE_LOADER_V4_SET_PROGRAM_LENGTH_OPERATION, + instruction, + vec![(authority.clone(), std::string::String::from("program_authority"))], + ); +} + +fn build_write_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + program: &crate::MdPubkey, + authority: &crate::MdPubkey, + offset: u32, + bytes: &[u8], +) -> kb_core::Result { + if bytes.is_empty() || bytes.len() > MAX_WRITE_BYTES { + return crate::executor_solana_core_invalid( + "execution_loader_v4_write_size_invalid", + format!("Loader v4 write payload must contain between 1 and {} bytes", MAX_WRITE_BYTES), + ); + } + let byte_length = match u32::try_from(bytes.len()) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_error) => { + return crate::executor_solana_core_invalid( + "execution_loader_v4_write_size_overflow", + "Loader v4 write payload length does not fit u32", + ); + }, + }; + if offset.checked_add(byte_length).is_none() { + return crate::executor_solana_core_invalid( + "execution_loader_v4_write_range_overflow", + "Loader v4 write offset plus payload length exceeds u32", + ); + } + let program_address = match crate::executor_solana_core_parse_pubkey(program, "program") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority_address = match crate::executor_solana_core_parse_pubkey(authority, "authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instruction = solana_instruction::Instruction { + program_id: solana_sdk_ids::loader_v4::ID, + accounts: vec![ + solana_instruction::AccountMeta::new(program_address, false), + solana_instruction::AccountMeta::new_readonly(authority_address, true), + ], + data: write_data(offset, bytes), + }; + return finalize_single( + intent, + fee_payer, + crate::EX_SOLANA_CORE_LOADER_V4_WRITE_OPERATION, + instruction, + vec![(authority.clone(), std::string::String::from("program_authority"))], + ); +} + +fn build_copy_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + program: &crate::MdPubkey, + authority: &crate::MdPubkey, + source_program: &crate::MdPubkey, + destination_offset: u32, + source_offset: u32, + length: u32, +) -> kb_core::Result { + if length == 0 { + return crate::executor_solana_core_invalid( + "execution_loader_v4_copy_length_zero", + "Loader v4 copy requires a positive byte length", + ); + } + if destination_offset.checked_add(length).is_none() + || source_offset.checked_add(length).is_none() + { + return crate::executor_solana_core_invalid( + "execution_loader_v4_copy_range_overflow", + "Loader v4 copy offset plus length exceeds u32", + ); + } + match distinct(program, source_program, "program", "source_program") { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let program_address = match crate::executor_solana_core_parse_pubkey(program, "program") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority_address = match crate::executor_solana_core_parse_pubkey(authority, "authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let source_address = + match crate::executor_solana_core_parse_pubkey(source_program, "source_program") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instruction = solana_instruction::Instruction { + program_id: solana_sdk_ids::loader_v4::ID, + accounts: vec![ + solana_instruction::AccountMeta::new(program_address, false), + solana_instruction::AccountMeta::new_readonly(authority_address, true), + solana_instruction::AccountMeta::new_readonly(source_address, false), + ], + data: copy_data(destination_offset, source_offset, length), + }; + return finalize_single( + intent, + fee_payer, + crate::EX_SOLANA_CORE_LOADER_V4_COPY_OPERATION, + instruction, + vec![(authority.clone(), std::string::String::from("program_authority"))], + ); +} + +fn build_authority_unit_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + operation_code: &str, + program: &crate::MdPubkey, + authority: &crate::MdPubkey, + tag: u32, +) -> kb_core::Result { + let program_address = match crate::executor_solana_core_parse_pubkey(program, "program") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority_address = match crate::executor_solana_core_parse_pubkey(authority, "authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instruction = solana_instruction::Instruction { + program_id: solana_sdk_ids::loader_v4::ID, + accounts: vec![ + solana_instruction::AccountMeta::new(program_address, false), + solana_instruction::AccountMeta::new_readonly(authority_address, true), + ], + data: tag.to_le_bytes().to_vec(), + }; + return finalize_single( + intent, + fee_payer, + operation_code, + instruction, + vec![(authority.clone(), std::string::String::from("program_authority"))], + ); +} + +fn build_deploy_from_source_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + program: &crate::MdPubkey, + authority: &crate::MdPubkey, + source_program: &crate::MdPubkey, +) -> kb_core::Result { + match distinct(program, source_program, "program", "source_program") { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let program_address = match crate::executor_solana_core_parse_pubkey(program, "program") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority_address = match crate::executor_solana_core_parse_pubkey(authority, "authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let source_address = + match crate::executor_solana_core_parse_pubkey(source_program, "source_program") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instruction = solana_instruction::Instruction { + program_id: solana_sdk_ids::loader_v4::ID, + accounts: vec![ + solana_instruction::AccountMeta::new(program_address, false), + solana_instruction::AccountMeta::new_readonly(authority_address, true), + solana_instruction::AccountMeta::new(source_address, false), + ], + data: DEPLOY_TAG.to_le_bytes().to_vec(), + }; + return finalize_single( + intent, + fee_payer, + crate::EX_SOLANA_CORE_LOADER_V4_DEPLOY_FROM_SOURCE_OPERATION, + instruction, + vec![(authority.clone(), std::string::String::from("program_authority"))], + ); +} + +fn build_transfer_authority_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + program: &crate::MdPubkey, + current_authority: &crate::MdPubkey, + new_authority: &crate::MdPubkey, +) -> kb_core::Result { + match distinct(current_authority, new_authority, "current_authority", "new_authority") { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let program_address = match crate::executor_solana_core_parse_pubkey(program, "program") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let current_address = + match crate::executor_solana_core_parse_pubkey(current_authority, "current_authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let new_address = match crate::executor_solana_core_parse_pubkey(new_authority, "new_authority") + { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instruction = solana_instruction::Instruction { + program_id: solana_sdk_ids::loader_v4::ID, + accounts: vec![ + solana_instruction::AccountMeta::new(program_address, false), + solana_instruction::AccountMeta::new_readonly(current_address, true), + solana_instruction::AccountMeta::new_readonly(new_address, true), + ], + data: TRANSFER_AUTHORITY_TAG.to_le_bytes().to_vec(), + }; + return finalize_single( + intent, + fee_payer, + crate::EX_SOLANA_CORE_LOADER_V4_TRANSFER_AUTHORITY_OPERATION, + instruction, + vec![ + ( + current_authority.clone(), + std::string::String::from("current_program_authority"), + ), + (new_authority.clone(), std::string::String::from("new_program_authority")), + ], + ); +} + +fn build_finalize_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + program: &crate::MdPubkey, + authority: &crate::MdPubkey, + next_version_program: &crate::MdPubkey, +) -> kb_core::Result { + let program_address = match crate::executor_solana_core_parse_pubkey(program, "program") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority_address = match crate::executor_solana_core_parse_pubkey(authority, "authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let next_version_address = match crate::executor_solana_core_parse_pubkey( + next_version_program, + "next_version_program", + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instruction = solana_instruction::Instruction { + program_id: solana_sdk_ids::loader_v4::ID, + accounts: vec![ + solana_instruction::AccountMeta::new(program_address, false), + solana_instruction::AccountMeta::new_readonly(authority_address, true), + solana_instruction::AccountMeta::new_readonly(next_version_address, false), + ], + data: FINALIZE_TAG.to_le_bytes().to_vec(), + }; + return finalize_single( + intent, + fee_payer, + crate::EX_SOLANA_CORE_LOADER_V4_FINALIZE_OPERATION, + instruction, + vec![(authority.clone(), std::string::String::from("program_authority"))], + ); +} + +fn set_program_length_instruction( + program: &solana_pubkey::Pubkey, + authority: &solana_pubkey::Pubkey, + recipient: &solana_pubkey::Pubkey, + new_size: u32, +) -> solana_instruction::Instruction { + let mut data = SET_PROGRAM_LENGTH_TAG.to_le_bytes().to_vec(); + data.extend_from_slice(&new_size.to_le_bytes()); + return solana_instruction::Instruction { + program_id: solana_sdk_ids::loader_v4::ID, + accounts: vec![ + solana_instruction::AccountMeta::new(*program, false), + solana_instruction::AccountMeta::new_readonly(*authority, true), + solana_instruction::AccountMeta::new(*recipient, false), + ], + data, + }; +} + +fn write_data(offset: u32, bytes: &[u8]) -> std::vec::Vec { + let mut data = WRITE_TAG.to_le_bytes().to_vec(); + data.extend_from_slice(&offset.to_le_bytes()); + data.extend_from_slice(&(bytes.len() as u64).to_le_bytes()); + data.extend_from_slice(bytes); + return data; +} + +fn copy_data(destination_offset: u32, source_offset: u32, length: u32) -> std::vec::Vec { + let mut data = COPY_TAG.to_le_bytes().to_vec(); + data.extend_from_slice(&destination_offset.to_le_bytes()); + data.extend_from_slice(&source_offset.to_le_bytes()); + data.extend_from_slice(&length.to_le_bytes()); + return data; +} + +fn finalize_single( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + operation_code: &str, + instruction: solana_instruction::Instruction, + signer_roles: std::vec::Vec<(crate::MdPubkey, std::string::String)>, +) -> kb_core::Result { + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + operation_code, + vec![instruction], + 0, + std::option::Option::None, + signer_roles, + ); +} + +fn validate_size(new_size: u32) -> kb_core::Result<()> { + if u64::from(new_size) > solana_system_interface::MAX_PERMITTED_DATA_LENGTH { + return crate::executor_solana_core_invalid( + "execution_loader_v4_program_size_out_of_range", + format!( + "Loader v4 program size must not exceed {} bytes", + solana_system_interface::MAX_PERMITTED_DATA_LENGTH + ), + ); + } + return std::result::Result::Ok(()); +} + +fn distinct( + first: &crate::MdPubkey, + second: &crate::MdPubkey, + first_name: &str, + second_name: &str, +) -> kb_core::Result<()> { + return crate::executor_solana_core_validate_distinct_pubkeys( + first, + second, + "execution_loader_v4_accounts_not_distinct", + format!("Loader v4 {first_name} and {second_name} accounts must be distinct"), + ); +} + +#[cfg(test)] +mod tests { + fn model(value: solana_pubkey::Pubkey) -> crate::MdPubkey { + return crate::MdPubkey(value.to_string()); + } + + fn policy(signers: &[solana_pubkey::Pubkey]) -> crate::ExApiExecutionPolicy { + return crate::ExApiExecutionPolicy { + cost_limit: crate::ExApiExecutionCostLimit { + max_spend_lamports: std::option::Option::Some(20_000_000), + max_fee_lamports: std::option::Option::Some(1_000_000), + max_compute_unit_price_micro_lamports: std::option::Option::Some(100), + }, + authorized_signers: signers.iter().map(|value| return model(*value)).collect(), + ..crate::ExApiExecutionPolicy::default() + }; + } + + fn intent( + operation: crate::ExSolanaCoreOperation, + fee_payer: solana_pubkey::Pubkey, + signers: &[solana_pubkey::Pubkey], + ) -> crate::ExSolanaCoreExecutionIntent { + return crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("loader-v4-test"), + fee_payer: model(fee_payer), + policy: policy(signers), + operation, + }; + } + + fn build( + intent: &crate::ExSolanaCoreExecutionIntent, + ) -> kb_core::Result { + let executor = crate::ExSolanaCoreExecutor; + return crate::ExApiTypedInstructionExecutor::build_prepared_plan(&executor, intent); + } + + fn build_or_panic( + intent: &crate::ExSolanaCoreExecutionIntent, + ) -> crate::ExApiPreparedExecutionPlan { + return match build(intent) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => panic!("Loader v4 test plan failed: {error}"), + }; + } + + fn assert_account( + account: &crate::ExApiPlannedAccount, + expected: solana_pubkey::Pubkey, + signer: bool, + writable: bool, + ) { + assert_eq!(account.pubkey.0, expected.to_string()); + assert_eq!(account.is_signer, signer); + assert_eq!(account.is_writable, writable); + } + + #[test] + fn create_buffer_preserves_official_compound_order_wire_and_spend() { + let payer = solana_pubkey::Pubkey::new_unique(); + let buffer = solana_pubkey::Pubkey::new_unique(); + let authority = solana_pubkey::Pubkey::new_unique(); + let recipient = solana_pubkey::Pubkey::new_unique(); + let intent = intent( + crate::ExSolanaCoreOperation::LoaderV4CreateBuffer { + payer: model(payer), + buffer: model(buffer), + authority: model(authority), + recipient: model(recipient), + lamports: 1_000_000, + new_size: 4096, + }, + payer, + &[payer, buffer, authority], + ); + let plan = build_or_panic(&intent); + assert_eq!(plan.instructions.len(), 2); + assert_eq!( + plan.instructions[0].program_id.0, + solana_sdk_ids::system_program::ID.to_string() + ); + assert_eq!(plan.instructions[1].program_id.0, solana_sdk_ids::loader_v4::ID.to_string()); + assert_eq!( + plan.instructions[1].data, + [ + super::SET_PROGRAM_LENGTH_TAG.to_le_bytes().as_slice(), + 4096_u32.to_le_bytes().as_slice(), + ] + .concat() + ); + assert_account(&plan.instructions[1].accounts[0], buffer, false, true); + assert_account(&plan.instructions[1].accounts[1], authority, true, false); + assert_account(&plan.instructions[1].accounts[2], recipient, false, true); + assert_eq!(plan.requested_spend_lamports, 1_000_000); + assert_eq!(plan.required_signers.len(), 3); + } + + #[test] + fn write_copy_and_set_length_preserve_exact_bincode_wire_contract() { + let fee_payer = solana_pubkey::Pubkey::new_unique(); + let program = solana_pubkey::Pubkey::new_unique(); + let authority = solana_pubkey::Pubkey::new_unique(); + let source = solana_pubkey::Pubkey::new_unique(); + let recipient = solana_pubkey::Pubkey::new_unique(); + let write_intent = intent( + crate::ExSolanaCoreOperation::LoaderV4Write { + program: model(program), + authority: model(authority), + offset: 7, + bytes: vec![1, 2, 3], + }, + fee_payer, + &[fee_payer, authority], + ); + let write_plan = build_or_panic(&write_intent); + assert_eq!( + write_plan.instructions[0].data, + [ + super::WRITE_TAG.to_le_bytes().as_slice(), + 7_u32.to_le_bytes().as_slice(), + 3_u64.to_le_bytes().as_slice(), + &[1, 2, 3], + ] + .concat() + ); + let copy_intent = intent( + crate::ExSolanaCoreOperation::LoaderV4Copy { + program: model(program), + authority: model(authority), + source_program: model(source), + destination_offset: 11, + source_offset: 13, + length: 17, + }, + fee_payer, + &[fee_payer, authority], + ); + let copy_plan = build_or_panic(©_intent); + assert_eq!( + copy_plan.instructions[0].data, + [ + super::COPY_TAG.to_le_bytes().as_slice(), + 11_u32.to_le_bytes().as_slice(), + 13_u32.to_le_bytes().as_slice(), + 17_u32.to_le_bytes().as_slice(), + ] + .concat() + ); + assert_account(©_plan.instructions[0].accounts[2], source, false, false); + let length_intent = intent( + crate::ExSolanaCoreOperation::LoaderV4SetProgramLength { + program: model(program), + authority: model(authority), + recipient: model(recipient), + new_size: 0, + }, + fee_payer, + &[fee_payer, authority], + ); + let length_plan = build_or_panic(&length_intent); + assert_eq!( + length_plan.instructions[0].data, + [ + super::SET_PROGRAM_LENGTH_TAG.to_le_bytes().as_slice(), + 0_u32.to_le_bytes().as_slice(), + ] + .concat() + ); + assert_eq!(length_plan.requested_spend_lamports, 0); + } + + #[test] + fn deploy_retract_transfer_and_finalize_preserve_official_accounts_and_tags() { + let fee_payer = solana_pubkey::Pubkey::new_unique(); + let program = solana_pubkey::Pubkey::new_unique(); + let authority = solana_pubkey::Pubkey::new_unique(); + let source = solana_pubkey::Pubkey::new_unique(); + let new_authority = solana_pubkey::Pubkey::new_unique(); + let next_version = solana_pubkey::Pubkey::new_unique(); + let fixtures = [ + ( + crate::ExSolanaCoreOperation::LoaderV4Deploy { + program: model(program), + authority: model(authority), + }, + super::DEPLOY_TAG, + vec![fee_payer, authority], + 2, + ), + ( + crate::ExSolanaCoreOperation::LoaderV4DeployFromSource { + program: model(program), + authority: model(authority), + source_program: model(source), + }, + super::DEPLOY_TAG, + vec![fee_payer, authority], + 3, + ), + ( + crate::ExSolanaCoreOperation::LoaderV4Retract { + program: model(program), + authority: model(authority), + }, + super::RETRACT_TAG, + vec![fee_payer, authority], + 2, + ), + ( + crate::ExSolanaCoreOperation::LoaderV4TransferAuthority { + program: model(program), + current_authority: model(authority), + new_authority: model(new_authority), + }, + super::TRANSFER_AUTHORITY_TAG, + vec![fee_payer, authority, new_authority], + 3, + ), + ( + crate::ExSolanaCoreOperation::LoaderV4Finalize { + program: model(program), + authority: model(authority), + next_version_program: model(next_version), + }, + super::FINALIZE_TAG, + vec![fee_payer, authority], + 3, + ), + ]; + for (operation, tag, signers, account_count) in fixtures { + let intent = intent(operation, fee_payer, signers.as_slice()); + let plan = build_or_panic(&intent); + assert_eq!(plan.instructions[0].data, tag.to_le_bytes().to_vec()); + assert_eq!(plan.instructions[0].accounts.len(), account_count); + } + } + + #[test] + fn malformed_sizes_ranges_and_authorities_are_rejected() { + let fee_payer = solana_pubkey::Pubkey::new_unique(); + let program = solana_pubkey::Pubkey::new_unique(); + let authority = solana_pubkey::Pubkey::new_unique(); + let recipient = solana_pubkey::Pubkey::new_unique(); + let empty_write = intent( + crate::ExSolanaCoreOperation::LoaderV4Write { + program: model(program), + authority: model(authority), + offset: 0, + bytes: std::vec::Vec::new(), + }, + fee_payer, + &[fee_payer, authority], + ); + assert!(build(&empty_write).is_err()); + let zero_copy = intent( + crate::ExSolanaCoreOperation::LoaderV4Copy { + program: model(program), + authority: model(authority), + source_program: model(recipient), + destination_offset: 0, + source_offset: 0, + length: 0, + }, + fee_payer, + &[fee_payer, authority], + ); + assert!(build(&zero_copy).is_err()); + let same_authority = intent( + crate::ExSolanaCoreOperation::LoaderV4TransferAuthority { + program: model(program), + current_authority: model(authority), + new_authority: model(authority), + }, + fee_payer, + &[fee_payer, authority], + ); + assert!(build(&same_authority).is_err()); + let zero_buffer = intent( + crate::ExSolanaCoreOperation::LoaderV4CreateBuffer { + payer: model(fee_payer), + buffer: model(program), + authority: model(authority), + recipient: model(recipient), + lamports: 1, + new_size: 0, + }, + fee_payer, + &[fee_payer, program, authority], + ); + assert!(build(&zero_buffer).is_err()); + } + + #[test] + fn loader_v4_wire_source_is_explicit_and_stable() { + assert_eq!(super::SOURCE, "solana-loader-v4-interface@3.1.0"); + assert_eq!(super::WRITE_TAG, 0); + assert_eq!(super::FINALIZE_TAG, 6); + } +} diff --git a/kb-lib/src/executor/solana/core/native_admin.rs b/kb-lib/src/executor/solana/core/native_admin.rs new file mode 100644 index 0000000..874c979 --- /dev/null +++ b/kb-lib/src/executor/solana/core/native_admin.rs @@ -0,0 +1,549 @@ +// file: kb-lib/src/executor/solana/core/native_admin.rs +// version: 1 + +//! Config Program and Feature Program instruction builders. + +const MAX_CONFIG_KEYS: usize = 128; +const MAX_CONFIG_INSTRUCTION_DATA_BYTES: usize = u16::MAX as usize; + +pub(crate) fn executor_solana_core_native_admin_build_prepared_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, +) -> kb_core::Result { + return match &intent.operation { + crate::ExSolanaCoreOperation::ConfigCreateAccount { + from, + config_account, + lamports, + max_config_data_space, + max_keys, + initial_data, + } => build_config_create_plan( + intent, + fee_payer, + from, + config_account, + *lamports, + *max_config_data_space, + max_keys.as_slice(), + initial_data.as_slice(), + ), + crate::ExSolanaCoreOperation::ConfigStore { + config_account, + config_account_is_signer, + keys, + data, + } => build_config_store_plan( + intent, + fee_payer, + config_account, + *config_account_is_signer, + keys.as_slice(), + data.as_slice(), + ), + crate::ExSolanaCoreOperation::FeatureActivate { + feature_account, + funding_account, + lamports, + } => build_feature_activate_plan( + intent, + fee_payer, + feature_account, + funding_account, + *lamports, + ), + crate::ExSolanaCoreOperation::FeatureRevokePendingActivation { feature_account } => { + build_feature_revoke_plan(intent, fee_payer, feature_account) + }, + _ => std::result::Result::Err(kb_core::Error::new( + "execution_native_admin_operation_mismatch", + "Config/Feature builder received an unrelated Solana core operation", + )), + }; +} + +fn build_config_create_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + from: &crate::MdPubkey, + config_account: &crate::MdPubkey, + lamports: u64, + max_config_data_space: u64, + max_keys: &[crate::ExSolanaCoreConfigKey], + initial_data: &[u8], +) -> kb_core::Result { + if lamports == 0 { + return std::result::Result::Err(kb_core::Error::new( + "execution_config_create_lamports_zero", + "Config account creation requires a positive rent-exempt lamport amount", + )); + } + if from == config_account { + return std::result::Result::Err(kb_core::Error::new( + "execution_config_create_same_account", + "Config funding and new account addresses must differ", + )); + } + let from_address = match crate::executor_solana_core_parse_pubkey(from, "from") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let config_address = + match crate::executor_solana_core_parse_pubkey(config_account, "config_account") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let encoded_max_keys = match encode_config_keys(max_keys) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let initial_data_len = initial_data.len() as u64; + if initial_data_len > max_config_data_space { + return std::result::Result::Err(kb_core::Error::new( + "execution_config_initial_data_exceeds_max_space", + "serialized initial Config data exceeds max_config_data_space", + )); + } + let space = match max_config_data_space.checked_add(encoded_max_keys.len() as u64) { + std::option::Option::Some(value) => value, + std::option::Option::None => { + return std::result::Result::Err(kb_core::Error::new( + "execution_config_space_overflow", + "Config account space exceeds u64", + )); + }, + }; + if space > solana_system_interface::MAX_PERMITTED_DATA_LENGTH { + return std::result::Result::Err(kb_core::Error::new( + "execution_config_space_out_of_range", + format!( + "Config account space must not exceed {} bytes", + solana_system_interface::MAX_PERMITTED_DATA_LENGTH + ), + )); + } + let config_program = match crate::executor_solana_core_parse_native_program_id( + kb_program_ids::CONFIG_PROGRAM_ID, + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let create = solana_system_interface::instruction::create_account( + &from_address, + &config_address, + lamports, + space, + &config_program, + ); + let mut initialize_data = vec![0_u8]; + initialize_data.extend_from_slice(initial_data); + let initialize = solana_instruction::Instruction { + program_id: config_program, + accounts: vec![solana_instruction::AccountMeta::new(config_address, true)], + data: initialize_data, + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_CONFIG_CREATE_ACCOUNT_OPERATION, + vec![create, initialize], + lamports, + std::option::Option::None, + vec![ + (from.clone(), std::string::String::from("config_funding_account")), + (config_account.clone(), std::string::String::from("new_config_account")), + ], + ); +} + +fn build_config_store_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + config_account: &crate::MdPubkey, + config_account_is_signer: bool, + keys: &[crate::ExSolanaCoreConfigKey], + data: &[u8], +) -> kb_core::Result { + let config_address = + match crate::executor_solana_core_parse_pubkey(config_account, "config_account") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let mut instruction_data = match encode_config_keys(keys) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let total_length = match instruction_data.len().checked_add(data.len()) { + std::option::Option::Some(value) => value, + std::option::Option::None => { + return std::result::Result::Err(kb_core::Error::new( + "execution_config_store_data_overflow", + "Config store instruction data length overflows usize", + )); + }, + }; + if total_length > MAX_CONFIG_INSTRUCTION_DATA_BYTES { + return std::result::Result::Err(kb_core::Error::new( + "execution_config_store_data_too_large", + format!( + "Config store instruction data must not exceed {MAX_CONFIG_INSTRUCTION_DATA_BYTES} bytes" + ), + )); + } + instruction_data.extend_from_slice(data); + let mut accounts = + vec![solana_instruction::AccountMeta::new(config_address, config_account_is_signer)]; + let mut signer_roles = std::vec::Vec::new(); + if config_account_is_signer { + signer_roles + .push((config_account.clone(), std::string::String::from("config_account_authority"))); + } + for key in keys { + if !key.is_signer || key.pubkey == *config_account { + continue; + } + let key_address = match crate::executor_solana_core_parse_pubkey(&key.pubkey, "config_key") + { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + accounts.push(solana_instruction::AccountMeta::new(key_address, true)); + signer_roles.push((key.pubkey.clone(), std::string::String::from("config_key_authority"))); + } + if signer_roles.is_empty() { + return std::result::Result::Err(kb_core::Error::new( + "execution_config_store_signer_missing", + "Config store requires the config account or at least one declared key signer", + )); + } + let program_id = match crate::executor_solana_core_parse_native_program_id( + kb_program_ids::CONFIG_PROGRAM_ID, + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instruction = solana_instruction::Instruction { + program_id, + accounts, + data: instruction_data, + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_CONFIG_STORE_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + signer_roles, + ); +} + +fn build_feature_activate_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + feature_account: &crate::MdPubkey, + funding_account: &crate::MdPubkey, + lamports: u64, +) -> kb_core::Result { + if lamports == 0 { + return std::result::Result::Err(kb_core::Error::new( + "execution_feature_activation_lamports_zero", + "feature activation requires a positive rent-exempt lamport amount", + )); + } + if feature_account == funding_account { + return std::result::Result::Err(kb_core::Error::new( + "execution_feature_activation_same_account", + "feature and funding accounts must differ", + )); + } + let feature_address = + match crate::executor_solana_core_parse_pubkey(feature_account, "feature_account") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let funding_address = + match crate::executor_solana_core_parse_pubkey(funding_account, "funding_account") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let feature_program = match crate::executor_solana_core_parse_native_program_id( + kb_program_ids::FEATURE_PROGRAM_ID, + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instructions = vec![ + solana_system_interface::instruction::transfer( + &funding_address, + &feature_address, + lamports, + ), + solana_system_interface::instruction::allocate( + &feature_address, + solana_feature_gate_interface::state::Feature::size_of() as u64, + ), + solana_system_interface::instruction::assign(&feature_address, &feature_program), + ]; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_FEATURE_ACTIVATE_OPERATION, + instructions, + lamports, + std::option::Option::None, + vec![ + (funding_account.clone(), std::string::String::from("feature_funding_account")), + (feature_account.clone(), std::string::String::from("new_feature_account")), + ], + ); +} + +fn build_feature_revoke_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + feature_account: &crate::MdPubkey, +) -> kb_core::Result { + let feature_address = + match crate::executor_solana_core_parse_pubkey(feature_account, "feature_account") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let feature_program = match crate::executor_solana_core_parse_native_program_id( + kb_program_ids::FEATURE_PROGRAM_ID, + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let incinerator = match crate::executor_solana_core_parse_native_program_id( + kb_program_ids::INCINERATOR_PROGRAM_ID, + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instruction = solana_instruction::Instruction { + program_id: feature_program, + accounts: vec![ + solana_instruction::AccountMeta::new(feature_address, true), + solana_instruction::AccountMeta::new(incinerator, false), + solana_instruction::AccountMeta::new_readonly( + solana_sdk_ids::system_program::id(), + false, + ), + ], + data: vec![0], + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_FEATURE_REVOKE_PENDING_ACTIVATION_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + vec![(feature_account.clone(), std::string::String::from("pending_feature_account"))], + ); +} + +fn encode_config_keys(keys: &[crate::ExSolanaCoreConfigKey]) -> kb_core::Result> { + if keys.len() > MAX_CONFIG_KEYS { + return std::result::Result::Err(kb_core::Error::new( + "execution_config_keys_too_many", + format!("Config key list must not exceed {MAX_CONFIG_KEYS} entries"), + )); + } + let mut seen = std::collections::BTreeSet::new(); + let mut encoded = encode_compact_u16(keys.len() as u16); + for key in keys { + if !seen.insert(key.pubkey.0.clone()) { + return std::result::Result::Err(kb_core::Error::new( + "execution_config_key_duplicate", + format!("Config key {} is declared more than once", key.pubkey.0), + )); + } + let address = match crate::executor_solana_core_parse_pubkey(&key.pubkey, "config_key") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + encoded.extend_from_slice(address.as_ref()); + encoded.push(u8::from(key.is_signer)); + } + return std::result::Result::Ok(encoded); +} + +fn encode_compact_u16(value: u16) -> std::vec::Vec { + if value < 128 { + return vec![value as u8]; + } + if value < 16_384 { + return vec![((value & 0x7f) as u8) | 0x80, (value >> 7) as u8]; + } + return vec![ + ((value & 0x7f) as u8) | 0x80, + (((value >> 7) & 0x7f) as u8) | 0x80, + (value >> 14) as u8, + ]; +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; // rust-rules: trait-import + + fn pubkey(value: solana_pubkey::Pubkey) -> crate::MdPubkey { + return crate::MdPubkey(value.to_string()); + } + + fn policy(signers: &[crate::MdPubkey], max_spend: u64) -> crate::ExApiExecutionPolicy { + return crate::ExApiExecutionPolicy { + authorized_signers: signers.to_vec(), + cost_limit: crate::ExApiExecutionCostLimit { + max_spend_lamports: std::option::Option::Some(max_spend), + max_fee_lamports: std::option::Option::Some(100_000), + max_compute_unit_price_micro_lamports: std::option::Option::Some(10), + }, + ..crate::ExApiExecutionPolicy::default() + }; + } + + #[test] + fn config_store_matches_the_official_wire_contract() { + let config = solana_pubkey::Pubkey::new_unique(); + let authority = solana_pubkey::Pubkey::new_unique(); + let payer = pubkey(solana_pubkey::Pubkey::new_unique()); + let intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("config-store"), + fee_payer: payer.clone(), + policy: policy(&[payer, pubkey(authority)], 0), + operation: crate::ExSolanaCoreOperation::ConfigStore { + config_account: pubkey(config), + config_account_is_signer: false, + keys: vec![crate::ExSolanaCoreConfigKey { + pubkey: pubkey(authority), + is_signer: true, + }], + data: vec![9, 8, 7], + }, + }; + let plan = crate::executor_solana_core_builder_build_prepared_plan(&intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + let instruction = &plan.instructions[0]; + let mut expected = vec![1]; + expected.extend_from_slice(authority.as_ref()); + expected.push(1); + expected.extend_from_slice(&[9, 8, 7]); + assert_eq!(instruction.data, expected); + assert_eq!(instruction.accounts.len(), 2); + assert!(instruction.accounts[1].is_signer); + assert!(instruction.accounts[1].is_writable); + assert_eq!(plan.operation_code, crate::EX_SOLANA_CORE_CONFIG_STORE_OPERATION); + } + + #[test] + fn config_create_uses_official_space_and_initialization_order() { + let funding = solana_pubkey::Pubkey::new_unique(); + let config = solana_pubkey::Pubkey::new_unique(); + let max_key = solana_pubkey::Pubkey::new_unique(); + let payer = pubkey(solana_pubkey::Pubkey::new_unique()); + let intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("config-create"), + fee_payer: payer.clone(), + policy: policy(&[payer, pubkey(funding), pubkey(config)], 500), + operation: crate::ExSolanaCoreOperation::ConfigCreateAccount { + from: pubkey(funding), + config_account: pubkey(config), + lamports: 500, + max_config_data_space: 64, + max_keys: vec![crate::ExSolanaCoreConfigKey { + pubkey: pubkey(max_key), + is_signer: true, + }], + initial_data: vec![0, 0, 0, 0], + }, + }; + let plan = crate::executor_solana_core_builder_build_prepared_plan(&intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + assert_eq!(plan.instructions.len(), 2); + assert_eq!(plan.instructions[1].data, vec![0, 0, 0, 0, 0]); + assert_eq!(plan.requested_spend_lamports, 500); + assert_eq!(plan.required_signers.len(), 3); + } + + #[test] + fn feature_operations_match_the_official_instruction_contracts() { + let feature = solana_pubkey::Pubkey::new_unique(); + let funding = solana_pubkey::Pubkey::new_unique(); + let payer = pubkey(solana_pubkey::Pubkey::new_unique()); + let activate = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("feature-activate"), + fee_payer: payer.clone(), + policy: policy(&[payer.clone(), pubkey(feature), pubkey(funding)], 600), + operation: crate::ExSolanaCoreOperation::FeatureActivate { + feature_account: pubkey(feature), + funding_account: pubkey(funding), + lamports: 600, + }, + }; + let activate_plan = crate::executor_solana_core_builder_build_prepared_plan(&activate) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + assert_eq!(activate_plan.instructions.len(), 3); + assert_eq!(activate_plan.instructions[0].program_id.0, kb_program_ids::SYSTEM_PROGRAM_ID); + assert_eq!(activate_plan.requested_spend_lamports, 600); + + let revoke = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("feature-revoke"), + fee_payer: payer.clone(), + policy: policy(&[payer, pubkey(feature)], 0), + operation: crate::ExSolanaCoreOperation::FeatureRevokePendingActivation { + feature_account: pubkey(feature), + }, + }; + let revoke_plan = crate::executor_solana_core_builder_build_prepared_plan(&revoke) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + assert_eq!(revoke_plan.instructions[0].data, vec![0]); + assert_eq!(revoke_plan.instructions[0].accounts.len(), 3); + assert_eq!( + revoke_plan.instructions[0].accounts[1].pubkey.0, + kb_program_ids::INCINERATOR_PROGRAM_ID + ); + } + + #[test] + fn malformed_config_and_feature_contracts_are_rejected() { + let same = pubkey(solana_pubkey::Pubkey::new_unique()); + let intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("feature-invalid"), + fee_payer: same.clone(), + policy: policy(std::slice::from_ref(&same), 0), + operation: crate::ExSolanaCoreOperation::FeatureActivate { + feature_account: same.clone(), + funding_account: same, + lamports: 0, + }, + }; + assert!(crate::executor_solana_core_builder_build_prepared_plan(&intent).is_err()); + + let config = solana_pubkey::Pubkey::new_unique(); + let payer = pubkey(solana_pubkey::Pubkey::new_unique()); + let no_signer = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("config-no-signer"), + fee_payer: payer.clone(), + policy: policy(&[payer], 0), + operation: crate::ExSolanaCoreOperation::ConfigStore { + config_account: pubkey(config), + config_account_is_signer: false, + keys: std::vec::Vec::new(), + data: vec![1], + }, + }; + assert!(crate::executor_solana_core_builder_build_prepared_plan(&no_signer).is_err()); + } + + #[test] + fn official_program_ids_parse() { + assert_eq!( + solana_pubkey::Pubkey::from_str(kb_program_ids::CONFIG_PROGRAM_ID) + .unwrap_or_else(|error| panic!("invalid id: {error}")) + .to_string(), + kb_program_ids::CONFIG_PROGRAM_ID + ); + } +} diff --git a/kb-lib/src/executor/solana/core/precompiles.rs b/kb-lib/src/executor/solana/core/precompiles.rs new file mode 100644 index 0000000..e1b5304 --- /dev/null +++ b/kb-lib/src/executor/solana/core/precompiles.rs @@ -0,0 +1,1247 @@ +// file: kb-lib/src/executor/solana/core/precompiles.rs +// version: 1 + +//! Deterministic native signature-precompile instruction builders. + +use std::str::FromStr; // rust-rules: trait-import + +const MAX_SIGNATURES_PER_INSTRUCTION: usize = 255; +const MAX_OFFSET_ADDRESSABLE_DATA_LENGTH: usize = u16::MAX as usize; + +const ED25519_OFFSETS_START: usize = 2; +const ED25519_OFFSETS_SIZE: usize = 14; +const ED25519_PUBLIC_KEY_SIZE: usize = 32; +const ED25519_SIGNATURE_SIZE: usize = 64; + +const SECP256K1_OFFSETS_START: usize = 1; +const SECP256K1_OFFSETS_SIZE: usize = 11; +const SECP256K1_ETHEREUM_ADDRESS_SIZE: usize = 20; +const SECP256K1_SIGNATURE_SIZE: usize = 64; +const SECP256K1_SIGNATURE_WITH_RECOVERY_SIZE: usize = 65; + +const SECP256R1_OFFSETS_START: usize = 2; +const SECP256R1_OFFSETS_SIZE: usize = 14; +const SECP256R1_PUBLIC_KEY_SIZE: usize = 33; +const SECP256R1_SIGNATURE_SIZE: usize = 64; +const SECP256R1_HALF_ORDER: [u8; 32] = [ + 0x7f, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xde, 0x73, 0x7d, 0x56, 0xd3, 0x8b, 0xcf, 0x42, 0x79, 0xdc, 0xe5, 0x61, 0x7e, 0x31, 0x92, 0xa8, +]; + +pub(crate) fn executor_solana_core_precompiles_build_prepared_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, +) -> kb_core::Result { + return match &intent.operation { + crate::ExSolanaCoreOperation::Ed25519VerifyInline { message, signature, public_key } => { + build_ed25519_inline_plan( + intent, + fee_payer, + message.as_slice(), + signature.as_slice(), + public_key.as_slice(), + ) + }, + crate::ExSolanaCoreOperation::Ed25519VerifyOffsets { offsets, data } => { + build_ed25519_offsets_plan(intent, fee_payer, offsets.as_slice(), data.as_slice()) + }, + crate::ExSolanaCoreOperation::Secp256k1VerifyInline { + message, + signature, + recovery_id, + ethereum_address, + } => build_secp256k1_inline_plan( + intent, + fee_payer, + message.as_slice(), + signature.as_slice(), + *recovery_id, + ethereum_address.as_slice(), + ), + crate::ExSolanaCoreOperation::Secp256k1VerifyOffsets { offsets, data } => { + build_secp256k1_offsets_plan(intent, fee_payer, offsets.as_slice(), data.as_slice()) + }, + crate::ExSolanaCoreOperation::Secp256r1VerifyInline { message, signature, public_key } => { + build_secp256r1_inline_plan( + intent, + fee_payer, + message.as_slice(), + signature.as_slice(), + public_key.as_slice(), + ) + }, + crate::ExSolanaCoreOperation::Secp256r1VerifyOffsets { offsets, data } => { + build_secp256r1_offsets_plan(intent, fee_payer, offsets.as_slice(), data.as_slice()) + }, + _ => std::result::Result::Err(kb_core::Error::new( + "execution_precompile_operation_mismatch", + "precompile builder received an unrelated Solana core operation", + )), + }; +} + +fn build_ed25519_inline_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + message: &[u8], + signature: &[u8], + public_key: &[u8], +) -> kb_core::Result { + match crate::executor_solana_core_validate_exact_length( + signature, + ED25519_SIGNATURE_SIZE, + "execution_ed25519_signature_length_invalid", + "Ed25519 signature", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match crate::executor_solana_core_validate_exact_length( + public_key, + ED25519_PUBLIC_KEY_SIZE, + "execution_ed25519_public_key_length_invalid", + "Ed25519 public key", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let data_start = ED25519_OFFSETS_START + ED25519_OFFSETS_SIZE; + let public_key_offset = data_start; + let signature_offset = public_key_offset + ED25519_PUBLIC_KEY_SIZE; + let message_data_offset = signature_offset + ED25519_SIGNATURE_SIZE; + match validate_inline_message_length(message, message_data_offset) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let offsets = crate::ExSolanaCoreEd25519VerificationOffsets { + signature_offset: signature_offset as u16, + signature_instruction_index: u16::MAX, + public_key_offset: public_key_offset as u16, + public_key_instruction_index: u16::MAX, + message_data_offset: message_data_offset as u16, + message_data_size: message.len() as u16, + message_instruction_index: u16::MAX, + }; + let mut data = std::vec::Vec::with_capacity(message_data_offset + message.len()); + data.push(1); + data.push(0); + append_ed25519_offsets(&mut data, &offsets); + data.extend_from_slice(public_key); + data.extend_from_slice(signature); + data.extend_from_slice(message); + let instruction = match instruction(kb_program_ids::ED25519_PROGRAM_ID, data) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_ED25519_VERIFY_INLINE_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + std::vec::Vec::new(), + ); +} + +fn build_ed25519_offsets_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + offsets: &[crate::ExSolanaCoreEd25519VerificationOffsets], + appended_data: &[u8], +) -> kb_core::Result { + let total_length = match validate_table_shape( + offsets.len(), + ED25519_OFFSETS_START, + ED25519_OFFSETS_SIZE, + appended_data.len(), + "execution_ed25519_offsets_empty", + "execution_ed25519_offsets_too_many", + "execution_ed25519_instruction_too_large", + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + for offset in offsets { + match validate_ed25519_local_ranges(offset, total_length) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + let mut data = std::vec::Vec::with_capacity(total_length); + data.push(offsets.len() as u8); + data.push(0); + for offset in offsets { + append_ed25519_offsets(&mut data, offset); + } + data.extend_from_slice(appended_data); + let instruction = match instruction(kb_program_ids::ED25519_PROGRAM_ID, data) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_ED25519_VERIFY_OFFSETS_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + std::vec::Vec::new(), + ); +} + +fn build_secp256k1_inline_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + message: &[u8], + signature: &[u8], + recovery_id: u8, + ethereum_address: &[u8], +) -> kb_core::Result { + match crate::executor_solana_core_validate_exact_length( + signature, + SECP256K1_SIGNATURE_SIZE, + "execution_secp256k1_signature_length_invalid", + "secp256k1 signature", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match crate::executor_solana_core_validate_exact_length( + ethereum_address, + SECP256K1_ETHEREUM_ADDRESS_SIZE, + "execution_secp256k1_ethereum_address_length_invalid", + "secp256k1 Ethereum address", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + if recovery_id > 3 { + return std::result::Result::Err(kb_core::Error::new( + "execution_secp256k1_recovery_id_invalid", + "secp256k1 recovery id must be between 0 and 3", + )); + } + let data_start = SECP256K1_OFFSETS_START + SECP256K1_OFFSETS_SIZE; + let ethereum_address_offset = data_start; + let signature_offset = ethereum_address_offset + SECP256K1_ETHEREUM_ADDRESS_SIZE; + let message_data_offset = signature_offset + SECP256K1_SIGNATURE_WITH_RECOVERY_SIZE; + match validate_inline_message_length(message, message_data_offset) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let offsets = crate::ExSolanaCoreSecp256k1VerificationOffsets { + signature_offset: signature_offset as u16, + signature_instruction_index: 0, + ethereum_address_offset: ethereum_address_offset as u16, + ethereum_address_instruction_index: 0, + message_data_offset: message_data_offset as u16, + message_data_size: message.len() as u16, + message_instruction_index: 0, + }; + let mut data = std::vec::Vec::with_capacity(message_data_offset + message.len()); + data.push(1); + append_secp256k1_offsets(&mut data, &offsets); + data.extend_from_slice(ethereum_address); + data.extend_from_slice(signature); + data.push(recovery_id); + data.extend_from_slice(message); + let instruction = match instruction(kb_program_ids::SECP256K1_PROGRAM_ID, data) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_SECP256K1_VERIFY_INLINE_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + std::vec::Vec::new(), + ); +} + +fn build_secp256k1_offsets_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + offsets: &[crate::ExSolanaCoreSecp256k1VerificationOffsets], + appended_data: &[u8], +) -> kb_core::Result { + let total_length = match validate_table_shape( + offsets.len(), + SECP256K1_OFFSETS_START, + SECP256K1_OFFSETS_SIZE, + appended_data.len(), + "execution_secp256k1_offsets_empty", + "execution_secp256k1_offsets_too_many", + "execution_secp256k1_instruction_too_large", + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + for offset in offsets { + match validate_secp256k1_local_ranges(offset, total_length) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + let mut data = std::vec::Vec::with_capacity(total_length); + data.push(offsets.len() as u8); + for offset in offsets { + append_secp256k1_offsets(&mut data, offset); + } + data.extend_from_slice(appended_data); + for offset in offsets { + match validate_secp256k1_local_payload(offset, data.as_slice()) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + let instruction = match instruction(kb_program_ids::SECP256K1_PROGRAM_ID, data) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_SECP256K1_VERIFY_OFFSETS_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + std::vec::Vec::new(), + ); +} + +fn build_secp256r1_inline_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + message: &[u8], + signature: &[u8], + public_key: &[u8], +) -> kb_core::Result { + match crate::executor_solana_core_validate_exact_length( + signature, + SECP256R1_SIGNATURE_SIZE, + "execution_secp256r1_signature_length_invalid", + "secp256r1 signature", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match validate_secp256r1_signature(signature) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match crate::executor_solana_core_validate_exact_length( + public_key, + SECP256R1_PUBLIC_KEY_SIZE, + "execution_secp256r1_public_key_length_invalid", + "secp256r1 compressed public key", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match validate_secp256r1_public_key(public_key) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let data_start = SECP256R1_OFFSETS_START + SECP256R1_OFFSETS_SIZE; + let public_key_offset = data_start; + let signature_offset = public_key_offset + SECP256R1_PUBLIC_KEY_SIZE; + let message_data_offset = signature_offset + SECP256R1_SIGNATURE_SIZE; + match validate_inline_message_length(message, message_data_offset) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let offsets = crate::ExSolanaCoreSecp256r1VerificationOffsets { + signature_offset: signature_offset as u16, + signature_instruction_index: u16::MAX, + public_key_offset: public_key_offset as u16, + public_key_instruction_index: u16::MAX, + message_data_offset: message_data_offset as u16, + message_data_size: message.len() as u16, + message_instruction_index: u16::MAX, + }; + let mut data = std::vec::Vec::with_capacity(message_data_offset + message.len()); + data.push(1); + data.push(0); + append_secp256r1_offsets(&mut data, &offsets); + data.extend_from_slice(public_key); + data.extend_from_slice(signature); + data.extend_from_slice(message); + let instruction = match instruction(kb_program_ids::SECP256R1_PROGRAM_ID, data) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_SECP256R1_VERIFY_INLINE_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + std::vec::Vec::new(), + ); +} + +fn build_secp256r1_offsets_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + offsets: &[crate::ExSolanaCoreSecp256r1VerificationOffsets], + appended_data: &[u8], +) -> kb_core::Result { + let total_length = match validate_table_shape( + offsets.len(), + SECP256R1_OFFSETS_START, + SECP256R1_OFFSETS_SIZE, + appended_data.len(), + "execution_secp256r1_offsets_empty", + "execution_secp256r1_offsets_too_many", + "execution_secp256r1_instruction_too_large", + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + for offset in offsets { + match validate_secp256r1_local_ranges(offset, total_length) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + let mut data = std::vec::Vec::with_capacity(total_length); + data.push(offsets.len() as u8); + data.push(0); + for offset in offsets { + append_secp256r1_offsets(&mut data, offset); + } + data.extend_from_slice(appended_data); + for offset in offsets { + match validate_secp256r1_local_payload(offset, data.as_slice()) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + let instruction = match instruction(kb_program_ids::SECP256R1_PROGRAM_ID, data) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_SECP256R1_VERIFY_OFFSETS_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + std::vec::Vec::new(), + ); +} + +fn instruction( + program_id: &str, + data: std::vec::Vec, +) -> kb_core::Result { + let parsed_program_id = match solana_pubkey::Pubkey::from_str(program_id) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => { + return std::result::Result::Err(kb_core::Error::new( + "execution_precompile_program_id_invalid", + format!("invalid native precompile program id {program_id}: {error}"), + )); + }, + }; + return std::result::Result::Ok(solana_instruction::Instruction { + program_id: parsed_program_id, + accounts: std::vec::Vec::new(), + data, + }); +} + +fn validate_inline_message_length(message: &[u8], prefix_length: usize) -> kb_core::Result<()> { + let total_length = match prefix_length.checked_add(message.len()) { + std::option::Option::Some(value) => value, + std::option::Option::None => { + return std::result::Result::Err(kb_core::Error::new( + "execution_precompile_message_length_overflow", + "precompile message length overflows the addressable instruction size", + )); + }, + }; + if message.len() > u16::MAX as usize || total_length > MAX_OFFSET_ADDRESSABLE_DATA_LENGTH { + return std::result::Result::Err(kb_core::Error::new( + "execution_precompile_message_too_large", + format!( + "precompile message and inline payload must fit within {MAX_OFFSET_ADDRESSABLE_DATA_LENGTH} bytes" + ), + )); + } + return std::result::Result::Ok(()); +} + +fn validate_table_shape( + count: usize, + offsets_start: usize, + offsets_size: usize, + appended_data_length: usize, + empty_code: &str, + count_code: &str, + size_code: &str, +) -> kb_core::Result { + if count == 0 { + return std::result::Result::Err(kb_core::Error::new( + empty_code, + "precompile offset table requires at least one signature record", + )); + } + if count > MAX_SIGNATURES_PER_INSTRUCTION { + return std::result::Result::Err(kb_core::Error::new( + count_code, + format!( + "precompile offset table cannot contain more than {MAX_SIGNATURES_PER_INSTRUCTION} records" + ), + )); + } + let offsets_length = match offsets_size.checked_mul(count) { + std::option::Option::Some(value) => value, + std::option::Option::None => { + return std::result::Result::Err(kb_core::Error::new( + size_code, + "precompile offset table length overflow", + )); + }, + }; + let header_length = match offsets_start.checked_add(offsets_length) { + std::option::Option::Some(value) => value, + std::option::Option::None => { + return std::result::Result::Err(kb_core::Error::new( + size_code, + "precompile offset header length overflow", + )); + }, + }; + let total_length = match header_length.checked_add(appended_data_length) { + std::option::Option::Some(value) => value, + std::option::Option::None => { + return std::result::Result::Err(kb_core::Error::new( + size_code, + "precompile instruction length overflow", + )); + }, + }; + if total_length > MAX_OFFSET_ADDRESSABLE_DATA_LENGTH { + return std::result::Result::Err(kb_core::Error::new( + size_code, + format!( + "precompile instruction data must not exceed {MAX_OFFSET_ADDRESSABLE_DATA_LENGTH} bytes" + ), + )); + } + return std::result::Result::Ok(total_length); +} + +fn validate_ed25519_local_ranges( + offsets: &crate::ExSolanaCoreEd25519VerificationOffsets, + total_length: usize, +) -> kb_core::Result<()> { + if offsets.signature_instruction_index == u16::MAX { + match validate_local_range( + offsets.signature_offset, + ED25519_SIGNATURE_SIZE, + total_length, + "execution_ed25519_signature_offset_out_of_bounds", + "Ed25519 signature", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + if offsets.public_key_instruction_index == u16::MAX { + match validate_local_range( + offsets.public_key_offset, + ED25519_PUBLIC_KEY_SIZE, + total_length, + "execution_ed25519_public_key_offset_out_of_bounds", + "Ed25519 public key", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + if offsets.message_instruction_index == u16::MAX { + match validate_local_range( + offsets.message_data_offset, + offsets.message_data_size as usize, + total_length, + "execution_ed25519_message_offset_out_of_bounds", + "Ed25519 message", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + return std::result::Result::Ok(()); +} + +fn validate_secp256k1_local_ranges( + offsets: &crate::ExSolanaCoreSecp256k1VerificationOffsets, + total_length: usize, +) -> kb_core::Result<()> { + if offsets.signature_instruction_index == 0 { + match validate_local_range( + offsets.signature_offset, + SECP256K1_SIGNATURE_WITH_RECOVERY_SIZE, + total_length, + "execution_secp256k1_signature_offset_out_of_bounds", + "secp256k1 signature and recovery id", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + if offsets.ethereum_address_instruction_index == 0 { + match validate_local_range( + offsets.ethereum_address_offset, + SECP256K1_ETHEREUM_ADDRESS_SIZE, + total_length, + "execution_secp256k1_ethereum_address_offset_out_of_bounds", + "secp256k1 Ethereum address", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + if offsets.message_instruction_index == 0 { + match validate_local_range( + offsets.message_data_offset, + offsets.message_data_size as usize, + total_length, + "execution_secp256k1_message_offset_out_of_bounds", + "secp256k1 message", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + return std::result::Result::Ok(()); +} + +fn validate_secp256r1_local_ranges( + offsets: &crate::ExSolanaCoreSecp256r1VerificationOffsets, + total_length: usize, +) -> kb_core::Result<()> { + if offsets.signature_instruction_index == u16::MAX { + match validate_local_range( + offsets.signature_offset, + SECP256R1_SIGNATURE_SIZE, + total_length, + "execution_secp256r1_signature_offset_out_of_bounds", + "secp256r1 signature", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + if offsets.public_key_instruction_index == u16::MAX { + match validate_local_range( + offsets.public_key_offset, + SECP256R1_PUBLIC_KEY_SIZE, + total_length, + "execution_secp256r1_public_key_offset_out_of_bounds", + "secp256r1 public key", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + if offsets.message_instruction_index == u16::MAX { + match validate_local_range( + offsets.message_data_offset, + offsets.message_data_size as usize, + total_length, + "execution_secp256r1_message_offset_out_of_bounds", + "secp256r1 message", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + return std::result::Result::Ok(()); +} + +fn validate_local_range( + offset: u16, + length: usize, + total_length: usize, + code: &str, + label: &str, +) -> kb_core::Result<()> { + let start = offset as usize; + let end = match start.checked_add(length) { + std::option::Option::Some(value) => value, + std::option::Option::None => { + return std::result::Result::Err(kb_core::Error::new( + code, + format!("{label} range overflows the instruction data"), + )); + }, + }; + if end > total_length { + return std::result::Result::Err(kb_core::Error::new( + code, + format!("{label} range exceeds the generated instruction data"), + )); + } + return std::result::Result::Ok(()); +} + +fn validate_secp256k1_local_payload( + offsets: &crate::ExSolanaCoreSecp256k1VerificationOffsets, + data: &[u8], +) -> kb_core::Result<()> { + if offsets.signature_instruction_index != 0 { + return std::result::Result::Ok(()); + } + let recovery_offset = + match (offsets.signature_offset as usize).checked_add(SECP256K1_SIGNATURE_SIZE) { + std::option::Option::Some(value) => value, + std::option::Option::None => { + return std::result::Result::Err(kb_core::Error::new( + "execution_secp256k1_signature_offset_out_of_bounds", + "secp256k1 recovery id offset overflows the instruction data", + )); + }, + }; + let recovery_id = match data.get(recovery_offset) { + std::option::Option::Some(value) => *value, + std::option::Option::None => { + return std::result::Result::Err(kb_core::Error::new( + "execution_secp256k1_signature_offset_out_of_bounds", + "secp256k1 recovery id exceeds the generated instruction data", + )); + }, + }; + if recovery_id > 3 { + return std::result::Result::Err(kb_core::Error::new( + "execution_secp256k1_recovery_id_invalid", + "secp256k1 recovery id must be between 0 and 3", + )); + } + return std::result::Result::Ok(()); +} + +fn validate_secp256r1_local_payload( + offsets: &crate::ExSolanaCoreSecp256r1VerificationOffsets, + data: &[u8], +) -> kb_core::Result<()> { + if offsets.signature_instruction_index == u16::MAX { + let start = offsets.signature_offset as usize; + let end = match start.checked_add(SECP256R1_SIGNATURE_SIZE) { + std::option::Option::Some(value) => value, + std::option::Option::None => { + return std::result::Result::Err(kb_core::Error::new( + "execution_secp256r1_signature_offset_out_of_bounds", + "secp256r1 signature range overflows the instruction data", + )); + }, + }; + let signature = match data.get(start..end) { + std::option::Option::Some(value) => value, + std::option::Option::None => { + return std::result::Result::Err(kb_core::Error::new( + "execution_secp256r1_signature_offset_out_of_bounds", + "secp256r1 signature exceeds the generated instruction data", + )); + }, + }; + match validate_secp256r1_signature(signature) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + if offsets.public_key_instruction_index == u16::MAX { + let start = offsets.public_key_offset as usize; + let end = match start.checked_add(SECP256R1_PUBLIC_KEY_SIZE) { + std::option::Option::Some(value) => value, + std::option::Option::None => { + return std::result::Result::Err(kb_core::Error::new( + "execution_secp256r1_public_key_offset_out_of_bounds", + "secp256r1 public key range overflows the instruction data", + )); + }, + }; + let public_key = match data.get(start..end) { + std::option::Option::Some(value) => value, + std::option::Option::None => { + return std::result::Result::Err(kb_core::Error::new( + "execution_secp256r1_public_key_offset_out_of_bounds", + "secp256r1 public key exceeds the generated instruction data", + )); + }, + }; + match validate_secp256r1_public_key(public_key) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + return std::result::Result::Ok(()); +} + +fn validate_secp256r1_public_key(public_key: &[u8]) -> kb_core::Result<()> { + let prefix = match public_key.first() { + std::option::Option::Some(value) => *value, + std::option::Option::None => { + return std::result::Result::Err(kb_core::Error::new( + "execution_secp256r1_public_key_length_invalid", + "secp256r1 compressed public key must contain 33 bytes", + )); + }, + }; + if prefix != 2 && prefix != 3 { + return std::result::Result::Err(kb_core::Error::new( + "execution_secp256r1_public_key_prefix_invalid", + "secp256r1 compressed public key must start with 0x02 or 0x03", + )); + } + return std::result::Result::Ok(()); +} + +fn validate_secp256r1_signature(signature: &[u8]) -> kb_core::Result<()> { + let r = &signature[..32]; + let s = &signature[32..]; + if r.iter().all(|value| return *value == 0) || s.iter().all(|value| return *value == 0) { + return std::result::Result::Err(kb_core::Error::new( + "execution_secp256r1_signature_zero_component", + "secp256r1 signature components r and s must both be non-zero", + )); + } + if s > SECP256R1_HALF_ORDER.as_slice() { + return std::result::Result::Err(kb_core::Error::new( + "execution_secp256r1_signature_not_low_s", + "secp256r1 signature must use the runtime-required low-S canonical form", + )); + } + return std::result::Result::Ok(()); +} + +fn append_ed25519_offsets( + data: &mut std::vec::Vec, + offsets: &crate::ExSolanaCoreEd25519VerificationOffsets, +) { + append_u16_signature_offsets( + data, + [ + offsets.signature_offset, + offsets.signature_instruction_index, + offsets.public_key_offset, + offsets.public_key_instruction_index, + offsets.message_data_offset, + offsets.message_data_size, + offsets.message_instruction_index, + ], + ); + return; +} + +fn append_secp256r1_offsets( + data: &mut std::vec::Vec, + offsets: &crate::ExSolanaCoreSecp256r1VerificationOffsets, +) { + append_u16_signature_offsets( + data, + [ + offsets.signature_offset, + offsets.signature_instruction_index, + offsets.public_key_offset, + offsets.public_key_instruction_index, + offsets.message_data_offset, + offsets.message_data_size, + offsets.message_instruction_index, + ], + ); + return; +} + +fn append_u16_signature_offsets(data: &mut std::vec::Vec, offsets: [u16; 7]) { + for value in offsets { + append_u16(data, value); + } + return; +} + +fn append_secp256k1_offsets( + data: &mut std::vec::Vec, + offsets: &crate::ExSolanaCoreSecp256k1VerificationOffsets, +) { + append_u16(data, offsets.signature_offset); + data.push(offsets.signature_instruction_index); + append_u16(data, offsets.ethereum_address_offset); + data.push(offsets.ethereum_address_instruction_index); + append_u16(data, offsets.message_data_offset); + append_u16(data, offsets.message_data_size); + data.push(offsets.message_instruction_index); + return; +} + +fn append_u16(data: &mut std::vec::Vec, value: u16) { + data.extend_from_slice(&value.to_le_bytes()); + return; +} + +#[cfg(test)] +mod tests { + fn pubkey(value: &str) -> crate::MdPubkey { + return crate::MdPubkey(std::string::String::from(value)); + } + + fn policy() -> crate::ExApiExecutionPolicy { + let fee_payer = pubkey(kb_program_ids::VOTE_PROGRAM_ID); + return crate::ExApiExecutionPolicy { + cost_limit: crate::ExApiExecutionCostLimit { + max_spend_lamports: std::option::Option::Some(0), + max_fee_lamports: std::option::Option::Some(100_000), + max_compute_unit_price_micro_lamports: std::option::Option::Some(100), + }, + authorized_signers: vec![fee_payer], + ..crate::ExApiExecutionPolicy::default() + }; + } + + fn intent(operation: crate::ExSolanaCoreOperation) -> crate::ExSolanaCoreExecutionIntent { + return crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("precompile-test"), + fee_payer: pubkey(kb_program_ids::VOTE_PROGRAM_ID), + policy: policy(), + operation, + }; + } + + fn assert_matches_instruction( + plan: &crate::ExApiPreparedExecutionPlan, + expected: &solana_instruction::Instruction, + ) { + assert_eq!(plan.instructions.len(), 1); + assert_eq!(plan.instructions[0].program_id.0, expected.program_id.to_string()); + assert_eq!(plan.instructions[0].data, expected.data); + assert!(plan.instructions[0].accounts.is_empty()); + assert_eq!(plan.required_signers.len(), 1); + assert_eq!(plan.required_signers[0].pubkey.0, kb_program_ids::VOTE_PROGRAM_ID); + assert_eq!(plan.requested_spend_lamports, 0); + return; + } + + #[test] + fn ed25519_and_secp256r1_offset_adapters_share_wire_without_sharing_types() { + let ed25519_offsets = crate::ExSolanaCoreEd25519VerificationOffsets { + signature_offset: 1, + signature_instruction_index: 2, + public_key_offset: 3, + public_key_instruction_index: 4, + message_data_offset: 5, + message_data_size: 6, + message_instruction_index: 7, + }; + let secp256r1_offsets = crate::ExSolanaCoreSecp256r1VerificationOffsets { + signature_offset: 1, + signature_instruction_index: 2, + public_key_offset: 3, + public_key_instruction_index: 4, + message_data_offset: 5, + message_data_size: 6, + message_instruction_index: 7, + }; + let mut ed25519_data = std::vec::Vec::new(); + let mut secp256r1_data = std::vec::Vec::new(); + super::append_ed25519_offsets(&mut ed25519_data, &ed25519_offsets); + super::append_secp256r1_offsets(&mut secp256r1_data, &secp256r1_offsets); + assert_eq!(ed25519_data, secp256r1_data); + assert_eq!(ed25519_data, vec![1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0]); + } + + #[test] + fn inline_precompile_operations_match_official_builders_and_layout() { + let message = b"khadhroony-precompile".to_vec(); + let ed_signature = [7u8; 64]; + let ed_public_key = [9u8; 32]; + let ed_plan = crate::executor_solana_core_builder_build_prepared_plan(&intent( + crate::ExSolanaCoreOperation::Ed25519VerifyInline { + message: message.clone(), + signature: ed_signature.to_vec(), + public_key: ed_public_key.to_vec(), + }, + )) + .unwrap_or_else(|error| panic!("unexpected Ed25519 error: {error}")); + let ed_expected = solana_ed25519_program::new_ed25519_instruction_with_signature( + message.as_slice(), + &ed_signature, + &ed_public_key, + ); + assert_matches_instruction(&ed_plan, &ed_expected); + assert_eq!(ed_plan.operation_code, crate::EX_SOLANA_CORE_ED25519_VERIFY_INLINE_OPERATION); + + let mut r1_signature = [1u8; 64]; + r1_signature[32] = 0x01; + let mut r1_public_key = [3u8; 33]; + r1_public_key[0] = 2; + let r1_plan = crate::executor_solana_core_builder_build_prepared_plan(&intent( + crate::ExSolanaCoreOperation::Secp256r1VerifyInline { + message: message.clone(), + signature: r1_signature.to_vec(), + public_key: r1_public_key.to_vec(), + }, + )) + .unwrap_or_else(|error| panic!("unexpected secp256r1 error: {error}")); + let r1_expected = solana_secp256r1_program::new_secp256r1_instruction_with_signature( + message.as_slice(), + &r1_signature, + &r1_public_key, + ); + assert_matches_instruction(&r1_plan, &r1_expected); + assert_eq!(r1_plan.operation_code, crate::EX_SOLANA_CORE_SECP256R1_VERIFY_INLINE_OPERATION); + + let k1_signature = [5u8; 64]; + let k1_address = [11u8; 20]; + let k1_plan = crate::executor_solana_core_builder_build_prepared_plan(&intent( + crate::ExSolanaCoreOperation::Secp256k1VerifyInline { + message: message.clone(), + signature: k1_signature.to_vec(), + recovery_id: 1, + ethereum_address: k1_address.to_vec(), + }, + )) + .unwrap_or_else(|error| panic!("unexpected secp256k1 error: {error}")); + assert_eq!(k1_plan.instructions[0].program_id.0, kb_program_ids::SECP256K1_PROGRAM_ID); + assert_eq!(k1_plan.instructions[0].data[0], 1); + assert_eq!( + &k1_plan.instructions[0].data[1..12], + &[32, 0, 0, 12, 0, 0, 97, 0, message.len() as u8, 0, 0] + ); + assert_eq!(&k1_plan.instructions[0].data[12..32], &k1_address); + assert_eq!(&k1_plan.instructions[0].data[32..96], &k1_signature); + assert_eq!(k1_plan.instructions[0].data[96], 1); + assert_eq!(&k1_plan.instructions[0].data[97..], message.as_slice()); + assert_eq!( + solana_secp256k1_program::SIGNATURE_OFFSETS_SERIALIZED_SIZE, + super::SECP256K1_OFFSETS_SIZE + ); + assert_eq!( + solana_secp256k1_program::HASHED_PUBKEY_SERIALIZED_SIZE, + super::SECP256K1_ETHEREUM_ADDRESS_SIZE + ); + } + + #[test] + fn offset_precompile_operations_preserve_official_tables_and_external_references() { + let ed_offsets = crate::ExSolanaCoreEd25519VerificationOffsets { + signature_offset: 0, + signature_instruction_index: 2, + public_key_offset: 64, + public_key_instruction_index: 2, + message_data_offset: 96, + message_data_size: 5, + message_instruction_index: 2, + }; + let ed_plan = crate::executor_solana_core_builder_build_prepared_plan(&intent( + crate::ExSolanaCoreOperation::Ed25519VerifyOffsets { + offsets: vec![ed_offsets], + data: vec![1, 2, 3], + }, + )) + .unwrap_or_else(|error| panic!("unexpected Ed25519 offsets error: {error}")); + let official_ed_offsets = solana_ed25519_program::Ed25519SignatureOffsets { + signature_offset: 0, + signature_instruction_index: 2, + public_key_offset: 64, + public_key_instruction_index: 2, + message_data_offset: 96, + message_data_size: 5, + message_instruction_index: 2, + }; + let mut ed_expected = + solana_ed25519_program::offsets_to_ed25519_instruction(&[official_ed_offsets]); + ed_expected.data.extend_from_slice(&[1, 2, 3]); + assert_matches_instruction(&ed_plan, &ed_expected); + + let k1_offsets = crate::ExSolanaCoreSecp256k1VerificationOffsets { + signature_offset: 9, + signature_instruction_index: 3, + ethereum_address_offset: 74, + ethereum_address_instruction_index: 3, + message_data_offset: 94, + message_data_size: 12, + message_instruction_index: 4, + }; + let k1_plan = crate::executor_solana_core_builder_build_prepared_plan(&intent( + crate::ExSolanaCoreOperation::Secp256k1VerifyOffsets { + offsets: vec![k1_offsets], + data: vec![8, 9], + }, + )) + .unwrap_or_else(|error| panic!("unexpected secp256k1 offsets error: {error}")); + assert_eq!(k1_plan.instructions[0].data, vec![1, 9, 0, 3, 74, 0, 3, 94, 0, 12, 0, 4, 8, 9]); + let official_k1_offsets = solana_secp256k1_program::SecpSignatureOffsets { + signature_offset: 9, + signature_instruction_index: 3, + eth_address_offset: 74, + eth_address_instruction_index: 3, + message_data_offset: 94, + message_data_size: 12, + message_instruction_index: 4, + }; + assert_eq!(official_k1_offsets.signature_offset, k1_offsets.signature_offset); + assert_eq!( + official_k1_offsets.eth_address_instruction_index, + k1_offsets.ethereum_address_instruction_index + ); + + let r1_offsets = crate::ExSolanaCoreSecp256r1VerificationOffsets { + signature_offset: 0, + signature_instruction_index: 5, + public_key_offset: 64, + public_key_instruction_index: 5, + message_data_offset: 97, + message_data_size: 7, + message_instruction_index: 6, + }; + let r1_plan = crate::executor_solana_core_builder_build_prepared_plan(&intent( + crate::ExSolanaCoreOperation::Secp256r1VerifyOffsets { + offsets: vec![r1_offsets], + data: vec![4, 5], + }, + )) + .unwrap_or_else(|error| panic!("unexpected secp256r1 offsets error: {error}")); + assert_eq!( + r1_plan.instructions[0].data, + vec![1, 0, 0, 0, 5, 0, 64, 0, 5, 0, 97, 0, 7, 0, 6, 0, 4, 5] + ); + let official_r1_offsets = solana_secp256r1_program::Secp256r1SignatureOffsets { + signature_offset: 0, + signature_instruction_index: 5, + public_key_offset: 64, + public_key_instruction_index: 5, + message_data_offset: 97, + message_data_size: 7, + message_instruction_index: 6, + }; + assert_eq!(official_r1_offsets.message_data_size, r1_offsets.message_data_size); + } + + #[test] + fn malformed_precompile_contracts_are_rejected_before_simulation() { + let short_signature = crate::executor_solana_core_builder_build_prepared_plan(&intent( + crate::ExSolanaCoreOperation::Ed25519VerifyInline { + message: std::vec::Vec::new(), + signature: vec![0; 63], + public_key: vec![0; 32], + }, + )) + .expect_err("short Ed25519 signature must fail"); + assert_eq!(short_signature.code(), "execution_ed25519_signature_length_invalid"); + + let bad_recovery = crate::executor_solana_core_builder_build_prepared_plan(&intent( + crate::ExSolanaCoreOperation::Secp256k1VerifyInline { + message: std::vec::Vec::new(), + signature: vec![1; 64], + recovery_id: 4, + ethereum_address: vec![2; 20], + }, + )) + .expect_err("invalid recovery id must fail"); + assert_eq!(bad_recovery.code(), "execution_secp256k1_recovery_id_invalid"); + + let high_s = crate::executor_solana_core_builder_build_prepared_plan(&intent( + crate::ExSolanaCoreOperation::Secp256r1VerifyInline { + message: std::vec::Vec::new(), + signature: vec![0xff; 64], + public_key: { + let mut value = vec![1; 33]; + value[0] = 2; + value + }, + }, + )) + .expect_err("high-S secp256r1 signature must fail"); + assert_eq!(high_s.code(), "execution_secp256r1_signature_not_low_s"); + + let empty_offsets = crate::executor_solana_core_builder_build_prepared_plan(&intent( + crate::ExSolanaCoreOperation::Ed25519VerifyOffsets { + offsets: std::vec::Vec::new(), + data: std::vec::Vec::new(), + }, + )) + .expect_err("empty offset table must fail"); + assert_eq!(empty_offsets.code(), "execution_ed25519_offsets_empty"); + + let template = crate::ExSolanaCoreEd25519VerificationOffsets { + signature_offset: 0, + signature_instruction_index: 1, + public_key_offset: 0, + public_key_instruction_index: 1, + message_data_offset: 0, + message_data_size: 0, + message_instruction_index: 1, + }; + let too_many = crate::executor_solana_core_builder_build_prepared_plan(&intent( + crate::ExSolanaCoreOperation::Ed25519VerifyOffsets { + offsets: vec![template; 256], + data: std::vec::Vec::new(), + }, + )) + .expect_err("more than 255 signatures must fail"); + assert_eq!(too_many.code(), "execution_ed25519_offsets_too_many"); + + let local_out_of_bounds = crate::executor_solana_core_builder_build_prepared_plan(&intent( + crate::ExSolanaCoreOperation::Secp256k1VerifyOffsets { + offsets: vec![crate::ExSolanaCoreSecp256k1VerificationOffsets { + signature_offset: 500, + signature_instruction_index: 0, + ethereum_address_offset: 0, + ethereum_address_instruction_index: 2, + message_data_offset: 0, + message_data_size: 0, + message_instruction_index: 2, + }], + data: std::vec::Vec::new(), + }, + )) + .expect_err("self-referenced out-of-bounds signature must fail"); + assert_eq!( + local_out_of_bounds.code(), + "execution_secp256k1_signature_offset_out_of_bounds" + ); + + let mut invalid_local_k1 = vec![0u8; 65]; + invalid_local_k1[64] = 4; + let invalid_local_k1 = crate::executor_solana_core_builder_build_prepared_plan(&intent( + crate::ExSolanaCoreOperation::Secp256k1VerifyOffsets { + offsets: vec![crate::ExSolanaCoreSecp256k1VerificationOffsets { + signature_offset: 12, + signature_instruction_index: 0, + ethereum_address_offset: 0, + ethereum_address_instruction_index: 2, + message_data_offset: 0, + message_data_size: 0, + message_instruction_index: 2, + }], + data: invalid_local_k1, + }, + )) + .expect_err("invalid local secp256k1 recovery id must fail"); + assert_eq!(invalid_local_k1.code(), "execution_secp256k1_recovery_id_invalid"); + + let invalid_local_r1 = crate::executor_solana_core_builder_build_prepared_plan(&intent( + crate::ExSolanaCoreOperation::Secp256r1VerifyOffsets { + offsets: vec![crate::ExSolanaCoreSecp256r1VerificationOffsets { + signature_offset: 16, + signature_instruction_index: u16::MAX, + public_key_offset: 0, + public_key_instruction_index: 2, + message_data_offset: 0, + message_data_size: 0, + message_instruction_index: 2, + }], + data: vec![0xff; 64], + }, + )) + .expect_err("high-S local secp256r1 signature must fail"); + assert_eq!(invalid_local_r1.code(), "execution_secp256r1_signature_not_low_s"); + } +} diff --git a/kb-lib/src/executor/solana/core/slashing.rs b/kb-lib/src/executor/solana/core/slashing.rs new file mode 100644 index 0000000..c6d480a --- /dev/null +++ b/kb-lib/src/executor/solana/core/slashing.rs @@ -0,0 +1,499 @@ +// file: kb-lib/src/executor/solana/core/slashing.rs +// version: 1 + +//! Stateless Slashing Program builders and the atomic duplicate-block proof plan. + +const HASH_BYTES: usize = 32; +const SIGNATURE_BYTES: usize = 64; +const DUPLICATE_BLOCK_PROOF_DATA_BYTES: usize = 305; +const DUPLICATE_BLOCK_PROOF_TYPE: u8 = 1; + +pub(crate) fn executor_solana_core_slashing_build_prepared_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, +) -> kb_core::Result { + return match &intent.operation { + crate::ExSolanaCoreOperation::SlashingCloseViolationReport { + report_account, + destination, + } => build_close_plan(intent, fee_payer, report_account, destination), + crate::ExSolanaCoreOperation::SlashingSubmitDuplicateBlockProof { + proof_account, + report_account, + funder, + report_lamports, + proof_offset, + slot, + node_pubkey, + reporter, + destination, + shred_1_merkle_root, + shred_1_signature, + shred_2_merkle_root, + shred_2_signature, + } => build_duplicate_block_plan( + intent, + fee_payer, + proof_account, + report_account, + funder.as_ref(), + *report_lamports, + *proof_offset, + *slot, + node_pubkey, + reporter, + destination, + shred_1_merkle_root.as_slice(), + shred_1_signature.as_slice(), + shred_2_merkle_root.as_slice(), + shred_2_signature.as_slice(), + ), + _ => std::result::Result::Err(kb_core::Error::new( + "execution_slashing_operation_mismatch", + "Slashing builder received an unrelated Solana core operation", + )), + }; +} + +fn build_close_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + report_account: &crate::MdPubkey, + destination: &crate::MdPubkey, +) -> kb_core::Result { + if report_account == destination { + return std::result::Result::Err(kb_core::Error::new( + "execution_slashing_close_same_account", + "Slashing report and destination accounts must differ", + )); + } + let report = match crate::executor_solana_core_parse_pubkey(report_account, "report_account") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let destination_address = + match crate::executor_solana_core_parse_pubkey(destination, "destination") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let program_id = match crate::executor_solana_core_parse_native_program_id( + kb_program_ids::SLASHING_PROGRAM_ID, + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instruction = solana_instruction::Instruction { + program_id, + accounts: vec![ + solana_instruction::AccountMeta::new(report, false), + solana_instruction::AccountMeta::new(destination_address, false), + ], + data: vec![0], + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_SLASHING_CLOSE_VIOLATION_REPORT_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + std::vec::Vec::new(), + ); +} + +#[allow(clippy::too_many_arguments)] +fn build_duplicate_block_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + proof_account: &crate::MdPubkey, + report_account: &crate::MdPubkey, + funder: std::option::Option<&crate::MdPubkey>, + report_lamports: u64, + proof_offset: u64, + slot: u64, + node_pubkey: &crate::MdPubkey, + reporter: &crate::MdPubkey, + destination: &crate::MdPubkey, + shred_1_merkle_root: &[u8], + shred_1_signature: &[u8], + shred_2_merkle_root: &[u8], + shred_2_signature: &[u8], +) -> kb_core::Result { + if intent.policy.blockhash.kind != crate::ExApiExecutionBlockhashKind::Latest { + return std::result::Result::Err(kb_core::Error::new( + "execution_slashing_duplicate_block_requires_latest_blockhash", + "duplicate-block proof submission must remain the first three transaction instructions and cannot use durable-nonce prepending", + )); + } + if report_lamports == 0 { + return std::result::Result::Err(kb_core::Error::new( + "execution_slashing_report_lamports_zero", + "duplicate-block proof submission requires a positive measured report-account rent amount", + )); + } + if report_account == destination { + return std::result::Result::Err(kb_core::Error::new( + "execution_slashing_destination_is_report", + "Slashing report PDA and close destination must differ", + )); + } + match crate::executor_solana_core_validate_exact_length( + shred_1_merkle_root, + HASH_BYTES, + "execution_slashing_first_root_length_invalid", + "first shred Merkle root", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match crate::executor_solana_core_validate_exact_length( + shred_2_merkle_root, + HASH_BYTES, + "execution_slashing_second_root_length_invalid", + "second shred Merkle root", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match crate::executor_solana_core_validate_exact_length( + shred_1_signature, + SIGNATURE_BYTES, + "execution_slashing_first_signature_length_invalid", + "first shred signature", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match crate::executor_solana_core_validate_exact_length( + shred_2_signature, + SIGNATURE_BYTES, + "execution_slashing_second_signature_length_invalid", + "second shred signature", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let proof = match crate::executor_solana_core_parse_pubkey(proof_account, "proof_account") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let report = match crate::executor_solana_core_parse_pubkey(report_account, "report_account") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let node = match crate::executor_solana_core_parse_pubkey(node_pubkey, "node_pubkey") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let reporter_address = match crate::executor_solana_core_parse_pubkey(reporter, "reporter") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let destination_address = + match crate::executor_solana_core_parse_pubkey(destination, "destination") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let funding_key = match funder { + std::option::Option::Some(value) => value, + std::option::Option::None => reporter, + }; + let funding_address = match crate::executor_solana_core_parse_pubkey(funding_key, "funder") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let slashing_program = match crate::executor_solana_core_parse_native_program_id( + kb_program_ids::SLASHING_PROGRAM_ID, + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let slot_bytes = slot.to_le_bytes(); + let proof_type = [DUPLICATE_BLOCK_PROOF_TYPE]; + let (derived_report, _bump) = solana_pubkey::Pubkey::find_program_address( + &[node.as_ref(), slot_bytes.as_slice(), proof_type.as_slice()], + &slashing_program, + ); + if derived_report != report { + return std::result::Result::Err(kb_core::Error::new( + "execution_slashing_report_pda_mismatch", + format!("supplied report account {report} does not match derived PDA {derived_report}"), + )); + } + if funding_address == report { + return std::result::Result::Err(kb_core::Error::new( + "execution_slashing_funder_is_report", + "Slashing report PDA cannot fund itself", + )); + } + let slashing_data = duplicate_block_instruction_data( + proof_offset, + slot, + &node, + &reporter_address, + &destination_address, + shred_1_merkle_root, + shred_1_signature, + shred_2_merkle_root, + shred_2_signature, + ); + if slashing_data.len() != DUPLICATE_BLOCK_PROOF_DATA_BYTES { + return std::result::Result::Err(kb_core::Error::new( + "execution_slashing_duplicate_block_internal_size_invalid", + "constructed duplicate-block instruction data has an invalid size", + )); + } + let transfer = + solana_system_interface::instruction::transfer(&funding_address, &report, report_lamports); + let sigverify = match duplicate_block_sigverify_instruction() { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let slashing = solana_instruction::Instruction { + program_id: slashing_program, + accounts: vec![ + solana_instruction::AccountMeta::new_readonly(proof, false), + solana_instruction::AccountMeta::new(report, false), + solana_instruction::AccountMeta::new_readonly( + solana_sdk_ids::sysvar::instructions::id(), + false, + ), + solana_instruction::AccountMeta::new_readonly( + solana_sdk_ids::system_program::id(), + false, + ), + ], + data: slashing_data, + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_SLASHING_SUBMIT_DUPLICATE_BLOCK_PROOF_OPERATION, + vec![transfer, sigverify, slashing], + report_lamports, + std::option::Option::None, + vec![(funding_key.clone(), std::string::String::from("slashing_report_funder"))], + ); +} + +#[allow(clippy::too_many_arguments)] +fn duplicate_block_instruction_data( + proof_offset: u64, + slot: u64, + node: &solana_pubkey::Pubkey, + reporter: &solana_pubkey::Pubkey, + destination: &solana_pubkey::Pubkey, + shred_1_merkle_root: &[u8], + shred_1_signature: &[u8], + shred_2_merkle_root: &[u8], + shred_2_signature: &[u8], +) -> std::vec::Vec { + let mut data = std::vec::Vec::with_capacity(DUPLICATE_BLOCK_PROOF_DATA_BYTES); + data.push(1); + data.extend_from_slice(&proof_offset.to_le_bytes()); + data.extend_from_slice(&slot.to_le_bytes()); + data.extend_from_slice(node.as_ref()); + data.extend_from_slice(reporter.as_ref()); + data.extend_from_slice(destination.as_ref()); + data.extend_from_slice(shred_1_merkle_root); + data.extend_from_slice(shred_1_signature); + data.extend_from_slice(shred_2_merkle_root); + data.extend_from_slice(shred_2_signature); + return data; +} + +fn duplicate_block_sigverify_instruction() -> kb_core::Result { + let records = [ + crate::ExSolanaCoreEd25519VerificationOffsets { + signature_offset: 145, + signature_instruction_index: 2, + public_key_offset: 17, + public_key_instruction_index: 2, + message_data_offset: 113, + message_data_size: HASH_BYTES as u16, + message_instruction_index: 2, + }, + crate::ExSolanaCoreEd25519VerificationOffsets { + signature_offset: 241, + signature_instruction_index: 2, + public_key_offset: 17, + public_key_instruction_index: 2, + message_data_offset: 209, + message_data_size: HASH_BYTES as u16, + message_instruction_index: 2, + }, + ]; + let mut data = vec![2, 0]; + for record in records { + data.extend_from_slice(&record.signature_offset.to_le_bytes()); + data.extend_from_slice(&record.signature_instruction_index.to_le_bytes()); + data.extend_from_slice(&record.public_key_offset.to_le_bytes()); + data.extend_from_slice(&record.public_key_instruction_index.to_le_bytes()); + data.extend_from_slice(&record.message_data_offset.to_le_bytes()); + data.extend_from_slice(&record.message_data_size.to_le_bytes()); + data.extend_from_slice(&record.message_instruction_index.to_le_bytes()); + } + let program_id = match crate::executor_solana_core_parse_native_program_id( + kb_program_ids::ED25519_PROGRAM_ID, + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return std::result::Result::Ok(solana_instruction::Instruction { + program_id, + accounts: std::vec::Vec::new(), + data, + }); +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; // rust-rules: trait-import + + fn model(value: solana_pubkey::Pubkey) -> crate::MdPubkey { + return crate::MdPubkey(value.to_string()); + } + + fn policy(signers: &[crate::MdPubkey], spend: u64) -> crate::ExApiExecutionPolicy { + return crate::ExApiExecutionPolicy { + authorized_signers: signers.to_vec(), + blockhash: crate::ExApiExecutionBlockhashPolicy { + kind: crate::ExApiExecutionBlockhashKind::Latest, + max_age_slots: std::option::Option::Some(100), + nonce_account: std::option::Option::None, + nonce_authority: std::option::Option::None, + }, + cost_limit: crate::ExApiExecutionCostLimit { + max_spend_lamports: std::option::Option::Some(spend), + max_fee_lamports: std::option::Option::Some(100_000), + max_compute_unit_price_micro_lamports: std::option::Option::Some(10), + }, + ..crate::ExApiExecutionPolicy::default() + }; + } + + #[test] + fn duplicate_block_plan_matches_official_order_offsets_and_wire() { + let proof = solana_pubkey::Pubkey::new_unique(); + let node = solana_pubkey::Pubkey::new_unique(); + let reporter = solana_pubkey::Pubkey::new_unique(); + let destination = solana_pubkey::Pubkey::new_unique(); + let slot = 42_u64; + let program = solana_pubkey::Pubkey::from_str(kb_program_ids::SLASHING_PROGRAM_ID) + .unwrap_or_else(|error| panic!("invalid id: {error}")); + let slot_bytes = slot.to_le_bytes(); + let (report, _bump) = solana_pubkey::Pubkey::find_program_address( + &[node.as_ref(), slot_bytes.as_slice(), &[1]], + &program, + ); + let payer = model(solana_pubkey::Pubkey::new_unique()); + let intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("slashing-duplicate"), + fee_payer: payer.clone(), + policy: policy(&[payer, model(reporter)], 1_000), + operation: crate::ExSolanaCoreOperation::SlashingSubmitDuplicateBlockProof { + proof_account: model(proof), + report_account: model(report), + funder: std::option::Option::None, + report_lamports: 1_000, + proof_offset: 34, + slot, + node_pubkey: model(node), + reporter: model(reporter), + destination: model(destination), + shred_1_merkle_root: vec![1; 32], + shred_1_signature: vec![2; 64], + shred_2_merkle_root: vec![3; 32], + shred_2_signature: vec![4; 64], + }, + }; + let plan = crate::executor_solana_core_builder_build_prepared_plan(&intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + assert_eq!(plan.instructions.len(), 3); + assert_eq!(plan.instructions[0].program_id.0, kb_program_ids::SYSTEM_PROGRAM_ID); + assert_eq!(plan.instructions[1].program_id.0, kb_program_ids::ED25519_PROGRAM_ID); + assert_eq!(plan.instructions[2].program_id.0, kb_program_ids::SLASHING_PROGRAM_ID); + assert_eq!(plan.instructions[2].data.len(), 305); + assert_eq!(&plan.instructions[2].data[1..9], &34_u64.to_le_bytes()); + assert_eq!(&plan.instructions[2].data[9..17], &slot.to_le_bytes()); + assert_eq!(&plan.instructions[1].data[2..4], &145_u16.to_le_bytes()); + assert_eq!(&plan.instructions[1].data[4..6], &2_u16.to_le_bytes()); + assert_eq!(plan.requested_spend_lamports, 1_000); + } + + #[test] + fn slashing_close_has_no_instruction_signer() { + let report = solana_pubkey::Pubkey::new_unique(); + let destination = solana_pubkey::Pubkey::new_unique(); + let payer = model(solana_pubkey::Pubkey::new_unique()); + let intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("slashing-close"), + fee_payer: payer.clone(), + policy: policy(&[payer], 0), + operation: crate::ExSolanaCoreOperation::SlashingCloseViolationReport { + report_account: model(report), + destination: model(destination), + }, + }; + let plan = crate::executor_solana_core_builder_build_prepared_plan(&intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + assert_eq!(plan.instructions[0].data, vec![0]); + assert!(plan.instructions[0].accounts.iter().all(|account| return !account.is_signer)); + assert_eq!(plan.required_signers.len(), 1); + } + + #[test] + fn duplicate_block_rejects_wrong_pda_sizes_and_durable_nonce() { + let key = model(solana_pubkey::Pubkey::new_unique()); + let mut tpolicy = policy(std::slice::from_ref(&key), 1); + tpolicy.blockhash.kind = crate::ExApiExecutionBlockhashKind::DurableNonce; + tpolicy.blockhash.max_age_slots = std::option::Option::None; + tpolicy.blockhash.nonce_account = std::option::Option::Some(key.clone()); + tpolicy.blockhash.nonce_authority = std::option::Option::Some(key.clone()); + let intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("slashing-invalid"), + fee_payer: key.clone(), + policy: tpolicy, + operation: crate::ExSolanaCoreOperation::SlashingSubmitDuplicateBlockProof { + proof_account: key.clone(), + report_account: key.clone(), + funder: std::option::Option::None, + report_lamports: 1, + proof_offset: 0, + slot: 0, + node_pubkey: key.clone(), + reporter: key.clone(), + destination: key.clone(), + shred_1_merkle_root: vec![0; 31], + shred_1_signature: vec![0; 64], + shred_2_merkle_root: vec![0; 32], + shred_2_signature: vec![0; 64], + }, + }; + assert!(crate::executor_solana_core_builder_build_prepared_plan(&intent).is_err()); + let destination_is_report = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("slashing-same-destination"), + fee_payer: key.clone(), + policy: policy(std::slice::from_ref(&key), 1), + operation: crate::ExSolanaCoreOperation::SlashingSubmitDuplicateBlockProof { + proof_account: key.clone(), + report_account: key.clone(), + funder: std::option::Option::None, + report_lamports: 1, + proof_offset: 0, + slot: 0, + node_pubkey: key.clone(), + reporter: key.clone(), + destination: key, + shred_1_merkle_root: vec![0; 32], + shred_1_signature: vec![0; 64], + shred_2_merkle_root: vec![0; 32], + shred_2_signature: vec![0; 64], + }, + }; + assert!( + crate::executor_solana_core_builder_build_prepared_plan(&destination_is_report) + .is_err() + ); + } +} diff --git a/kb-lib/src/executor/solana/core/stake.rs b/kb-lib/src/executor/solana/core/stake.rs new file mode 100644 index 0000000..3697be0 --- /dev/null +++ b/kb-lib/src/executor/solana/core/stake.rs @@ -0,0 +1,2021 @@ +// file: kb-lib/src/executor/solana/core/stake.rs +// version: 1 + +//! Stake Program instruction builders using the exact wincode-compatible wire contract. + +const SOURCE: &str = "solana-stake-interface@4.3.1"; + +#[allow(dead_code)] +#[derive(Clone, Debug, Eq, PartialEq, wincode::SchemaWrite)] +enum StakeInstructionWire { + Initialize(solana_stake_interface::state::Authorized, solana_stake_interface::state::Lockup), + Authorize([u8; 32], solana_stake_interface::state::StakeAuthorize), + DelegateStake, + Split(u64), + Withdraw(u64), + Deactivate, + SetLockup(StakeLockupArgsWire), + Merge, + AuthorizeWithSeed(StakeAuthorizeWithSeedArgsWire), + InitializeChecked, + AuthorizeChecked(solana_stake_interface::state::StakeAuthorize), + AuthorizeCheckedWithSeed(StakeAuthorizeCheckedWithSeedArgsWire), + SetLockupChecked(StakeLockupCheckedArgsWire), + GetMinimumDelegation, + DeactivateDelinquent, + Redelegate, + MoveStake(u64), + MoveLamports(u64), +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, wincode::SchemaWrite)] +struct StakeLockupArgsWire { + unix_timestamp: std::option::Option, + epoch: std::option::Option, + custodian: std::option::Option<[u8; 32]>, +} + +#[derive(Clone, Debug, Eq, PartialEq, wincode::SchemaWrite)] +struct StakeAuthorizeWithSeedArgsWire { + new_authorized_pubkey: [u8; 32], + stake_authorize: solana_stake_interface::state::StakeAuthorize, + authority_seed: std::string::String, + authority_owner: [u8; 32], +} + +#[derive(Clone, Debug, Eq, PartialEq, wincode::SchemaWrite)] +struct StakeAuthorizeCheckedWithSeedArgsWire { + stake_authorize: solana_stake_interface::state::StakeAuthorize, + authority_seed: std::string::String, + authority_owner: [u8; 32], +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, wincode::SchemaWrite)] +struct StakeLockupCheckedArgsWire { + unix_timestamp: std::option::Option, + epoch: std::option::Option, +} + +pub(crate) fn executor_solana_core_stake_build_prepared_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, +) -> kb_core::Result { + return match &intent.operation { + crate::ExSolanaCoreOperation::StakeInitialize { + stake_account, + staker, + withdrawer, + lockup_unix_timestamp, + lockup_epoch, + lockup_custodian, + } => build_initialize_plan( + intent, + fee_payer, + stake_account, + staker, + withdrawer, + *lockup_unix_timestamp, + *lockup_epoch, + lockup_custodian, + ), + crate::ExSolanaCoreOperation::StakeInitializeChecked { + stake_account, + staker, + withdrawer, + } => build_initialize_checked_plan(intent, fee_payer, stake_account, staker, withdrawer), + crate::ExSolanaCoreOperation::StakeCreateAccount { + from, + stake_account, + staker, + withdrawer, + lockup_unix_timestamp, + lockup_epoch, + lockup_custodian, + lamports, + } => build_create_account_plan( + intent, + fee_payer, + from, + stake_account, + std::option::Option::None, + staker, + withdrawer, + *lockup_unix_timestamp, + *lockup_epoch, + std::option::Option::Some(lockup_custodian), + *lamports, + false, + std::option::Option::None, + ), + crate::ExSolanaCoreOperation::StakeCreateAccountWithSeed { + from, + stake_account, + base, + seed, + staker, + withdrawer, + lockup_unix_timestamp, + lockup_epoch, + lockup_custodian, + lamports, + } => build_create_account_plan( + intent, + fee_payer, + from, + stake_account, + std::option::Option::Some((base, seed.as_str())), + staker, + withdrawer, + *lockup_unix_timestamp, + *lockup_epoch, + std::option::Option::Some(lockup_custodian), + *lamports, + false, + std::option::Option::None, + ), + crate::ExSolanaCoreOperation::StakeCreateAccountChecked { + from, + stake_account, + staker, + withdrawer, + lamports, + } => build_create_account_plan( + intent, + fee_payer, + from, + stake_account, + std::option::Option::None, + staker, + withdrawer, + 0, + 0, + std::option::Option::None, + *lamports, + true, + std::option::Option::None, + ), + crate::ExSolanaCoreOperation::StakeCreateAccountWithSeedChecked { + from, + stake_account, + base, + seed, + staker, + withdrawer, + lamports, + } => build_create_account_plan( + intent, + fee_payer, + from, + stake_account, + std::option::Option::Some((base, seed.as_str())), + staker, + withdrawer, + 0, + 0, + std::option::Option::None, + *lamports, + true, + std::option::Option::None, + ), + crate::ExSolanaCoreOperation::StakeSplit { + stake_account, + stake_authority, + lamports, + split_stake_account, + } => build_split_plan( + intent, + fee_payer, + stake_account, + stake_authority, + *lamports, + split_stake_account, + std::option::Option::None, + ), + crate::ExSolanaCoreOperation::StakeSplitWithSeed { + stake_account, + stake_authority, + lamports, + split_stake_account, + base, + seed, + } => build_split_plan( + intent, + fee_payer, + stake_account, + stake_authority, + *lamports, + split_stake_account, + std::option::Option::Some((base, seed.as_str())), + ), + crate::ExSolanaCoreOperation::StakeMerge { + destination_stake_account, + source_stake_account, + stake_authority, + } => build_merge_plan( + intent, + fee_payer, + destination_stake_account, + source_stake_account, + stake_authority, + ), + crate::ExSolanaCoreOperation::StakeCreateAccountAndDelegate { + from, + stake_account, + vote_account, + staker, + withdrawer, + lockup_unix_timestamp, + lockup_epoch, + lockup_custodian, + lamports, + } => build_create_account_plan( + intent, + fee_payer, + from, + stake_account, + std::option::Option::None, + staker, + withdrawer, + *lockup_unix_timestamp, + *lockup_epoch, + std::option::Option::Some(lockup_custodian), + *lamports, + false, + std::option::Option::Some(vote_account), + ), + crate::ExSolanaCoreOperation::StakeCreateAccountWithSeedAndDelegate { + from, + stake_account, + base, + seed, + vote_account, + staker, + withdrawer, + lockup_unix_timestamp, + lockup_epoch, + lockup_custodian, + lamports, + } => build_create_account_plan( + intent, + fee_payer, + from, + stake_account, + std::option::Option::Some((base, seed.as_str())), + staker, + withdrawer, + *lockup_unix_timestamp, + *lockup_epoch, + std::option::Option::Some(lockup_custodian), + *lamports, + false, + std::option::Option::Some(vote_account), + ), + crate::ExSolanaCoreOperation::StakeAuthorize { + stake_account, + authority, + new_authority, + authorization, + lockup_authority, + } => build_authorize_plan( + intent, + fee_payer, + stake_account, + authority, + std::option::Option::None, + new_authority, + *authorization, + lockup_authority.as_ref(), + false, + ), + crate::ExSolanaCoreOperation::StakeAuthorizeChecked { + stake_account, + authority, + new_authority, + authorization, + lockup_authority, + } => build_authorize_plan( + intent, + fee_payer, + stake_account, + authority, + std::option::Option::None, + new_authority, + *authorization, + lockup_authority.as_ref(), + true, + ), + crate::ExSolanaCoreOperation::StakeAuthorizeWithSeed { + stake_account, + authority_base, + authority_seed, + authority_owner, + new_authority, + authorization, + lockup_authority, + } => build_authorize_plan( + intent, + fee_payer, + stake_account, + authority_base, + std::option::Option::Some((authority_seed.as_str(), authority_owner)), + new_authority, + *authorization, + lockup_authority.as_ref(), + false, + ), + crate::ExSolanaCoreOperation::StakeAuthorizeCheckedWithSeed { + stake_account, + authority_base, + authority_seed, + authority_owner, + new_authority, + authorization, + lockup_authority, + } => build_authorize_plan( + intent, + fee_payer, + stake_account, + authority_base, + std::option::Option::Some((authority_seed.as_str(), authority_owner)), + new_authority, + *authorization, + lockup_authority.as_ref(), + true, + ), + crate::ExSolanaCoreOperation::StakeDelegate { + stake_account, + stake_authority, + vote_account, + } => build_delegate_plan(intent, fee_payer, stake_account, stake_authority, vote_account), + crate::ExSolanaCoreOperation::StakeWithdraw { + stake_account, + withdraw_authority, + to, + lamports, + lockup_authority, + } => build_withdraw_plan( + intent, + fee_payer, + stake_account, + withdraw_authority, + to, + *lamports, + lockup_authority.as_ref(), + ), + crate::ExSolanaCoreOperation::StakeDeactivate { stake_account, stake_authority } => { + build_deactivate_plan(intent, fee_payer, stake_account, stake_authority) + }, + crate::ExSolanaCoreOperation::StakeSetLockup { + stake_account, + authority, + unix_timestamp, + epoch, + custodian, + } => build_set_lockup_plan( + intent, + fee_payer, + stake_account, + authority, + *unix_timestamp, + *epoch, + custodian.as_ref(), + false, + ), + crate::ExSolanaCoreOperation::StakeSetLockupChecked { + stake_account, + authority, + unix_timestamp, + epoch, + new_custodian, + } => build_set_lockup_plan( + intent, + fee_payer, + stake_account, + authority, + *unix_timestamp, + *epoch, + new_custodian.as_ref(), + true, + ), + crate::ExSolanaCoreOperation::StakeGetMinimumDelegation => { + build_get_minimum_delegation_plan(intent, fee_payer) + }, + crate::ExSolanaCoreOperation::StakeDeactivateDelinquent { + stake_account, + delinquent_vote_account, + reference_vote_account, + } => build_deactivate_delinquent_plan( + intent, + fee_payer, + stake_account, + delinquent_vote_account, + reference_vote_account, + ), + crate::ExSolanaCoreOperation::StakeMoveStake { + source_stake_account, + destination_stake_account, + stake_authority, + lamports, + } => build_move_plan( + intent, + fee_payer, + source_stake_account, + destination_stake_account, + stake_authority, + *lamports, + true, + ), + crate::ExSolanaCoreOperation::StakeMoveLamports { + source_stake_account, + destination_stake_account, + stake_authority, + lamports, + } => build_move_plan( + intent, + fee_payer, + source_stake_account, + destination_stake_account, + stake_authority, + *lamports, + false, + ), + _ => std::result::Result::Err(kb_core::Error::new( + "execution_stake_operation_mismatch", + "Stake builder received an unrelated Solana core operation", + )), + }; +} + +#[allow(clippy::too_many_arguments)] +fn build_initialize_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + stake_account: &crate::MdPubkey, + staker: &crate::MdPubkey, + withdrawer: &crate::MdPubkey, + lockup_unix_timestamp: i64, + lockup_epoch: u64, + lockup_custodian: &crate::MdPubkey, +) -> kb_core::Result { + let stake = match crate::executor_solana_core_parse_pubkey(stake_account, "stake_account") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authorized = match authorized(staker, withdrawer) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let custodian = + match crate::executor_solana_core_parse_pubkey(lockup_custodian, "lockup_custodian") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instruction = match stake_instruction( + StakeInstructionWire::Initialize( + authorized, + solana_stake_interface::state::Lockup { + unix_timestamp: lockup_unix_timestamp, + epoch: lockup_epoch, + custodian, + }, + ), + vec![ + solana_instruction::AccountMeta::new(stake, false), + solana_instruction::AccountMeta::new_readonly( + solana_sdk_ids::sysvar::rent::id(), + false, + ), + ], + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_STAKE_INITIALIZE_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + std::vec::Vec::new(), + ); +} + +fn build_initialize_checked_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + stake_account: &crate::MdPubkey, + staker: &crate::MdPubkey, + withdrawer: &crate::MdPubkey, +) -> kb_core::Result { + let stake = match crate::executor_solana_core_parse_pubkey(stake_account, "stake_account") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let staker_address = match crate::executor_solana_core_parse_pubkey(staker, "staker") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let withdrawer_address = + match crate::executor_solana_core_parse_pubkey(withdrawer, "withdrawer") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instruction = match stake_instruction( + StakeInstructionWire::InitializeChecked, + vec![ + solana_instruction::AccountMeta::new(stake, false), + solana_instruction::AccountMeta::new_readonly( + solana_sdk_ids::sysvar::rent::id(), + false, + ), + solana_instruction::AccountMeta::new_readonly(staker_address, false), + solana_instruction::AccountMeta::new_readonly(withdrawer_address, true), + ], + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_STAKE_INITIALIZE_CHECKED_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + vec![(withdrawer.clone(), std::string::String::from("stake_withdraw_authority"))], + ); +} + +#[allow(clippy::too_many_arguments)] +fn build_create_account_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + from: &crate::MdPubkey, + stake_account: &crate::MdPubkey, + seed_contract: std::option::Option<(&crate::MdPubkey, &str)>, + staker: &crate::MdPubkey, + withdrawer: &crate::MdPubkey, + lockup_unix_timestamp: i64, + lockup_epoch: u64, + lockup_custodian: std::option::Option<&crate::MdPubkey>, + lamports: u64, + checked: bool, + vote_account: std::option::Option<&crate::MdPubkey>, +) -> kb_core::Result { + match crate::executor_solana_core_validate_positive_u64( + lamports, + "execution_stake_create_lamports_zero", + "Stake account creation requires a positive measured rent-exempt lamport amount", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + if from == stake_account { + return std::result::Result::Err(kb_core::Error::new( + "execution_stake_create_same_account", + "Stake funding and new stake accounts must differ", + )); + } + let from_address = match crate::executor_solana_core_parse_pubkey(from, "from") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let stake_address = + match crate::executor_solana_core_parse_pubkey(stake_account, "stake_account") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let stake_program = match stake_program_id() { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let mut signer_roles = vec![(from.clone(), std::string::String::from("stake_funding_account"))]; + let create = match seed_contract { + std::option::Option::Some((base, seed)) => { + let base_address = match crate::executor_solana_core_parse_pubkey(base, "base") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + match crate::executor_solana_core_validate_seeded_address( + &stake_address, + &base_address, + seed, + &stake_program, + "stake_account", + "execution_stake_seed_invalid", + "execution_stake_seeded_address_mismatch", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + signer_roles.push((base.clone(), std::string::String::from("stake_seed_base"))); + solana_system_interface::instruction::create_account_with_seed( + &from_address, + &stake_address, + &base_address, + seed, + lamports, + solana_stake_interface::state::StakeStateV2::size_of() as u64, + &stake_program, + ) + }, + std::option::Option::None => { + signer_roles + .push((stake_account.clone(), std::string::String::from("new_stake_account"))); + solana_system_interface::instruction::create_account( + &from_address, + &stake_address, + lamports, + solana_stake_interface::state::StakeStateV2::size_of() as u64, + &stake_program, + ) + }, + }; + let mut instructions = vec![create]; + if checked { + let staker_address = match crate::executor_solana_core_parse_pubkey(staker, "staker") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let withdrawer_address = + match crate::executor_solana_core_parse_pubkey(withdrawer, "withdrawer") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let initialize = match stake_instruction( + StakeInstructionWire::InitializeChecked, + vec![ + solana_instruction::AccountMeta::new(stake_address, false), + solana_instruction::AccountMeta::new_readonly( + solana_sdk_ids::sysvar::rent::id(), + false, + ), + solana_instruction::AccountMeta::new_readonly(staker_address, false), + solana_instruction::AccountMeta::new_readonly(withdrawer_address, true), + ], + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + instructions.push(initialize); + signer_roles + .push((withdrawer.clone(), std::string::String::from("stake_withdraw_authority"))); + } else { + let authorized = match authorized(staker, withdrawer) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let lockup_custodian = match lockup_custodian { + std::option::Option::Some(value) => value, + std::option::Option::None => { + return std::result::Result::Err(kb_core::Error::new( + "execution_stake_lockup_custodian_missing", + "unchecked Stake account creation requires a lockup custodian", + )); + }, + }; + let custodian = + match crate::executor_solana_core_parse_pubkey(lockup_custodian, "lockup_custodian") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let initialize = match stake_instruction( + StakeInstructionWire::Initialize( + authorized, + solana_stake_interface::state::Lockup { + unix_timestamp: lockup_unix_timestamp, + epoch: lockup_epoch, + custodian, + }, + ), + vec![ + solana_instruction::AccountMeta::new(stake_address, false), + solana_instruction::AccountMeta::new_readonly( + solana_sdk_ids::sysvar::rent::id(), + false, + ), + ], + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + instructions.push(initialize); + } + let operation_code = match (seed_contract.is_some(), checked, vote_account.is_some()) { + (false, false, false) => crate::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_OPERATION, + (true, false, false) => crate::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_WITH_SEED_OPERATION, + (false, true, false) => crate::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_CHECKED_OPERATION, + (true, true, false) => { + crate::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_WITH_SEED_CHECKED_OPERATION + }, + (false, false, true) => crate::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_AND_DELEGATE_OPERATION, + (true, false, true) => { + crate::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_WITH_SEED_AND_DELEGATE_OPERATION + }, + (_, true, true) => { + return std::result::Result::Err(kb_core::Error::new( + "execution_stake_checked_create_delegate_unavailable", + "the official Stake interface does not expose a checked create-and-delegate helper", + )); + }, + }; + if let std::option::Option::Some(vote) = vote_account { + let delegate = match delegate_instruction(stake_account, staker, vote) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + instructions.push(delegate); + signer_roles.push((staker.clone(), std::string::String::from("stake_authority"))); + } + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + operation_code, + instructions, + lamports, + std::option::Option::None, + signer_roles, + ); +} + +fn build_split_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + stake_account: &crate::MdPubkey, + stake_authority: &crate::MdPubkey, + lamports: u64, + split_stake_account: &crate::MdPubkey, + seed_contract: std::option::Option<(&crate::MdPubkey, &str)>, +) -> kb_core::Result { + match crate::executor_solana_core_validate_positive_u64( + lamports, + "execution_stake_split_lamports_zero", + "Stake split requires a positive lamport amount", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match crate::executor_solana_core_validate_distinct_pubkeys( + stake_account, + split_stake_account, + "execution_stake_split_same_account", + "Stake split source and destination accounts must differ", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let source = match crate::executor_solana_core_parse_pubkey(stake_account, "stake_account") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let destination = match crate::executor_solana_core_parse_pubkey( + split_stake_account, + "split_stake_account", + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority = + match crate::executor_solana_core_parse_pubkey(stake_authority, "stake_authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let stake_program = match stake_program_id() { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let mut signer_roles = + vec![(stake_authority.clone(), std::string::String::from("stake_authority"))]; + let mut instructions = match seed_contract { + std::option::Option::Some((base, seed)) => { + let base_address = match crate::executor_solana_core_parse_pubkey(base, "base") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + match crate::executor_solana_core_validate_seeded_address( + &destination, + &base_address, + seed, + &stake_program, + "split_stake_account", + "execution_stake_seed_invalid", + "execution_stake_seeded_address_mismatch", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + signer_roles.push((base.clone(), std::string::String::from("stake_split_seed_base"))); + vec![solana_system_interface::instruction::allocate_with_seed( + &destination, + &base_address, + seed, + solana_stake_interface::state::StakeStateV2::size_of() as u64, + &stake_program, + )] + }, + std::option::Option::None => { + signer_roles.push(( + split_stake_account.clone(), + std::string::String::from("new_split_stake_account"), + )); + vec![ + solana_system_interface::instruction::allocate( + &destination, + solana_stake_interface::state::StakeStateV2::size_of() as u64, + ), + solana_system_interface::instruction::assign(&destination, &stake_program), + ] + }, + }; + let split = match stake_instruction( + StakeInstructionWire::Split(lamports), + vec![ + solana_instruction::AccountMeta::new(source, false), + solana_instruction::AccountMeta::new(destination, false), + solana_instruction::AccountMeta::new_readonly(authority, true), + ], + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + instructions.push(split); + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + if seed_contract.is_some() { + crate::EX_SOLANA_CORE_STAKE_SPLIT_WITH_SEED_OPERATION + } else { + crate::EX_SOLANA_CORE_STAKE_SPLIT_OPERATION + }, + instructions, + lamports, + std::option::Option::None, + signer_roles, + ); +} + +fn build_merge_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + destination_stake_account: &crate::MdPubkey, + source_stake_account: &crate::MdPubkey, + stake_authority: &crate::MdPubkey, +) -> kb_core::Result { + match crate::executor_solana_core_validate_distinct_pubkeys( + destination_stake_account, + source_stake_account, + "execution_stake_merge_same_account", + "Stake merge source and destination accounts must differ", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let destination = match crate::executor_solana_core_parse_pubkey( + destination_stake_account, + "destination_stake_account", + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let source = match crate::executor_solana_core_parse_pubkey( + source_stake_account, + "source_stake_account", + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority = + match crate::executor_solana_core_parse_pubkey(stake_authority, "stake_authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instruction = match stake_instruction( + StakeInstructionWire::Merge, + vec![ + solana_instruction::AccountMeta::new(destination, false), + solana_instruction::AccountMeta::new(source, false), + solana_instruction::AccountMeta::new_readonly( + solana_sdk_ids::sysvar::clock::id(), + false, + ), + solana_instruction::AccountMeta::new_readonly( + solana_sdk_ids::sysvar::stake_history::id(), + false, + ), + solana_instruction::AccountMeta::new_readonly(authority, true), + ], + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_STAKE_MERGE_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + vec![(stake_authority.clone(), std::string::String::from("stake_authority"))], + ); +} + +#[allow(clippy::too_many_arguments)] +fn build_authorize_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + stake_account: &crate::MdPubkey, + authority_or_base: &crate::MdPubkey, + seed_contract: std::option::Option<(&str, &crate::MdPubkey)>, + new_authority: &crate::MdPubkey, + authorization: crate::ExSolanaCoreStakeAuthorizationKind, + lockup_authority: std::option::Option<&crate::MdPubkey>, + checked: bool, +) -> kb_core::Result { + let stake = match crate::executor_solana_core_parse_pubkey(stake_account, "stake_account") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority = match crate::executor_solana_core_parse_pubkey(authority_or_base, "authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let new_authority_address = + match crate::executor_solana_core_parse_pubkey(new_authority, "new_authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let mut accounts = vec![solana_instruction::AccountMeta::new(stake, false)]; + let wire = match seed_contract { + std::option::Option::Some((seed, owner)) => { + let owner_address = + match crate::executor_solana_core_parse_pubkey(owner, "authority_owner") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + accounts.push(solana_instruction::AccountMeta::new_readonly(authority, true)); + accounts.push(solana_instruction::AccountMeta::new_readonly( + solana_sdk_ids::sysvar::clock::id(), + false, + )); + if checked { + accounts.push(solana_instruction::AccountMeta::new_readonly( + new_authority_address, + true, + )); + StakeInstructionWire::AuthorizeCheckedWithSeed( + StakeAuthorizeCheckedWithSeedArgsWire { + stake_authorize: authorization.as_official(), + authority_seed: std::string::String::from(seed), + authority_owner: owner_address.to_bytes(), + }, + ) + } else { + StakeInstructionWire::AuthorizeWithSeed(StakeAuthorizeWithSeedArgsWire { + new_authorized_pubkey: new_authority_address.to_bytes(), + stake_authorize: authorization.as_official(), + authority_seed: std::string::String::from(seed), + authority_owner: owner_address.to_bytes(), + }) + } + }, + std::option::Option::None => { + accounts.push(solana_instruction::AccountMeta::new_readonly( + solana_sdk_ids::sysvar::clock::id(), + false, + )); + accounts.push(solana_instruction::AccountMeta::new_readonly(authority, true)); + if checked { + accounts.push(solana_instruction::AccountMeta::new_readonly( + new_authority_address, + true, + )); + StakeInstructionWire::AuthorizeChecked(authorization.as_official()) + } else { + StakeInstructionWire::Authorize( + new_authority_address.to_bytes(), + authorization.as_official(), + ) + } + }, + }; + let mut signer_roles = + vec![(authority_or_base.clone(), std::string::String::from("stake_current_authority"))]; + if checked { + signer_roles + .push((new_authority.clone(), std::string::String::from("stake_new_authority"))); + } + if let std::option::Option::Some(custodian) = lockup_authority { + let custodian_address = + match crate::executor_solana_core_parse_pubkey(custodian, "lockup_authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + accounts.push(solana_instruction::AccountMeta::new_readonly(custodian_address, true)); + signer_roles.push((custodian.clone(), std::string::String::from("stake_lockup_authority"))); + } + let instruction = match stake_instruction(wire, accounts) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let operation_code = match (seed_contract.is_some(), checked) { + (false, false) => crate::EX_SOLANA_CORE_STAKE_AUTHORIZE_OPERATION, + (false, true) => crate::EX_SOLANA_CORE_STAKE_AUTHORIZE_CHECKED_OPERATION, + (true, false) => crate::EX_SOLANA_CORE_STAKE_AUTHORIZE_WITH_SEED_OPERATION, + (true, true) => crate::EX_SOLANA_CORE_STAKE_AUTHORIZE_CHECKED_WITH_SEED_OPERATION, + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + operation_code, + vec![instruction], + 0, + std::option::Option::None, + signer_roles, + ); +} + +fn delegate_instruction( + stake_account: &crate::MdPubkey, + stake_authority: &crate::MdPubkey, + vote_account: &crate::MdPubkey, +) -> kb_core::Result { + let stake = match crate::executor_solana_core_parse_pubkey(stake_account, "stake_account") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority = + match crate::executor_solana_core_parse_pubkey(stake_authority, "stake_authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let vote = match crate::executor_solana_core_parse_pubkey(vote_account, "vote_account") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let stake_config = match crate::executor_solana_core_parse_native_program_id( + kb_program_ids::STAKE_CONFIG_ACCOUNT_ID, + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return stake_instruction( + StakeInstructionWire::DelegateStake, + vec![ + solana_instruction::AccountMeta::new(stake, false), + solana_instruction::AccountMeta::new_readonly(vote, false), + solana_instruction::AccountMeta::new_readonly( + solana_sdk_ids::sysvar::clock::id(), + false, + ), + solana_instruction::AccountMeta::new_readonly( + solana_sdk_ids::sysvar::stake_history::id(), + false, + ), + solana_instruction::AccountMeta::new_readonly(stake_config, false), + solana_instruction::AccountMeta::new_readonly(authority, true), + ], + ); +} + +fn build_delegate_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + stake_account: &crate::MdPubkey, + stake_authority: &crate::MdPubkey, + vote_account: &crate::MdPubkey, +) -> kb_core::Result { + let instruction = match delegate_instruction(stake_account, stake_authority, vote_account) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_STAKE_DELEGATE_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + vec![(stake_authority.clone(), std::string::String::from("stake_authority"))], + ); +} + +fn build_withdraw_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + stake_account: &crate::MdPubkey, + withdraw_authority: &crate::MdPubkey, + to: &crate::MdPubkey, + lamports: u64, + lockup_authority: std::option::Option<&crate::MdPubkey>, +) -> kb_core::Result { + match crate::executor_solana_core_validate_positive_u64( + lamports, + "execution_stake_withdraw_lamports_zero", + "Stake withdrawal requires a positive lamport amount", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match crate::executor_solana_core_validate_distinct_pubkeys( + stake_account, + to, + "execution_stake_withdraw_same_account", + "Stake withdrawal source and destination accounts must differ", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let stake = match crate::executor_solana_core_parse_pubkey(stake_account, "stake_account") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let destination = match crate::executor_solana_core_parse_pubkey(to, "to") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let withdrawer = + match crate::executor_solana_core_parse_pubkey(withdraw_authority, "withdraw_authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let mut accounts = vec![ + solana_instruction::AccountMeta::new(stake, false), + solana_instruction::AccountMeta::new(destination, false), + solana_instruction::AccountMeta::new_readonly(solana_sdk_ids::sysvar::clock::id(), false), + solana_instruction::AccountMeta::new_readonly( + solana_sdk_ids::sysvar::stake_history::id(), + false, + ), + solana_instruction::AccountMeta::new_readonly(withdrawer, true), + ]; + let mut signer_roles = vec![( + withdraw_authority.clone(), + std::string::String::from("stake_withdraw_authority"), + )]; + if let std::option::Option::Some(custodian) = lockup_authority { + let custodian_address = + match crate::executor_solana_core_parse_pubkey(custodian, "lockup_authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + accounts.push(solana_instruction::AccountMeta::new_readonly(custodian_address, true)); + signer_roles.push((custodian.clone(), std::string::String::from("stake_lockup_authority"))); + } + let instruction = match stake_instruction(StakeInstructionWire::Withdraw(lamports), accounts) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_STAKE_WITHDRAW_OPERATION, + vec![instruction], + lamports, + std::option::Option::None, + signer_roles, + ); +} + +fn build_deactivate_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + stake_account: &crate::MdPubkey, + stake_authority: &crate::MdPubkey, +) -> kb_core::Result { + let stake = match crate::executor_solana_core_parse_pubkey(stake_account, "stake_account") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority = + match crate::executor_solana_core_parse_pubkey(stake_authority, "stake_authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instruction = match stake_instruction( + StakeInstructionWire::Deactivate, + vec![ + solana_instruction::AccountMeta::new(stake, false), + solana_instruction::AccountMeta::new_readonly( + solana_sdk_ids::sysvar::clock::id(), + false, + ), + solana_instruction::AccountMeta::new_readonly(authority, true), + ], + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_STAKE_DEACTIVATE_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + vec![(stake_authority.clone(), std::string::String::from("stake_authority"))], + ); +} + +#[allow(clippy::too_many_arguments)] +fn build_set_lockup_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + stake_account: &crate::MdPubkey, + authority: &crate::MdPubkey, + unix_timestamp: std::option::Option, + epoch: std::option::Option, + custodian: std::option::Option<&crate::MdPubkey>, + checked: bool, +) -> kb_core::Result { + if unix_timestamp.is_none() && epoch.is_none() && custodian.is_none() { + return std::result::Result::Err(kb_core::Error::new( + "execution_stake_lockup_empty", + "Stake lockup update must change at least one field", + )); + } + let stake = match crate::executor_solana_core_parse_pubkey(stake_account, "stake_account") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority_address = match crate::executor_solana_core_parse_pubkey(authority, "authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let mut accounts = vec![ + solana_instruction::AccountMeta::new(stake, false), + solana_instruction::AccountMeta::new_readonly(authority_address, true), + ]; + let mut signer_roles = vec![( + authority.clone(), + std::string::String::from("stake_lockup_or_withdraw_authority"), + )]; + let wire = if checked { + if let std::option::Option::Some(new_custodian) = custodian { + let new_custodian_address = + match crate::executor_solana_core_parse_pubkey(new_custodian, "new_custodian") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + accounts + .push(solana_instruction::AccountMeta::new_readonly(new_custodian_address, true)); + signer_roles.push(( + new_custodian.clone(), + std::string::String::from("stake_new_lockup_authority"), + )); + } + StakeInstructionWire::SetLockupChecked(StakeLockupCheckedArgsWire { unix_timestamp, epoch }) + } else { + let custodian_bytes = match custodian { + std::option::Option::Some(value) => { + let parsed = match crate::executor_solana_core_parse_pubkey(value, "custodian") { + std::result::Result::Ok(address) => address, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + std::option::Option::Some(parsed.to_bytes()) + }, + std::option::Option::None => std::option::Option::None, + }; + StakeInstructionWire::SetLockup(StakeLockupArgsWire { + unix_timestamp, + epoch, + custodian: custodian_bytes, + }) + }; + let instruction = match stake_instruction(wire, accounts) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + if checked { + crate::EX_SOLANA_CORE_STAKE_SET_LOCKUP_CHECKED_OPERATION + } else { + crate::EX_SOLANA_CORE_STAKE_SET_LOCKUP_OPERATION + }, + vec![instruction], + 0, + std::option::Option::None, + signer_roles, + ); +} + +fn build_get_minimum_delegation_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, +) -> kb_core::Result { + let instruction = + match stake_instruction(StakeInstructionWire::GetMinimumDelegation, std::vec::Vec::new()) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_STAKE_GET_MINIMUM_DELEGATION_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + std::vec::Vec::new(), + ); +} + +fn build_deactivate_delinquent_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + stake_account: &crate::MdPubkey, + delinquent_vote_account: &crate::MdPubkey, + reference_vote_account: &crate::MdPubkey, +) -> kb_core::Result { + match crate::executor_solana_core_validate_distinct_pubkeys( + delinquent_vote_account, + reference_vote_account, + "execution_stake_delinquent_reference_same_account", + "delinquent and reference vote accounts must differ", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let stake = match crate::executor_solana_core_parse_pubkey(stake_account, "stake_account") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let delinquent = match crate::executor_solana_core_parse_pubkey( + delinquent_vote_account, + "delinquent_vote_account", + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let reference = match crate::executor_solana_core_parse_pubkey( + reference_vote_account, + "reference_vote_account", + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instruction = match stake_instruction( + StakeInstructionWire::DeactivateDelinquent, + vec![ + solana_instruction::AccountMeta::new(stake, false), + solana_instruction::AccountMeta::new_readonly(delinquent, false), + solana_instruction::AccountMeta::new_readonly(reference, false), + ], + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_STAKE_DEACTIVATE_DELINQUENT_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + std::vec::Vec::new(), + ); +} + +fn build_move_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + source_stake_account: &crate::MdPubkey, + destination_stake_account: &crate::MdPubkey, + stake_authority: &crate::MdPubkey, + lamports: u64, + move_stake: bool, +) -> kb_core::Result { + match crate::executor_solana_core_validate_positive_u64( + lamports, + "execution_stake_move_lamports_zero", + "Stake move requires a positive lamport amount", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match crate::executor_solana_core_validate_distinct_pubkeys( + source_stake_account, + destination_stake_account, + "execution_stake_move_same_account", + "Stake move source and destination accounts must differ", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let source = match crate::executor_solana_core_parse_pubkey( + source_stake_account, + "source_stake_account", + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let destination = match crate::executor_solana_core_parse_pubkey( + destination_stake_account, + "destination_stake_account", + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority = + match crate::executor_solana_core_parse_pubkey(stake_authority, "stake_authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let wire = if move_stake { + StakeInstructionWire::MoveStake(lamports) + } else { + StakeInstructionWire::MoveLamports(lamports) + }; + let instruction = match stake_instruction( + wire, + vec![ + solana_instruction::AccountMeta::new(source, false), + solana_instruction::AccountMeta::new(destination, false), + solana_instruction::AccountMeta::new_readonly(authority, true), + ], + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + if move_stake { + crate::EX_SOLANA_CORE_STAKE_MOVE_STAKE_OPERATION + } else { + crate::EX_SOLANA_CORE_STAKE_MOVE_LAMPORTS_OPERATION + }, + vec![instruction], + lamports, + std::option::Option::None, + vec![(stake_authority.clone(), std::string::String::from("stake_authority"))], + ); +} + +fn authorized( + staker: &crate::MdPubkey, + withdrawer: &crate::MdPubkey, +) -> kb_core::Result { + let staker_address = match crate::executor_solana_core_parse_pubkey(staker, "staker") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let withdrawer_address = + match crate::executor_solana_core_parse_pubkey(withdrawer, "withdrawer") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return std::result::Result::Ok(solana_stake_interface::state::Authorized { + staker: staker_address, + withdrawer: withdrawer_address, + }); +} + +fn stake_instruction( + wire: StakeInstructionWire, + accounts: std::vec::Vec, +) -> kb_core::Result { + let data = match wincode::serialize(&wire) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => { + return std::result::Result::Err(kb_core::Error::new( + "execution_stake_wincode_failed", + format!("cannot serialize {SOURCE} instruction: {error}"), + )); + }, + }; + let program_id = match stake_program_id() { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return std::result::Result::Ok(solana_instruction::Instruction { program_id, accounts, data }); +} + +fn stake_program_id() -> kb_core::Result { + return crate::executor_solana_core_parse_native_program_id(kb_program_ids::STAKE_PROGRAM_ID); +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; // rust-rules: trait-import + + fn model(value: solana_pubkey::Pubkey) -> crate::MdPubkey { + return crate::MdPubkey(value.to_string()); + } + + fn policy(signers: &[crate::MdPubkey], spend: u64) -> crate::ExApiExecutionPolicy { + return crate::ExApiExecutionPolicy { + authorized_signers: signers.to_vec(), + cost_limit: crate::ExApiExecutionCostLimit { + max_spend_lamports: std::option::Option::Some(spend), + max_fee_lamports: std::option::Option::Some(100_000), + max_compute_unit_price_micro_lamports: std::option::Option::Some(10), + }, + ..crate::ExApiExecutionPolicy::default() + }; + } + + fn base_intent( + operation: crate::ExSolanaCoreOperation, + signers: &[crate::MdPubkey], + spend: u64, + ) -> crate::ExSolanaCoreExecutionIntent { + let payer = model(solana_pubkey::Pubkey::new_unique()); + let mut authorized = vec![payer.clone()]; + authorized.extend_from_slice(signers); + return crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("stake-test"), + fee_payer: payer, + policy: policy(authorized.as_slice(), spend), + operation, + }; + } + + #[test] + fn initialize_variants_match_official_wire_and_accounts() { + let stake = model(solana_pubkey::Pubkey::new_unique()); + let staker = model(solana_pubkey::Pubkey::new_unique()); + let withdrawer = model(solana_pubkey::Pubkey::new_unique()); + let custodian = model(solana_pubkey::Pubkey::new_unique()); + let unchecked = base_intent( + crate::ExSolanaCoreOperation::StakeInitialize { + stake_account: stake.clone(), + staker: staker.clone(), + withdrawer: withdrawer.clone(), + lockup_unix_timestamp: -1, + lockup_epoch: 9, + lockup_custodian: custodian, + }, + &[], + 0, + ); + let unchecked_plan = crate::executor_solana_core_builder_build_prepared_plan(&unchecked) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + assert_eq!(&unchecked_plan.instructions[0].data[..4], &0_u32.to_le_bytes()); + assert_eq!(unchecked_plan.instructions[0].accounts.len(), 2); + assert_eq!(unchecked_plan.required_signers.len(), 1); + let checked = base_intent( + crate::ExSolanaCoreOperation::StakeInitializeChecked { + stake_account: stake, + staker, + withdrawer: withdrawer.clone(), + }, + std::slice::from_ref(&withdrawer), + 0, + ); + let checked_plan = crate::executor_solana_core_builder_build_prepared_plan(&checked) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + assert_eq!(&checked_plan.instructions[0].data[..4], &9_u32.to_le_bytes()); + assert_eq!(checked_plan.instructions[0].accounts.len(), 4); + assert!(checked_plan.instructions[0].accounts[3].is_signer); + } + + #[test] + fn create_account_variants_preserve_official_instruction_order() { + let from = model(solana_pubkey::Pubkey::new_unique()); + let stake = model(solana_pubkey::Pubkey::new_unique()); + let staker = model(solana_pubkey::Pubkey::new_unique()); + let withdrawer = model(solana_pubkey::Pubkey::new_unique()); + let custodian = model(solana_pubkey::Pubkey::default()); + let intent = base_intent( + crate::ExSolanaCoreOperation::StakeCreateAccount { + from: from.clone(), + stake_account: stake.clone(), + staker, + withdrawer, + lockup_unix_timestamp: 0, + lockup_epoch: 0, + lockup_custodian: custodian, + lamports: 500, + }, + &[from.clone(), stake.clone()], + 500, + ); + let plan = crate::executor_solana_core_builder_build_prepared_plan(&intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + assert_eq!(plan.instructions.len(), 2); + assert_eq!(plan.instructions[0].program_id.0, kb_program_ids::SYSTEM_PROGRAM_ID); + assert_eq!(plan.instructions[1].program_id.0, kb_program_ids::STAKE_PROGRAM_ID); + assert_eq!(plan.requested_spend_lamports, 500); + let checked_stake = model(solana_pubkey::Pubkey::new_unique()); + let checked_withdrawer = model(solana_pubkey::Pubkey::new_unique()); + let checked = base_intent( + crate::ExSolanaCoreOperation::StakeCreateAccountChecked { + from: from.clone(), + stake_account: checked_stake.clone(), + staker: model(solana_pubkey::Pubkey::new_unique()), + withdrawer: checked_withdrawer.clone(), + lamports: 501, + }, + &[from, checked_stake, checked_withdrawer], + 501, + ); + let checked_plan = crate::executor_solana_core_builder_build_prepared_plan(&checked) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + assert_eq!(checked_plan.instructions.len(), 2); + assert_eq!(&checked_plan.instructions[1].data[..4], &9_u32.to_le_bytes()); + assert!(checked_plan.instructions[1].accounts[3].is_signer); + } + + #[test] + fn seeded_create_and_split_validate_derivations_and_signers() { + let from = solana_pubkey::Pubkey::new_unique(); + let base = solana_pubkey::Pubkey::new_unique(); + let stake_program = solana_pubkey::Pubkey::from_str(kb_program_ids::STAKE_PROGRAM_ID) + .unwrap_or_else(|error| panic!("invalid Stake id: {error}")); + let seed = "stake-seed"; + let stake = solana_pubkey::Pubkey::create_with_seed(&base, seed, &stake_program) + .unwrap_or_else(|error| panic!("seed failure: {error}")); + let staker = model(solana_pubkey::Pubkey::new_unique()); + let withdrawer = model(solana_pubkey::Pubkey::new_unique()); + let checked = base_intent( + crate::ExSolanaCoreOperation::StakeCreateAccountWithSeedChecked { + from: model(from), + stake_account: model(stake), + base: model(base), + seed: std::string::String::from(seed), + staker, + withdrawer: withdrawer.clone(), + lamports: 700, + }, + &[model(from), model(base), withdrawer], + 700, + ); + let plan = crate::executor_solana_core_builder_build_prepared_plan(&checked) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + assert_eq!(plan.instructions.len(), 2); + assert_eq!(&plan.instructions[1].data[..4], &9_u32.to_le_bytes()); + let split_destination = + solana_pubkey::Pubkey::create_with_seed(&base, "split-seed", &stake_program) + .unwrap_or_else(|error| panic!("seed failure: {error}")); + let authority = model(solana_pubkey::Pubkey::new_unique()); + let split = base_intent( + crate::ExSolanaCoreOperation::StakeSplitWithSeed { + stake_account: model(stake), + stake_authority: authority.clone(), + lamports: 10, + split_stake_account: model(split_destination), + base: model(base), + seed: std::string::String::from("split-seed"), + }, + &[model(base), authority.clone()], + 10, + ); + let split_plan = crate::executor_solana_core_builder_build_prepared_plan(&split) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + assert_eq!(split_plan.instructions.len(), 2); + assert_eq!(&split_plan.instructions[1].data[..4], &3_u32.to_le_bytes()); + let unchecked_seed = "stake-unchecked"; + let unchecked_stake = + solana_pubkey::Pubkey::create_with_seed(&base, unchecked_seed, &stake_program) + .unwrap_or_else(|error| panic!("seed failure: {error}")); + let unchecked = base_intent( + crate::ExSolanaCoreOperation::StakeCreateAccountWithSeed { + from: model(from), + stake_account: model(unchecked_stake), + base: model(base), + seed: std::string::String::from(unchecked_seed), + staker: model(solana_pubkey::Pubkey::new_unique()), + withdrawer: model(solana_pubkey::Pubkey::new_unique()), + lockup_unix_timestamp: 0, + lockup_epoch: 0, + lockup_custodian: model(solana_pubkey::Pubkey::default()), + lamports: 800, + }, + &[model(from), model(base)], + 800, + ); + let unchecked_plan = crate::executor_solana_core_builder_build_prepared_plan(&unchecked) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + assert_eq!(unchecked_plan.instructions.len(), 2); + assert_eq!(&unchecked_plan.instructions[1].data[..4], &0_u32.to_le_bytes()); + let split_key = model(solana_pubkey::Pubkey::new_unique()); + let unseeded_split = base_intent( + crate::ExSolanaCoreOperation::StakeSplit { + stake_account: model(stake), + stake_authority: authority.clone(), + lamports: 11, + split_stake_account: split_key.clone(), + }, + &[authority, split_key], + 11, + ); + let unseeded_split_plan = + crate::executor_solana_core_builder_build_prepared_plan(&unseeded_split) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + assert_eq!(unseeded_split_plan.instructions.len(), 3); + assert_eq!(&unseeded_split_plan.instructions[2].data[..4], &3_u32.to_le_bytes()); + } + + #[test] + fn create_and_delegate_compound_plans_append_delegate_last() { + let from = model(solana_pubkey::Pubkey::new_unique()); + let stake = model(solana_pubkey::Pubkey::new_unique()); + let staker = model(solana_pubkey::Pubkey::new_unique()); + let withdrawer = model(solana_pubkey::Pubkey::new_unique()); + let vote = model(solana_pubkey::Pubkey::new_unique()); + let intent = base_intent( + crate::ExSolanaCoreOperation::StakeCreateAccountAndDelegate { + from: from.clone(), + stake_account: stake.clone(), + vote_account: vote, + staker: staker.clone(), + withdrawer, + lockup_unix_timestamp: 0, + lockup_epoch: 0, + lockup_custodian: model(solana_pubkey::Pubkey::default()), + lamports: 1_000, + }, + &[from.clone(), stake.clone(), staker.clone()], + 1_000, + ); + let plan = crate::executor_solana_core_builder_build_prepared_plan(&intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + assert_eq!(plan.instructions.len(), 3); + assert_eq!(&plan.instructions[2].data[..4], &2_u32.to_le_bytes()); + assert_eq!(plan.instructions[2].accounts.len(), 6); + let base = solana_pubkey::Pubkey::new_unique(); + let stake_program = solana_pubkey::Pubkey::from_str(kb_program_ids::STAKE_PROGRAM_ID) + .unwrap_or_else(|error| panic!("invalid Stake id: {error}")); + let seed = "delegated-seed"; + let seeded_stake = solana_pubkey::Pubkey::create_with_seed(&base, seed, &stake_program) + .unwrap_or_else(|error| panic!("seed failure: {error}")); + let seeded = base_intent( + crate::ExSolanaCoreOperation::StakeCreateAccountWithSeedAndDelegate { + from: from.clone(), + stake_account: model(seeded_stake), + base: model(base), + seed: std::string::String::from(seed), + vote_account: model(solana_pubkey::Pubkey::new_unique()), + staker: staker.clone(), + withdrawer: model(solana_pubkey::Pubkey::new_unique()), + lockup_unix_timestamp: 0, + lockup_epoch: 0, + lockup_custodian: model(solana_pubkey::Pubkey::default()), + lamports: 1_001, + }, + &[from, model(base), staker], + 1_001, + ); + let seeded_plan = crate::executor_solana_core_builder_build_prepared_plan(&seeded) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + assert_eq!(seeded_plan.instructions.len(), 3); + assert_eq!(&seeded_plan.instructions[2].data[..4], &2_u32.to_le_bytes()); + } + + #[test] + fn authorize_variants_match_official_accounts_and_wire_tags() { + let stake = model(solana_pubkey::Pubkey::new_unique()); + let current = model(solana_pubkey::Pubkey::new_unique()); + let new_authority = model(solana_pubkey::Pubkey::new_unique()); + let owner = model(solana_pubkey::Pubkey::new_unique()); + let cases = [ + ( + crate::ExSolanaCoreOperation::StakeAuthorize { + stake_account: stake.clone(), + authority: current.clone(), + new_authority: new_authority.clone(), + authorization: crate::ExSolanaCoreStakeAuthorizationKind::Staker, + lockup_authority: std::option::Option::None, + }, + 1_u32, + 3_usize, + false, + ), + ( + crate::ExSolanaCoreOperation::StakeAuthorizeChecked { + stake_account: stake.clone(), + authority: current.clone(), + new_authority: new_authority.clone(), + authorization: crate::ExSolanaCoreStakeAuthorizationKind::Withdrawer, + lockup_authority: std::option::Option::None, + }, + 10_u32, + 4_usize, + true, + ), + ( + crate::ExSolanaCoreOperation::StakeAuthorizeWithSeed { + stake_account: stake.clone(), + authority_base: current.clone(), + authority_seed: std::string::String::from("authority-seed"), + authority_owner: owner.clone(), + new_authority: new_authority.clone(), + authorization: crate::ExSolanaCoreStakeAuthorizationKind::Staker, + lockup_authority: std::option::Option::None, + }, + 8_u32, + 3_usize, + false, + ), + ( + crate::ExSolanaCoreOperation::StakeAuthorizeCheckedWithSeed { + stake_account: stake, + authority_base: current.clone(), + authority_seed: std::string::String::from("authority-seed"), + authority_owner: owner, + new_authority: new_authority.clone(), + authorization: crate::ExSolanaCoreStakeAuthorizationKind::Withdrawer, + lockup_authority: std::option::Option::None, + }, + 11_u32, + 4_usize, + true, + ), + ]; + for (operation, tag, account_count, new_authority_signs) in cases { + let signers = if new_authority_signs { + vec![current.clone(), new_authority.clone()] + } else { + vec![current.clone()] + }; + let intent = base_intent(operation, signers.as_slice(), 0); + let plan = crate::executor_solana_core_builder_build_prepared_plan(&intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + assert_eq!(&plan.instructions[0].data[..4], &tag.to_le_bytes()); + assert_eq!(plan.instructions[0].accounts.len(), account_count); + assert_eq!( + plan.instructions[0] + .accounts + .iter() + .filter(|account| return account.is_signer) + .count(), + if new_authority_signs { 2 } else { 1 } + ); + } + } + + #[test] + fn delegate_merge_withdraw_and_deactivate_match_official_contracts() { + let stake = model(solana_pubkey::Pubkey::new_unique()); + let other = model(solana_pubkey::Pubkey::new_unique()); + let authority = model(solana_pubkey::Pubkey::new_unique()); + let vote = model(solana_pubkey::Pubkey::new_unique()); + let delegate = base_intent( + crate::ExSolanaCoreOperation::StakeDelegate { + stake_account: stake.clone(), + stake_authority: authority.clone(), + vote_account: vote, + }, + std::slice::from_ref(&authority), + 0, + ); + let delegate_plan = crate::executor_solana_core_builder_build_prepared_plan(&delegate) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + assert_eq!(delegate_plan.instructions[0].accounts.len(), 6); + assert_eq!(&delegate_plan.instructions[0].data[..4], &2_u32.to_le_bytes()); + let merge = base_intent( + crate::ExSolanaCoreOperation::StakeMerge { + destination_stake_account: stake.clone(), + source_stake_account: other.clone(), + stake_authority: authority.clone(), + }, + std::slice::from_ref(&authority), + 0, + ); + let merge_plan = crate::executor_solana_core_builder_build_prepared_plan(&merge) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + assert_eq!(&merge_plan.instructions[0].data[..4], &7_u32.to_le_bytes()); + let withdraw = base_intent( + crate::ExSolanaCoreOperation::StakeWithdraw { + stake_account: stake.clone(), + withdraw_authority: authority.clone(), + to: other, + lamports: 5, + lockup_authority: std::option::Option::None, + }, + std::slice::from_ref(&authority), + 5, + ); + let withdraw_plan = crate::executor_solana_core_builder_build_prepared_plan(&withdraw) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + assert_eq!(&withdraw_plan.instructions[0].data[..4], &4_u32.to_le_bytes()); + assert_eq!(withdraw_plan.requested_spend_lamports, 5); + + let deactivate = base_intent( + crate::ExSolanaCoreOperation::StakeDeactivate { + stake_account: stake, + stake_authority: authority.clone(), + }, + std::slice::from_ref(&authority), + 0, + ); + let deactivate_plan = crate::executor_solana_core_builder_build_prepared_plan(&deactivate) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + assert_eq!(&deactivate_plan.instructions[0].data[..4], &5_u32.to_le_bytes()); + } + + #[test] + fn lockup_variants_preserve_optional_fields_and_checked_signer() { + let stake = model(solana_pubkey::Pubkey::new_unique()); + let authority = model(solana_pubkey::Pubkey::new_unique()); + let new_custodian = model(solana_pubkey::Pubkey::new_unique()); + let checked = base_intent( + crate::ExSolanaCoreOperation::StakeSetLockupChecked { + stake_account: stake, + authority: authority.clone(), + unix_timestamp: std::option::Option::Some(10), + epoch: std::option::Option::Some(20), + new_custodian: std::option::Option::Some(new_custodian.clone()), + }, + &[authority, new_custodian], + 0, + ); + let plan = crate::executor_solana_core_builder_build_prepared_plan(&checked) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + assert_eq!(&plan.instructions[0].data[..4], &12_u32.to_le_bytes()); + assert_eq!(plan.instructions[0].accounts.len(), 3); + assert!(plan.instructions[0].accounts[2].is_signer); + } + + #[test] + fn minimum_delegation_and_delinquent_deactivation_require_no_instruction_signer() { + let minimum = base_intent(crate::ExSolanaCoreOperation::StakeGetMinimumDelegation, &[], 0); + let minimum_plan = crate::executor_solana_core_builder_build_prepared_plan(&minimum) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + assert_eq!(&minimum_plan.instructions[0].data[..4], &13_u32.to_le_bytes()); + assert!(minimum_plan.instructions[0].accounts.is_empty()); + let delinquent = base_intent( + crate::ExSolanaCoreOperation::StakeDeactivateDelinquent { + stake_account: model(solana_pubkey::Pubkey::new_unique()), + delinquent_vote_account: model(solana_pubkey::Pubkey::new_unique()), + reference_vote_account: model(solana_pubkey::Pubkey::new_unique()), + }, + &[], + 0, + ); + let delinquent_plan = crate::executor_solana_core_builder_build_prepared_plan(&delinquent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + assert_eq!(&delinquent_plan.instructions[0].data[..4], &14_u32.to_le_bytes()); + assert!( + delinquent_plan.instructions[0] + .accounts + .iter() + .all(|account| return !account.is_signer) + ); + } + + #[test] + fn move_operations_encode_exact_amounts_and_charge_spend_policy() { + let source = model(solana_pubkey::Pubkey::new_unique()); + let destination = model(solana_pubkey::Pubkey::new_unique()); + let authority = model(solana_pubkey::Pubkey::new_unique()); + for (operation, tag) in [ + ( + crate::ExSolanaCoreOperation::StakeMoveStake { + source_stake_account: source.clone(), + destination_stake_account: destination.clone(), + stake_authority: authority.clone(), + lamports: 44, + }, + 16_u32, + ), + ( + crate::ExSolanaCoreOperation::StakeMoveLamports { + source_stake_account: source.clone(), + destination_stake_account: destination.clone(), + stake_authority: authority.clone(), + lamports: 44, + }, + 17_u32, + ), + ] { + let intent = base_intent(operation, std::slice::from_ref(&authority), 44); + let plan = crate::executor_solana_core_builder_build_prepared_plan(&intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + assert_eq!(&plan.instructions[0].data[..4], &tag.to_le_bytes()); + assert_eq!(&plan.instructions[0].data[4..12], &44_u64.to_le_bytes()); + assert_eq!(plan.requested_spend_lamports, 44); + } + } + + #[test] + fn malformed_amounts_accounts_seeds_and_empty_lockup_are_rejected() { + let same = model(solana_pubkey::Pubkey::new_unique()); + let authority = model(solana_pubkey::Pubkey::new_unique()); + let move_zero = base_intent( + crate::ExSolanaCoreOperation::StakeMoveLamports { + source_stake_account: same.clone(), + destination_stake_account: same.clone(), + stake_authority: authority.clone(), + lamports: 0, + }, + std::slice::from_ref(&authority), + 0, + ); + assert!(crate::executor_solana_core_builder_build_prepared_plan(&move_zero).is_err()); + let empty_lockup = base_intent( + crate::ExSolanaCoreOperation::StakeSetLockup { + stake_account: same, + authority: authority.clone(), + unix_timestamp: std::option::Option::None, + epoch: std::option::Option::None, + custodian: std::option::Option::None, + }, + std::slice::from_ref(&authority), + 0, + ); + assert!(crate::executor_solana_core_builder_build_prepared_plan(&empty_lockup).is_err()); + } +} diff --git a/kb-lib/src/executor/solana/core/validation.rs b/kb-lib/src/executor/solana/core/validation.rs new file mode 100644 index 0000000..07e10b3 --- /dev/null +++ b/kb-lib/src/executor/solana/core/validation.rs @@ -0,0 +1,216 @@ +// file: kb-lib/src/executor/solana/core/validation.rs +// version: 1 + +//! Shared parsing and validation helpers for native execution plans. + +use std::str::FromStr; // rust-rules: trait-import + +/// Parses one model public key with the canonical execution error contract. +pub(crate) fn executor_solana_core_parse_pubkey( + pubkey: &crate::MdPubkey, + field_name: &str, +) -> kb_core::Result { + return match solana_pubkey::Pubkey::from_str(pubkey.0.as_str()) { + std::result::Result::Ok(value) => std::result::Result::Ok(value), + std::result::Result::Err(error) => std::result::Result::Err(kb_core::Error::new( + "execution_pubkey_invalid", + format!("invalid {field_name} public key {}: {error}", pubkey.0), + )), + }; +} + +/// Parses one canonical native program identifier. +pub(crate) fn executor_solana_core_parse_native_program_id( + value: &str, +) -> kb_core::Result { + return match solana_pubkey::Pubkey::from_str(value) { + std::result::Result::Ok(program_id) => std::result::Result::Ok(program_id), + std::result::Result::Err(error) => std::result::Result::Err(kb_core::Error::new( + "execution_native_program_id_invalid", + format!("invalid native program id {value}: {error}"), + )), + }; +} + +/// Validates that two model public keys identify distinct accounts. +pub(crate) fn executor_solana_core_validate_distinct_pubkeys( + first: &crate::MdPubkey, + second: &crate::MdPubkey, + code: &str, + message: impl std::convert::Into, +) -> kb_core::Result<()> { + if first == second { + return std::result::Result::Err(kb_core::Error::new(code, message)); + } + return std::result::Result::Ok(()); +} + +/// Builds one typed validation error without duplicating domain-local wrappers. +pub(crate) fn executor_solana_core_invalid( + code: &str, + message: impl std::convert::Into, +) -> kb_core::Result { + return std::result::Result::Err(kb_core::Error::new(code, message)); +} + +/// Validates that an unsigned execution amount is strictly positive. +pub(crate) fn executor_solana_core_validate_positive_u64( + value: u64, + code: &str, + message: &str, +) -> kb_core::Result<()> { + if value == 0 { + return std::result::Result::Err(kb_core::Error::new(code, message)); + } + return std::result::Result::Ok(()); +} + +/// Validates one `Pubkey::create_with_seed` derivation with caller-owned error codes. +pub(crate) fn executor_solana_core_validate_seeded_address( + address: &solana_pubkey::Pubkey, + base: &solana_pubkey::Pubkey, + seed: &str, + owner: &solana_pubkey::Pubkey, + field_name: &str, + invalid_seed_code: &str, + mismatch_code: &str, +) -> kb_core::Result<()> { + let derived = match solana_pubkey::Pubkey::create_with_seed(base, seed, owner) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => { + return std::result::Result::Err(kb_core::Error::new( + invalid_seed_code, + format!("cannot derive {field_name} from the supplied seed: {error}"), + )); + }, + }; + if derived != *address { + return std::result::Result::Err(kb_core::Error::new( + mismatch_code, + format!("{field_name} {address} does not match derived address {derived}"), + )); + } + return std::result::Result::Ok(()); +} + +/// Validates an exact byte-slice length while preserving the caller error code. +pub(crate) fn executor_solana_core_validate_exact_length( + value: &[u8], + expected: usize, + code: &str, + label: &str, +) -> kb_core::Result<()> { + if value.len() != expected { + return std::result::Result::Err(kb_core::Error::new( + code, + format!("{label} must contain exactly {expected} bytes"), + )); + } + return std::result::Result::Ok(()); +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; // rust-rules: trait-import + + fn model(value: solana_pubkey::Pubkey) -> crate::MdPubkey { + return crate::MdPubkey(value.to_string()); + } + + #[test] + fn pubkey_parser_preserves_the_canonical_error_contract() { + let address = solana_pubkey::Pubkey::new_unique(); + let parsed = crate::executor_solana_core_parse_pubkey(&model(address), "account"); + match parsed { + std::result::Result::Ok(value) => assert_eq!(value, address), + std::result::Result::Err(error) => panic!("valid public key rejected: {error}"), + } + let invalid = crate::executor_solana_core_parse_pubkey( + &crate::MdPubkey(std::string::String::from("not-base58")), + "account", + ); + assert!(invalid.is_err()); + } + + #[test] + fn native_program_id_parser_accepts_official_ids_and_rejects_invalid_text() { + let parsed = + crate::executor_solana_core_parse_native_program_id(kb_program_ids::SYSTEM_PROGRAM_ID); + let expected = solana_pubkey::Pubkey::from_str(kb_program_ids::SYSTEM_PROGRAM_ID) + .unwrap_or_else(|error| panic!("invalid fixture: {error}")); + match parsed { + std::result::Result::Ok(value) => assert_eq!(value, expected), + std::result::Result::Err(error) => panic!("official program id rejected: {error}"), + } + assert!(crate::executor_solana_core_parse_native_program_id("invalid").is_err()); + } + + #[test] + fn shared_scalar_and_account_validations_preserve_caller_codes() { + let first = model(solana_pubkey::Pubkey::new_unique()); + let second = model(solana_pubkey::Pubkey::new_unique()); + assert!( + crate::executor_solana_core_validate_distinct_pubkeys( + &first, + &second, + "distinct", + "must differ", + ) + .is_ok() + ); + assert!( + crate::executor_solana_core_validate_distinct_pubkeys( + &first, + &first, + "distinct", + "must differ", + ) + .is_err() + ); + assert!( + crate::executor_solana_core_validate_positive_u64(1, "positive", "must be positive") + .is_ok() + ); + assert!( + crate::executor_solana_core_validate_positive_u64(0, "positive", "must be positive") + .is_err() + ); + let invalid: kb_core::Result<()> = + crate::executor_solana_core_invalid("invalid", "shared validation error"); + assert!(invalid.is_err()); + let base = solana_pubkey::Pubkey::new_unique(); + let owner = solana_pubkey::Pubkey::new_unique(); + let derived = solana_pubkey::Pubkey::create_with_seed(&base, "seed", &owner) + .unwrap_or_else(|error| panic!("invalid fixture: {error}")); + assert!( + crate::executor_solana_core_validate_seeded_address( + &derived, &base, "seed", &owner, "derived", "seed", "mismatch", + ) + .is_ok() + ); + assert!( + crate::executor_solana_core_validate_seeded_address( + &solana_pubkey::Pubkey::new_unique(), + &base, + "seed", + &owner, + "derived", + "seed", + "mismatch", + ) + .is_err() + ); + } + + #[test] + fn exact_length_validation_accepts_only_the_requested_shape() { + assert!( + crate::executor_solana_core_validate_exact_length(&[1_u8, 2_u8], 2, "length", "value") + .is_ok() + ); + assert!( + crate::executor_solana_core_validate_exact_length(&[1_u8], 2, "length", "value") + .is_err() + ); + } +} diff --git a/kb-lib/src/executor/solana/core/vote.rs b/kb-lib/src/executor/solana/core/vote.rs new file mode 100644 index 0000000..ce6acd3 --- /dev/null +++ b/kb-lib/src/executor/solana/core/vote.rs @@ -0,0 +1,2358 @@ +// file: kb-lib/src/executor/solana/core/vote.rs +// version: 1 + +//! Exact Vote Program instruction builders backed by `solana-vote-interface`. + +use std::str::FromStr; // rust-rules: trait-import + +const SOURCE: &str = "solana-vote-interface@6.0.3"; +const MAX_COMMISSION_BPS: u16 = 10_000; +const MAX_SEED_LENGTH: usize = 32; + +#[derive(Clone, Copy)] +enum VoteStateWireKind { + Update, + Compact, +} + +pub(crate) fn executor_solana_core_vote_build_prepared_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, +) -> kb_core::Result { + return match &intent.operation { + crate::ExSolanaCoreOperation::VoteInitializeAccount { + vote_account, + node_pubkey, + authorized_voter, + authorized_withdrawer, + commission_percent, + } => build_initialize_plan( + intent, + fee_payer, + vote_account, + node_pubkey, + authorized_voter, + authorized_withdrawer, + *commission_percent, + ), + crate::ExSolanaCoreOperation::VoteInitializeAccountV2 { + vote_account, + node_pubkey, + authorized_voter, + authorized_voter_bls_pubkey, + authorized_voter_bls_proof_of_possession, + authorized_withdrawer, + inflation_rewards_commission_bps, + block_revenue_commission_bps, + inflation_rewards_collector, + block_revenue_collector, + } => build_initialize_v2_plan( + intent, + fee_payer, + vote_account, + node_pubkey, + authorized_voter, + authorized_voter_bls_pubkey.as_slice(), + authorized_voter_bls_proof_of_possession.as_slice(), + authorized_withdrawer, + *inflation_rewards_commission_bps, + *block_revenue_commission_bps, + inflation_rewards_collector, + block_revenue_collector, + ), + crate::ExSolanaCoreOperation::VoteCreateAccount { + from, + vote_account, + node_pubkey, + authorized_voter, + authorized_withdrawer, + commission_percent, + lamports, + } => build_create_account_plan( + intent, + fee_payer, + from, + vote_account, + std::option::Option::None, + node_pubkey, + authorized_voter, + authorized_withdrawer, + *commission_percent, + *lamports, + ), + crate::ExSolanaCoreOperation::VoteCreateAccountWithSeed { + from, + vote_account, + base, + seed, + node_pubkey, + authorized_voter, + authorized_withdrawer, + commission_percent, + lamports, + } => build_create_account_plan( + intent, + fee_payer, + from, + vote_account, + std::option::Option::Some((base, seed.as_str())), + node_pubkey, + authorized_voter, + authorized_withdrawer, + *commission_percent, + *lamports, + ), + crate::ExSolanaCoreOperation::VoteCreateAccountV2 { + from, + vote_account, + node_pubkey, + authorized_voter, + authorized_voter_bls_pubkey, + authorized_voter_bls_proof_of_possession, + authorized_withdrawer, + inflation_rewards_commission_bps, + block_revenue_commission_bps, + inflation_rewards_collector, + block_revenue_collector, + lamports, + } => build_create_account_v2_plan( + intent, + fee_payer, + from, + vote_account, + std::option::Option::None, + node_pubkey, + authorized_voter, + authorized_voter_bls_pubkey.as_slice(), + authorized_voter_bls_proof_of_possession.as_slice(), + authorized_withdrawer, + *inflation_rewards_commission_bps, + *block_revenue_commission_bps, + inflation_rewards_collector, + block_revenue_collector, + *lamports, + ), + crate::ExSolanaCoreOperation::VoteCreateAccountWithSeedV2 { + from, + vote_account, + base, + seed, + node_pubkey, + authorized_voter, + authorized_voter_bls_pubkey, + authorized_voter_bls_proof_of_possession, + authorized_withdrawer, + inflation_rewards_commission_bps, + block_revenue_commission_bps, + inflation_rewards_collector, + block_revenue_collector, + lamports, + } => build_create_account_v2_plan( + intent, + fee_payer, + from, + vote_account, + std::option::Option::Some((base, seed.as_str())), + node_pubkey, + authorized_voter, + authorized_voter_bls_pubkey.as_slice(), + authorized_voter_bls_proof_of_possession.as_slice(), + authorized_withdrawer, + *inflation_rewards_commission_bps, + *block_revenue_commission_bps, + inflation_rewards_collector, + block_revenue_collector, + *lamports, + ), + crate::ExSolanaCoreOperation::VoteAuthorize { + vote_account, + current_authority, + new_authority, + authorization, + } => build_authorize_plan( + intent, + fee_payer, + vote_account, + current_authority, + std::option::Option::None, + new_authority, + authorization, + false, + ), + crate::ExSolanaCoreOperation::VoteAuthorizeChecked { + vote_account, + current_authority, + new_authority, + authorization, + } => build_authorize_plan( + intent, + fee_payer, + vote_account, + current_authority, + std::option::Option::None, + new_authority, + authorization, + true, + ), + crate::ExSolanaCoreOperation::VoteAuthorizeWithSeed { + vote_account, + current_authority_base, + current_authority_derived_key_owner, + current_authority_derived_key_seed, + new_authority, + authorization, + } => build_authorize_plan( + intent, + fee_payer, + vote_account, + current_authority_base, + std::option::Option::Some(( + current_authority_derived_key_owner, + current_authority_derived_key_seed.as_str(), + )), + new_authority, + authorization, + false, + ), + crate::ExSolanaCoreOperation::VoteAuthorizeCheckedWithSeed { + vote_account, + current_authority_base, + current_authority_derived_key_owner, + current_authority_derived_key_seed, + new_authority, + authorization, + } => build_authorize_plan( + intent, + fee_payer, + vote_account, + current_authority_base, + std::option::Option::Some(( + current_authority_derived_key_owner, + current_authority_derived_key_seed.as_str(), + )), + new_authority, + authorization, + true, + ), + crate::ExSolanaCoreOperation::VoteSubmit { + vote_account, + authorized_voter, + slots, + bank_hash, + timestamp, + } => build_vote_plan( + intent, + fee_payer, + vote_account, + authorized_voter, + slots.as_slice(), + bank_hash, + *timestamp, + std::option::Option::None, + ), + crate::ExSolanaCoreOperation::VoteSwitch { + vote_account, + authorized_voter, + slots, + bank_hash, + timestamp, + switch_proof_hash, + } => build_vote_plan( + intent, + fee_payer, + vote_account, + authorized_voter, + slots.as_slice(), + bank_hash, + *timestamp, + std::option::Option::Some(switch_proof_hash.as_str()), + ), + crate::ExSolanaCoreOperation::VoteWithdraw { + vote_account, + withdraw_authority, + to, + lamports, + } => { + build_withdraw_plan(intent, fee_payer, vote_account, withdraw_authority, to, *lamports) + }, + crate::ExSolanaCoreOperation::VoteUpdateValidatorIdentity { + vote_account, + withdraw_authority, + new_validator_identity, + } => build_update_validator_identity_plan( + intent, + fee_payer, + vote_account, + withdraw_authority, + new_validator_identity, + ), + crate::ExSolanaCoreOperation::VoteUpdateCommission { + vote_account, + withdraw_authority, + commission_percent, + } => build_update_commission_plan( + intent, + fee_payer, + vote_account, + withdraw_authority, + *commission_percent, + ), + crate::ExSolanaCoreOperation::VoteUpdateVoteState { + vote_account, + authorized_voter, + lockouts, + root, + bank_hash, + timestamp, + } => build_vote_state_plan( + intent, + fee_payer, + vote_account, + authorized_voter, + lockouts.as_slice(), + *root, + bank_hash, + *timestamp, + VoteStateWireKind::Update, + std::option::Option::None, + ), + crate::ExSolanaCoreOperation::VoteUpdateVoteStateSwitch { + vote_account, + authorized_voter, + lockouts, + root, + bank_hash, + timestamp, + switch_proof_hash, + } => build_vote_state_plan( + intent, + fee_payer, + vote_account, + authorized_voter, + lockouts.as_slice(), + *root, + bank_hash, + *timestamp, + VoteStateWireKind::Update, + std::option::Option::Some(switch_proof_hash.as_str()), + ), + crate::ExSolanaCoreOperation::VoteCompactUpdateVoteState { + vote_account, + authorized_voter, + lockouts, + root, + bank_hash, + timestamp, + } => build_vote_state_plan( + intent, + fee_payer, + vote_account, + authorized_voter, + lockouts.as_slice(), + *root, + bank_hash, + *timestamp, + VoteStateWireKind::Compact, + std::option::Option::None, + ), + crate::ExSolanaCoreOperation::VoteCompactUpdateVoteStateSwitch { + vote_account, + authorized_voter, + lockouts, + root, + bank_hash, + timestamp, + switch_proof_hash, + } => build_vote_state_plan( + intent, + fee_payer, + vote_account, + authorized_voter, + lockouts.as_slice(), + *root, + bank_hash, + *timestamp, + VoteStateWireKind::Compact, + std::option::Option::Some(switch_proof_hash.as_str()), + ), + crate::ExSolanaCoreOperation::VoteTowerSync { + vote_account, + authorized_voter, + lockouts, + root, + bank_hash, + timestamp, + block_id, + } => build_tower_sync_plan( + intent, + fee_payer, + vote_account, + authorized_voter, + lockouts.as_slice(), + *root, + bank_hash, + *timestamp, + block_id, + std::option::Option::None, + ), + crate::ExSolanaCoreOperation::VoteTowerSyncSwitch { + vote_account, + authorized_voter, + lockouts, + root, + bank_hash, + timestamp, + block_id, + switch_proof_hash, + } => build_tower_sync_plan( + intent, + fee_payer, + vote_account, + authorized_voter, + lockouts.as_slice(), + *root, + bank_hash, + *timestamp, + block_id, + std::option::Option::Some(switch_proof_hash.as_str()), + ), + crate::ExSolanaCoreOperation::VoteUpdateCommissionCollector { + vote_account, + withdraw_authority, + new_collector, + commission_kind, + } => build_update_commission_collector_plan( + intent, + fee_payer, + vote_account, + withdraw_authority, + new_collector, + *commission_kind, + ), + crate::ExSolanaCoreOperation::VoteUpdateCommissionBps { + vote_account, + withdraw_authority, + commission_kind, + commission_bps, + } => build_update_commission_bps_plan( + intent, + fee_payer, + vote_account, + withdraw_authority, + *commission_kind, + *commission_bps, + ), + crate::ExSolanaCoreOperation::VoteDepositDelegatorRewards { + vote_account, + source, + deposit_lamports, + } => build_deposit_delegator_rewards_plan( + intent, + fee_payer, + vote_account, + source, + *deposit_lamports, + ), + _ => std::result::Result::Err(kb_core::Error::new( + "execution_vote_operation_mismatch", + "Vote builder received an unrelated Solana core operation", + )), + }; +} + +#[allow(clippy::too_many_arguments)] +fn build_initialize_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + vote_account: &crate::MdPubkey, + node_pubkey: &crate::MdPubkey, + authorized_voter: &crate::MdPubkey, + authorized_withdrawer: &crate::MdPubkey, + commission_percent: u8, +) -> kb_core::Result { + let instruction = match initialize_instruction( + vote_account, + node_pubkey, + authorized_voter, + authorized_withdrawer, + commission_percent, + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_VOTE_INITIALIZE_ACCOUNT_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + vec![(node_pubkey.clone(), std::string::String::from("vote_node_identity"))], + ); +} + +#[allow(clippy::too_many_arguments)] +fn build_initialize_v2_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + vote_account: &crate::MdPubkey, + node_pubkey: &crate::MdPubkey, + authorized_voter: &crate::MdPubkey, + authorized_voter_bls_pubkey: &[u8], + authorized_voter_bls_proof_of_possession: &[u8], + authorized_withdrawer: &crate::MdPubkey, + inflation_rewards_commission_bps: u16, + block_revenue_commission_bps: u16, + inflation_rewards_collector: &crate::MdPubkey, + block_revenue_collector: &crate::MdPubkey, +) -> kb_core::Result { + let instruction = match initialize_v2_instruction( + vote_account, + node_pubkey, + authorized_voter, + authorized_voter_bls_pubkey, + authorized_voter_bls_proof_of_possession, + authorized_withdrawer, + inflation_rewards_commission_bps, + block_revenue_commission_bps, + inflation_rewards_collector, + block_revenue_collector, + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_VOTE_INITIALIZE_ACCOUNT_V2_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + vec![(node_pubkey.clone(), std::string::String::from("vote_node_identity"))], + ); +} + +#[allow(clippy::too_many_arguments)] +fn build_create_account_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + from: &crate::MdPubkey, + vote_account: &crate::MdPubkey, + seed_contract: std::option::Option<(&crate::MdPubkey, &str)>, + node_pubkey: &crate::MdPubkey, + authorized_voter: &crate::MdPubkey, + authorized_withdrawer: &crate::MdPubkey, + commission_percent: u8, + lamports: u64, +) -> kb_core::Result { + match crate::executor_solana_core_validate_positive_u64( + lamports, + "execution_vote_create_lamports_zero", + "Vote account creation requires a positive measured rent-exempt lamport amount", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match crate::executor_solana_core_validate_distinct_pubkeys( + from, + vote_account, + "execution_vote_create_same_account", + "Vote funding and new Vote accounts must differ", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let from_address = match crate::executor_solana_core_parse_pubkey(from, "from") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let vote_address = match crate::executor_solana_core_parse_pubkey(vote_account, "vote_account") + { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let vote_program = match vote_program_id() { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let mut signer_roles = vec![(from.clone(), std::string::String::from("vote_funding_account"))]; + let create = match seed_contract { + std::option::Option::Some((base, seed)) => { + let base_address = match crate::executor_solana_core_parse_pubkey(base, "base") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + match executor_solana_core_validate_seeded_address( + &vote_address, + &base_address, + seed, + &vote_program, + "vote_account", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + signer_roles.push((base.clone(), std::string::String::from("vote_seed_base"))); + solana_system_interface::instruction::create_account_with_seed( + &from_address, + &vote_address, + &base_address, + seed, + lamports, + solana_vote_interface::state::VoteStateV4::size_of() as u64, + &vote_program, + ) + }, + std::option::Option::None => { + signer_roles + .push((vote_account.clone(), std::string::String::from("new_vote_account"))); + solana_system_interface::instruction::create_account( + &from_address, + &vote_address, + lamports, + solana_vote_interface::state::VoteStateV4::size_of() as u64, + &vote_program, + ) + }, + }; + let initialize = match initialize_instruction( + vote_account, + node_pubkey, + authorized_voter, + authorized_withdrawer, + commission_percent, + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + signer_roles.push((node_pubkey.clone(), std::string::String::from("vote_node_identity"))); + let operation_code = if seed_contract.is_some() { + crate::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_WITH_SEED_OPERATION + } else { + crate::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_OPERATION + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + operation_code, + vec![create, initialize], + lamports, + std::option::Option::None, + signer_roles, + ); +} + +#[allow(clippy::too_many_arguments)] +fn build_create_account_v2_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + from: &crate::MdPubkey, + vote_account: &crate::MdPubkey, + seed_contract: std::option::Option<(&crate::MdPubkey, &str)>, + node_pubkey: &crate::MdPubkey, + authorized_voter: &crate::MdPubkey, + authorized_voter_bls_pubkey: &[u8], + authorized_voter_bls_proof_of_possession: &[u8], + authorized_withdrawer: &crate::MdPubkey, + inflation_rewards_commission_bps: u16, + block_revenue_commission_bps: u16, + inflation_rewards_collector: &crate::MdPubkey, + block_revenue_collector: &crate::MdPubkey, + lamports: u64, +) -> kb_core::Result { + match crate::executor_solana_core_validate_positive_u64( + lamports, + "execution_vote_create_lamports_zero", + "Vote account creation requires a positive measured rent-exempt lamport amount", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match crate::executor_solana_core_validate_distinct_pubkeys( + from, + vote_account, + "execution_vote_create_same_account", + "Vote funding and new Vote accounts must differ", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let from_address = match crate::executor_solana_core_parse_pubkey(from, "from") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let vote_address = match crate::executor_solana_core_parse_pubkey(vote_account, "vote_account") + { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let vote_program = match vote_program_id() { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let mut signer_roles = vec![(from.clone(), std::string::String::from("vote_funding_account"))]; + let create = match seed_contract { + std::option::Option::Some((base, seed)) => { + let base_address = match crate::executor_solana_core_parse_pubkey(base, "base") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + match executor_solana_core_validate_seeded_address( + &vote_address, + &base_address, + seed, + &vote_program, + "vote_account", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + signer_roles.push((base.clone(), std::string::String::from("vote_seed_base"))); + solana_system_interface::instruction::create_account_with_seed( + &from_address, + &vote_address, + &base_address, + seed, + lamports, + solana_vote_interface::state::VoteStateV4::size_of() as u64, + &vote_program, + ) + }, + std::option::Option::None => { + signer_roles + .push((vote_account.clone(), std::string::String::from("new_vote_account"))); + solana_system_interface::instruction::create_account( + &from_address, + &vote_address, + lamports, + solana_vote_interface::state::VoteStateV4::size_of() as u64, + &vote_program, + ) + }, + }; + let initialize = match initialize_v2_instruction( + vote_account, + node_pubkey, + authorized_voter, + authorized_voter_bls_pubkey, + authorized_voter_bls_proof_of_possession, + authorized_withdrawer, + inflation_rewards_commission_bps, + block_revenue_commission_bps, + inflation_rewards_collector, + block_revenue_collector, + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + signer_roles.push((node_pubkey.clone(), std::string::String::from("vote_node_identity"))); + let operation_code = if seed_contract.is_some() { + crate::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_WITH_SEED_V2_OPERATION + } else { + crate::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_V2_OPERATION + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + operation_code, + vec![create, initialize], + lamports, + std::option::Option::None, + signer_roles, + ); +} + +#[allow(clippy::too_many_arguments)] +fn build_authorize_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + vote_account: &crate::MdPubkey, + current_authority_or_base: &crate::MdPubkey, + seed_contract: std::option::Option<(&crate::MdPubkey, &str)>, + new_authority: &crate::MdPubkey, + authorization: &crate::ExSolanaCoreVoteAuthorizationKind, + checked: bool, +) -> kb_core::Result { + let vote_address = match crate::executor_solana_core_parse_pubkey(vote_account, "vote_account") + { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let current_authority = match crate::executor_solana_core_parse_pubkey( + current_authority_or_base, + "current_authority_or_base", + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let new_authority_address = + match crate::executor_solana_core_parse_pubkey(new_authority, "new_authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authorization_type = match vote_authorization(authorization) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let (wire, operation_code) = match (seed_contract, checked) { + (std::option::Option::None, false) => ( + solana_vote_interface::instruction::VoteInstruction::Authorize( + new_authority_address, + authorization_type, + ), + crate::EX_SOLANA_CORE_VOTE_AUTHORIZE_OPERATION, + ), + (std::option::Option::None, true) => ( + solana_vote_interface::instruction::VoteInstruction::AuthorizeChecked( + authorization_type, + ), + crate::EX_SOLANA_CORE_VOTE_AUTHORIZE_CHECKED_OPERATION, + ), + (std::option::Option::Some((owner, seed)), false) => { + let owner_address = match crate::executor_solana_core_parse_pubkey( + owner, + "current_authority_derived_key_owner", + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + match validate_seed(seed, "current_authority_derived_key_seed") { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + ( + solana_vote_interface::instruction::VoteInstruction::AuthorizeWithSeed( + solana_vote_interface::state::VoteAuthorizeWithSeedArgs { + authorization_type, + current_authority_derived_key_owner: owner_address, + current_authority_derived_key_seed: std::string::String::from(seed), + new_authority: new_authority_address, + }, + ), + crate::EX_SOLANA_CORE_VOTE_AUTHORIZE_WITH_SEED_OPERATION, + ) + }, + (std::option::Option::Some((owner, seed)), true) => { + let owner_address = match crate::executor_solana_core_parse_pubkey( + owner, + "current_authority_derived_key_owner", + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + match validate_seed(seed, "current_authority_derived_key_seed") { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + ( + solana_vote_interface::instruction::VoteInstruction::AuthorizeCheckedWithSeed( + solana_vote_interface::state::VoteAuthorizeCheckedWithSeedArgs { + authorization_type, + current_authority_derived_key_owner: owner_address, + current_authority_derived_key_seed: std::string::String::from(seed), + }, + ), + crate::EX_SOLANA_CORE_VOTE_AUTHORIZE_CHECKED_WITH_SEED_OPERATION, + ) + }, + }; + let mut accounts = vec![ + solana_instruction::AccountMeta::new(vote_address, false), + solana_instruction::AccountMeta::new_readonly(solana_sdk_ids::sysvar::clock::id(), false), + solana_instruction::AccountMeta::new_readonly(current_authority, true), + ]; + let mut signer_roles = vec![( + current_authority_or_base.clone(), + std::string::String::from("vote_current_authority"), + )]; + if checked { + accounts.push(solana_instruction::AccountMeta::new_readonly(new_authority_address, true)); + signer_roles.push((new_authority.clone(), std::string::String::from("vote_new_authority"))); + } + let instruction = match vote_instruction(wire, accounts) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + operation_code, + vec![instruction], + 0, + std::option::Option::None, + signer_roles, + ); +} + +#[allow(clippy::too_many_arguments)] +fn build_vote_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + vote_account: &crate::MdPubkey, + authorized_voter: &crate::MdPubkey, + slots: &[u64], + bank_hash: &str, + timestamp: std::option::Option, + switch_proof_hash: std::option::Option<&str>, +) -> kb_core::Result { + let vote = match vote_value(slots, bank_hash, timestamp) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let (wire, operation_code) = match switch_proof_hash { + std::option::Option::Some(value) => { + let proof = match parse_hash(value, "switch_proof_hash") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + ( + solana_vote_interface::instruction::VoteInstruction::VoteSwitch(vote, proof), + crate::EX_SOLANA_CORE_VOTE_SWITCH_OPERATION, + ) + }, + std::option::Option::None => ( + solana_vote_interface::instruction::VoteInstruction::Vote(vote), + crate::EX_SOLANA_CORE_VOTE_SUBMIT_OPERATION, + ), + }; + let vote_address = match crate::executor_solana_core_parse_pubkey(vote_account, "vote_account") + { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let voter_address = + match crate::executor_solana_core_parse_pubkey(authorized_voter, "authorized_voter") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instruction = match vote_instruction( + wire, + vec![ + solana_instruction::AccountMeta::new(vote_address, false), + solana_instruction::AccountMeta::new_readonly( + solana_sdk_ids::sysvar::slot_hashes::id(), + false, + ), + solana_instruction::AccountMeta::new_readonly( + solana_sdk_ids::sysvar::clock::id(), + false, + ), + solana_instruction::AccountMeta::new_readonly(voter_address, true), + ], + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + operation_code, + vec![instruction], + 0, + std::option::Option::None, + vec![(authorized_voter.clone(), std::string::String::from("vote_authority"))], + ); +} + +fn build_withdraw_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + vote_account: &crate::MdPubkey, + withdraw_authority: &crate::MdPubkey, + to: &crate::MdPubkey, + lamports: u64, +) -> kb_core::Result { + match crate::executor_solana_core_validate_positive_u64( + lamports, + "execution_vote_withdraw_lamports_zero", + "Vote withdrawal requires a positive lamport amount", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match crate::executor_solana_core_validate_distinct_pubkeys( + vote_account, + to, + "execution_vote_withdraw_same_account", + "Vote account and withdrawal recipient must differ", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let vote_address = match crate::executor_solana_core_parse_pubkey(vote_account, "vote_account") + { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let to_address = match crate::executor_solana_core_parse_pubkey(to, "to") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority_address = + match crate::executor_solana_core_parse_pubkey(withdraw_authority, "withdraw_authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instruction = match vote_instruction( + solana_vote_interface::instruction::VoteInstruction::Withdraw(lamports), + vec![ + solana_instruction::AccountMeta::new(vote_address, false), + solana_instruction::AccountMeta::new(to_address, false), + solana_instruction::AccountMeta::new_readonly(authority_address, true), + ], + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_VOTE_WITHDRAW_OPERATION, + vec![instruction], + lamports, + std::option::Option::None, + vec![(withdraw_authority.clone(), std::string::String::from("vote_withdraw_authority"))], + ); +} + +fn build_update_validator_identity_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + vote_account: &crate::MdPubkey, + withdraw_authority: &crate::MdPubkey, + new_validator_identity: &crate::MdPubkey, +) -> kb_core::Result { + let vote_address = match crate::executor_solana_core_parse_pubkey(vote_account, "vote_account") + { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let withdraw_address = + match crate::executor_solana_core_parse_pubkey(withdraw_authority, "withdraw_authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let identity_address = match crate::executor_solana_core_parse_pubkey( + new_validator_identity, + "new_validator_identity", + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instruction = match vote_instruction( + solana_vote_interface::instruction::VoteInstruction::UpdateValidatorIdentity, + vec![ + solana_instruction::AccountMeta::new(vote_address, false), + solana_instruction::AccountMeta::new_readonly(identity_address, true), + solana_instruction::AccountMeta::new_readonly(withdraw_address, true), + ], + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_VOTE_UPDATE_VALIDATOR_IDENTITY_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + vec![ + ( + new_validator_identity.clone(), + std::string::String::from("vote_new_node_identity"), + ), + (withdraw_authority.clone(), std::string::String::from("vote_withdraw_authority")), + ], + ); +} + +fn build_update_commission_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + vote_account: &crate::MdPubkey, + withdraw_authority: &crate::MdPubkey, + commission_percent: u8, +) -> kb_core::Result { + match validate_commission_percent(commission_percent) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let instruction = match admin_instruction( + solana_vote_interface::instruction::VoteInstruction::UpdateCommission(commission_percent), + vote_account, + withdraw_authority, + std::option::Option::None, + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_VOTE_UPDATE_COMMISSION_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + vec![(withdraw_authority.clone(), std::string::String::from("vote_withdraw_authority"))], + ); +} + +#[allow(clippy::too_many_arguments)] +fn build_vote_state_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + vote_account: &crate::MdPubkey, + authorized_voter: &crate::MdPubkey, + lockouts: &[crate::ExSolanaCoreVoteLockout], + root: std::option::Option, + bank_hash: &str, + timestamp: std::option::Option, + wire_kind: VoteStateWireKind, + switch_proof_hash: std::option::Option<&str>, +) -> kb_core::Result { + let update = match vote_state_update(lockouts, root, bank_hash, timestamp) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let (wire, operation_code) = match (wire_kind, switch_proof_hash) { + (VoteStateWireKind::Update, std::option::Option::None) => ( + solana_vote_interface::instruction::VoteInstruction::UpdateVoteState(update), + crate::EX_SOLANA_CORE_VOTE_UPDATE_VOTE_STATE_OPERATION, + ), + (VoteStateWireKind::Update, std::option::Option::Some(value)) => { + let proof = match parse_hash(value, "switch_proof_hash") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + ( + solana_vote_interface::instruction::VoteInstruction::UpdateVoteStateSwitch( + update, proof, + ), + crate::EX_SOLANA_CORE_VOTE_UPDATE_VOTE_STATE_SWITCH_OPERATION, + ) + }, + (VoteStateWireKind::Compact, std::option::Option::None) => ( + solana_vote_interface::instruction::VoteInstruction::CompactUpdateVoteState(update), + crate::EX_SOLANA_CORE_VOTE_COMPACT_UPDATE_VOTE_STATE_OPERATION, + ), + (VoteStateWireKind::Compact, std::option::Option::Some(value)) => { + let proof = match parse_hash(value, "switch_proof_hash") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + ( + solana_vote_interface::instruction::VoteInstruction::CompactUpdateVoteStateSwitch( + update, proof, + ), + crate::EX_SOLANA_CORE_VOTE_COMPACT_UPDATE_VOTE_STATE_SWITCH_OPERATION, + ) + }, + }; + return finalize_vote_authority_instruction( + intent, + fee_payer, + operation_code, + vote_account, + authorized_voter, + wire, + ); +} + +#[allow(clippy::too_many_arguments)] +fn build_tower_sync_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + vote_account: &crate::MdPubkey, + authorized_voter: &crate::MdPubkey, + lockouts: &[crate::ExSolanaCoreVoteLockout], + root: std::option::Option, + bank_hash: &str, + timestamp: std::option::Option, + block_id: &str, + switch_proof_hash: std::option::Option<&str>, +) -> kb_core::Result { + let tower = match tower_sync(lockouts, root, bank_hash, timestamp, block_id) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let (wire, operation_code) = match switch_proof_hash { + std::option::Option::Some(value) => { + let proof = match parse_hash(value, "switch_proof_hash") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + ( + solana_vote_interface::instruction::VoteInstruction::TowerSyncSwitch(tower, proof), + crate::EX_SOLANA_CORE_VOTE_TOWER_SYNC_SWITCH_OPERATION, + ) + }, + std::option::Option::None => ( + solana_vote_interface::instruction::VoteInstruction::TowerSync(tower), + crate::EX_SOLANA_CORE_VOTE_TOWER_SYNC_OPERATION, + ), + }; + return finalize_vote_authority_instruction( + intent, + fee_payer, + operation_code, + vote_account, + authorized_voter, + wire, + ); +} + +fn build_update_commission_collector_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + vote_account: &crate::MdPubkey, + withdraw_authority: &crate::MdPubkey, + new_collector: &crate::MdPubkey, + commission_kind: crate::ExSolanaCoreVoteCommissionKind, +) -> kb_core::Result { + let instruction = match admin_instruction( + solana_vote_interface::instruction::VoteInstruction::UpdateCommissionCollector( + commission_kind.as_official(), + ), + vote_account, + withdraw_authority, + std::option::Option::Some(new_collector), + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_VOTE_UPDATE_COMMISSION_COLLECTOR_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + vec![(withdraw_authority.clone(), std::string::String::from("vote_withdraw_authority"))], + ); +} + +fn build_update_commission_bps_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + vote_account: &crate::MdPubkey, + withdraw_authority: &crate::MdPubkey, + commission_kind: crate::ExSolanaCoreVoteCommissionKind, + commission_bps: u16, +) -> kb_core::Result { + match validate_commission_bps(commission_bps, "commission_bps") { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let instruction = match admin_instruction( + solana_vote_interface::instruction::VoteInstruction::UpdateCommissionBps { + commission_bps, + kind: commission_kind.as_official(), + }, + vote_account, + withdraw_authority, + std::option::Option::None, + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_VOTE_UPDATE_COMMISSION_BPS_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + vec![(withdraw_authority.clone(), std::string::String::from("vote_withdraw_authority"))], + ); +} + +fn build_deposit_delegator_rewards_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + vote_account: &crate::MdPubkey, + source: &crate::MdPubkey, + deposit_lamports: u64, +) -> kb_core::Result { + match crate::executor_solana_core_validate_positive_u64( + deposit_lamports, + "execution_vote_deposit_lamports_zero", + "Vote delegator-reward deposit requires a positive lamport amount", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match crate::executor_solana_core_validate_distinct_pubkeys( + vote_account, + source, + "execution_vote_deposit_same_account", + "Vote account and delegator-reward source must differ", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let vote_address = match crate::executor_solana_core_parse_pubkey(vote_account, "vote_account") + { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let source_address = match crate::executor_solana_core_parse_pubkey(source, "source") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instruction = match vote_instruction( + solana_vote_interface::instruction::VoteInstruction::DepositDelegatorRewards { + deposit: deposit_lamports, + }, + vec![ + solana_instruction::AccountMeta::new(vote_address, false), + solana_instruction::AccountMeta::new(source_address, true), + ], + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_VOTE_DEPOSIT_DELEGATOR_REWARDS_OPERATION, + vec![instruction], + deposit_lamports, + std::option::Option::None, + vec![(source.clone(), std::string::String::from("vote_reward_source"))], + ); +} + +#[allow(clippy::too_many_arguments)] +fn initialize_instruction( + vote_account: &crate::MdPubkey, + node_pubkey: &crate::MdPubkey, + authorized_voter: &crate::MdPubkey, + authorized_withdrawer: &crate::MdPubkey, + commission_percent: u8, +) -> kb_core::Result { + match validate_commission_percent(commission_percent) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let vote_address = match crate::executor_solana_core_parse_pubkey(vote_account, "vote_account") + { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let node_address = match crate::executor_solana_core_parse_pubkey(node_pubkey, "node_pubkey") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let voter_address = + match crate::executor_solana_core_parse_pubkey(authorized_voter, "authorized_voter") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let withdrawer_address = match crate::executor_solana_core_parse_pubkey( + authorized_withdrawer, + "authorized_withdrawer", + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return vote_instruction( + solana_vote_interface::instruction::VoteInstruction::InitializeAccount( + solana_vote_interface::state::VoteInit { + node_pubkey: node_address, + authorized_voter: voter_address, + authorized_withdrawer: withdrawer_address, + commission: commission_percent, + }, + ), + vec![ + solana_instruction::AccountMeta::new(vote_address, false), + solana_instruction::AccountMeta::new_readonly( + solana_sdk_ids::sysvar::rent::id(), + false, + ), + solana_instruction::AccountMeta::new_readonly( + solana_sdk_ids::sysvar::clock::id(), + false, + ), + solana_instruction::AccountMeta::new_readonly(node_address, true), + ], + ); +} + +#[allow(clippy::too_many_arguments)] +fn initialize_v2_instruction( + vote_account: &crate::MdPubkey, + node_pubkey: &crate::MdPubkey, + authorized_voter: &crate::MdPubkey, + authorized_voter_bls_pubkey: &[u8], + authorized_voter_bls_proof_of_possession: &[u8], + authorized_withdrawer: &crate::MdPubkey, + inflation_rewards_commission_bps: u16, + block_revenue_commission_bps: u16, + inflation_rewards_collector: &crate::MdPubkey, + block_revenue_collector: &crate::MdPubkey, +) -> kb_core::Result { + match validate_commission_bps( + inflation_rewards_commission_bps, + "inflation_rewards_commission_bps", + ) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + match validate_commission_bps(block_revenue_commission_bps, "block_revenue_commission_bps") { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let bls_pubkey = match fixed_bls_pubkey(authorized_voter_bls_pubkey) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let bls_proof = match fixed_bls_proof(authorized_voter_bls_proof_of_possession) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let vote_address = match crate::executor_solana_core_parse_pubkey(vote_account, "vote_account") + { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let node_address = match crate::executor_solana_core_parse_pubkey(node_pubkey, "node_pubkey") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let voter_address = + match crate::executor_solana_core_parse_pubkey(authorized_voter, "authorized_voter") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let withdrawer_address = match crate::executor_solana_core_parse_pubkey( + authorized_withdrawer, + "authorized_withdrawer", + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let inflation_collector = match crate::executor_solana_core_parse_pubkey( + inflation_rewards_collector, + "inflation_rewards_collector", + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let block_collector = match crate::executor_solana_core_parse_pubkey( + block_revenue_collector, + "block_revenue_collector", + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return vote_instruction( + solana_vote_interface::instruction::VoteInstruction::InitializeAccountV2( + solana_vote_interface::state::VoteInitV2 { + node_pubkey: node_address, + authorized_voter: voter_address, + authorized_voter_bls_pubkey: bls_pubkey, + authorized_voter_bls_proof_of_possession: bls_proof, + authorized_withdrawer: withdrawer_address, + inflation_rewards_commission_bps, + block_revenue_commission_bps, + }, + ), + vec![ + solana_instruction::AccountMeta::new(vote_address, false), + solana_instruction::AccountMeta::new_readonly(node_address, true), + solana_instruction::AccountMeta::new(inflation_collector, false), + solana_instruction::AccountMeta::new(block_collector, false), + ], + ); +} + +fn vote_authorization( + value: &crate::ExSolanaCoreVoteAuthorizationKind, +) -> kb_core::Result { + return match value { + crate::ExSolanaCoreVoteAuthorizationKind::Voter => { + std::result::Result::Ok(solana_vote_interface::state::VoteAuthorize::Voter) + }, + crate::ExSolanaCoreVoteAuthorizationKind::Withdrawer => { + std::result::Result::Ok(solana_vote_interface::state::VoteAuthorize::Withdrawer) + }, + crate::ExSolanaCoreVoteAuthorizationKind::VoterWithBls { + bls_pubkey, + bls_proof_of_possession, + } => { + let pubkey = match fixed_bls_pubkey(bls_pubkey.as_slice()) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let proof = match fixed_bls_proof(bls_proof_of_possession.as_slice()) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + std::result::Result::Ok(solana_vote_interface::state::VoteAuthorize::VoterWithBLS( + solana_vote_interface::state::VoterWithBLSArgs { + bls_pubkey: pubkey, + bls_proof_of_possession: proof, + }, + )) + }, + }; +} + +fn vote_value( + slots: &[u64], + bank_hash: &str, + timestamp: std::option::Option, +) -> kb_core::Result { + match validate_slots(slots) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let hash = match parse_hash(bank_hash, "bank_hash") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return std::result::Result::Ok(solana_vote_interface::state::Vote { + slots: slots.to_vec(), + hash, + timestamp, + }); +} + +fn vote_state_update( + lockouts: &[crate::ExSolanaCoreVoteLockout], + root: std::option::Option, + bank_hash: &str, + timestamp: std::option::Option, +) -> kb_core::Result { + let wire_lockouts = match lockout_deque(lockouts, root) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let hash = match parse_hash(bank_hash, "bank_hash") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return std::result::Result::Ok(solana_vote_interface::state::VoteStateUpdate { + lockouts: wire_lockouts, + root, + hash, + timestamp, + }); +} + +fn tower_sync( + lockouts: &[crate::ExSolanaCoreVoteLockout], + root: std::option::Option, + bank_hash: &str, + timestamp: std::option::Option, + block_id: &str, +) -> kb_core::Result { + let wire_lockouts = match lockout_deque(lockouts, root) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let hash = match parse_hash(bank_hash, "bank_hash") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let block_hash = match parse_hash(block_id, "block_id") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return std::result::Result::Ok(solana_vote_interface::state::TowerSync { + lockouts: wire_lockouts, + root, + hash, + timestamp, + block_id: block_hash, + }); +} + +fn lockout_deque( + lockouts: &[crate::ExSolanaCoreVoteLockout], + root: std::option::Option, +) -> kb_core::Result> { + if lockouts.is_empty() { + return std::result::Result::Err(kb_core::Error::new( + "execution_vote_lockouts_empty", + "Vote state and tower updates require at least one lockout", + )); + } + if lockouts.len() > solana_vote_interface::state::MAX_LOCKOUT_HISTORY { + return std::result::Result::Err(kb_core::Error::new( + "execution_vote_lockouts_too_many", + format!( + "Vote lockout count {} exceeds official maximum {}", + lockouts.len(), + solana_vote_interface::state::MAX_LOCKOUT_HISTORY + ), + )); + } + let mut previous = std::option::Option::None; + let mut output = std::collections::VecDeque::with_capacity(lockouts.len()); + for lockout in lockouts { + if lockout.confirmation_count > solana_vote_interface::state::MAX_LOCKOUT_HISTORY as u32 { + return std::result::Result::Err(kb_core::Error::new( + "execution_vote_confirmation_count_invalid", + format!( + "Vote confirmation count {} exceeds official maximum {}", + lockout.confirmation_count, + solana_vote_interface::state::MAX_LOCKOUT_HISTORY + ), + )); + } + if let std::option::Option::Some(previous_slot) = previous { + if lockout.slot <= previous_slot { + return std::result::Result::Err(kb_core::Error::new( + "execution_vote_lockouts_not_strictly_increasing", + "Vote lockout slots must be strictly increasing", + )); + } + } + previous = std::option::Option::Some(lockout.slot); + output.push_back(solana_vote_interface::state::Lockout::new_with_confirmation_count( + lockout.slot, + lockout.confirmation_count, + )); + } + if let std::option::Option::Some(root_slot) = root { + if let std::option::Option::Some(first) = lockouts.first() { + if root_slot > first.slot { + return std::result::Result::Err(kb_core::Error::new( + "execution_vote_root_above_first_lockout", + "Vote root must be at or below the first lockout slot", + )); + } + } + } + return std::result::Result::Ok(output); +} + +fn finalize_vote_authority_instruction( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + operation_code: &str, + vote_account: &crate::MdPubkey, + authorized_voter: &crate::MdPubkey, + wire: solana_vote_interface::instruction::VoteInstruction, +) -> kb_core::Result { + let vote_address = match crate::executor_solana_core_parse_pubkey(vote_account, "vote_account") + { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let voter_address = + match crate::executor_solana_core_parse_pubkey(authorized_voter, "authorized_voter") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instruction = match vote_instruction( + wire, + vec![ + solana_instruction::AccountMeta::new(vote_address, false), + solana_instruction::AccountMeta::new_readonly(voter_address, true), + ], + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + operation_code, + vec![instruction], + 0, + std::option::Option::None, + vec![(authorized_voter.clone(), std::string::String::from("vote_authority"))], + ); +} + +fn admin_instruction( + wire: solana_vote_interface::instruction::VoteInstruction, + vote_account: &crate::MdPubkey, + withdraw_authority: &crate::MdPubkey, + writable_middle_account: std::option::Option<&crate::MdPubkey>, +) -> kb_core::Result { + let vote_address = match crate::executor_solana_core_parse_pubkey(vote_account, "vote_account") + { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority_address = + match crate::executor_solana_core_parse_pubkey(withdraw_authority, "withdraw_authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let mut accounts = vec![solana_instruction::AccountMeta::new(vote_address, false)]; + if let std::option::Option::Some(account) = writable_middle_account { + let account_address = + match crate::executor_solana_core_parse_pubkey(account, "writable_middle_account") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + accounts.push(solana_instruction::AccountMeta::new(account_address, false)); + } + accounts.push(solana_instruction::AccountMeta::new_readonly(authority_address, true)); + return vote_instruction(wire, accounts); +} + +fn vote_instruction( + wire: solana_vote_interface::instruction::VoteInstruction, + accounts: std::vec::Vec, +) -> kb_core::Result { + let data = match wincode::serialize(&wire) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => { + return std::result::Result::Err(kb_core::Error::new( + "execution_vote_wincode_failed", + format!("cannot serialize {SOURCE} instruction: {error}"), + )); + }, + }; + let program_id = match vote_program_id() { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return std::result::Result::Ok(solana_instruction::Instruction { program_id, accounts, data }); +} + +fn vote_program_id() -> kb_core::Result { + return crate::executor_solana_core_parse_native_program_id(kb_program_ids::VOTE_PROGRAM_ID); +} + +fn validate_commission_percent(value: u8) -> kb_core::Result<()> { + if value > 100 { + return std::result::Result::Err(kb_core::Error::new( + "execution_vote_commission_percent_invalid", + format!("Vote legacy commission percentage {value} exceeds 100"), + )); + } + return std::result::Result::Ok(()); +} + +fn validate_commission_bps(value: u16, field_name: &str) -> kb_core::Result<()> { + if value > MAX_COMMISSION_BPS { + return std::result::Result::Err(kb_core::Error::new( + "execution_vote_commission_bps_invalid", + format!("Vote {field_name} value {value} exceeds {MAX_COMMISSION_BPS}"), + )); + } + return std::result::Result::Ok(()); +} + +fn fixed_bls_pubkey( + value: &[u8], +) -> kb_core::Result<[u8; solana_vote_interface::state::BLS_PUBLIC_KEY_COMPRESSED_SIZE]> { + if value.len() != solana_vote_interface::state::BLS_PUBLIC_KEY_COMPRESSED_SIZE { + return std::result::Result::Err(kb_core::Error::new( + "execution_vote_bls_pubkey_length_invalid", + format!( + "Vote BLS public key length {} must equal {}", + value.len(), + solana_vote_interface::state::BLS_PUBLIC_KEY_COMPRESSED_SIZE + ), + )); + } + let mut output = [0_u8; solana_vote_interface::state::BLS_PUBLIC_KEY_COMPRESSED_SIZE]; + output.copy_from_slice(value); + return std::result::Result::Ok(output); +} + +fn fixed_bls_proof( + value: &[u8], +) -> kb_core::Result<[u8; solana_vote_interface::state::BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE]> { + if value.len() != solana_vote_interface::state::BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE { + return std::result::Result::Err(kb_core::Error::new( + "execution_vote_bls_proof_length_invalid", + format!( + "Vote BLS proof length {} must equal {}", + value.len(), + solana_vote_interface::state::BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE + ), + )); + } + let mut output = [0_u8; solana_vote_interface::state::BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE]; + output.copy_from_slice(value); + return std::result::Result::Ok(output); +} + +fn validate_slots(slots: &[u64]) -> kb_core::Result<()> { + if slots.is_empty() { + return std::result::Result::Err(kb_core::Error::new( + "execution_vote_slots_empty", + "Vote submission requires at least one slot", + )); + } + if slots.len() > solana_vote_interface::state::MAX_LOCKOUT_HISTORY { + return std::result::Result::Err(kb_core::Error::new( + "execution_vote_slots_too_many", + format!( + "Vote slot count {} exceeds official maximum {}", + slots.len(), + solana_vote_interface::state::MAX_LOCKOUT_HISTORY + ), + )); + } + for pair in slots.windows(2) { + if pair[1] <= pair[0] { + return std::result::Result::Err(kb_core::Error::new( + "execution_vote_slots_not_strictly_increasing", + "Vote slots must be strictly increasing", + )); + } + } + return std::result::Result::Ok(()); +} + +fn validate_seed(seed: &str, field_name: &str) -> kb_core::Result<()> { + if seed.is_empty() { + return std::result::Result::Err(kb_core::Error::new( + "execution_vote_seed_empty", + format!("Vote {field_name} must not be empty"), + )); + } + if seed.len() > MAX_SEED_LENGTH { + return std::result::Result::Err(kb_core::Error::new( + "execution_vote_seed_too_long", + format!("Vote {field_name} length {} exceeds {}", seed.len(), MAX_SEED_LENGTH), + )); + } + return std::result::Result::Ok(()); +} + +fn executor_solana_core_validate_seeded_address( + address: &solana_pubkey::Pubkey, + base: &solana_pubkey::Pubkey, + seed: &str, + owner: &solana_pubkey::Pubkey, + field_name: &str, +) -> kb_core::Result<()> { + match validate_seed(seed, field_name) { + std::result::Result::Ok(()) => {}, + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + return crate::executor_solana_core_validate_seeded_address( + address, + base, + seed, + owner, + field_name, + "execution_vote_seed_invalid", + "execution_vote_seeded_address_mismatch", + ); +} + +fn parse_hash(value: &str, field_name: &str) -> kb_core::Result { + return match solana_hash::Hash::from_str(value) { + std::result::Result::Ok(hash) => std::result::Result::Ok(hash), + std::result::Result::Err(error) => std::result::Result::Err(kb_core::Error::new( + "execution_vote_hash_invalid", + format!("invalid Vote {field_name} hash {value}: {error}"), + )), + }; +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; // rust-rules: trait-import + + fn model(value: solana_pubkey::Pubkey) -> crate::MdPubkey { + return crate::MdPubkey(value.to_string()); + } + + fn base_intent( + operation: crate::ExSolanaCoreOperation, + signers: &[crate::MdPubkey], + spend: u64, + ) -> crate::ExSolanaCoreExecutionIntent { + let payer = model(solana_pubkey::Pubkey::new_unique()); + let mut authorized = vec![payer.clone()]; + authorized.extend_from_slice(signers); + return crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("vote-test"), + fee_payer: payer, + policy: crate::ExApiExecutionPolicy { + authorized_signers: authorized, + cost_limit: crate::ExApiExecutionCostLimit { + max_spend_lamports: std::option::Option::Some(spend), + max_fee_lamports: std::option::Option::Some(100_000), + max_compute_unit_price_micro_lamports: std::option::Option::Some(10), + }, + ..crate::ExApiExecutionPolicy::default() + }, + operation, + }; + } + + fn serialize(value: &solana_vote_interface::instruction::VoteInstruction) -> std::vec::Vec { + return wincode::serialize(value) + .unwrap_or_else(|error| panic!("unexpected Vote serialization error: {error}")); + } + + fn assert_discriminator(data: &[u8], expected: u32) { + let expected_bytes = expected.to_le_bytes(); + assert_eq!(data.get(..4), std::option::Option::Some(expected_bytes.as_slice())); + } + + #[test] + fn initialize_variants_match_official_wire_accounts_and_signers() { + let vote = model(solana_pubkey::Pubkey::new_unique()); + let node = model(solana_pubkey::Pubkey::new_unique()); + let voter = model(solana_pubkey::Pubkey::new_unique()); + let withdrawer = model(solana_pubkey::Pubkey::new_unique()); + let legacy = super::initialize_instruction(&vote, &node, &voter, &withdrawer, 7) + .unwrap_or_else(|error| panic!("unexpected Vote initialization error: {error}")); + let expected_legacy = + solana_vote_interface::instruction::VoteInstruction::InitializeAccount( + solana_vote_interface::state::VoteInit { + node_pubkey: solana_pubkey::Pubkey::from_str(node.0.as_str()) + .unwrap_or_else(|error| panic!("invalid test node: {error}")), + authorized_voter: solana_pubkey::Pubkey::from_str(voter.0.as_str()) + .unwrap_or_else(|error| panic!("invalid test voter: {error}")), + authorized_withdrawer: solana_pubkey::Pubkey::from_str(withdrawer.0.as_str()) + .unwrap_or_else(|error| panic!("invalid test withdrawer: {error}")), + commission: 7, + }, + ); + assert_eq!(legacy.data, serialize(&expected_legacy)); + assert_eq!(legacy.accounts.len(), 4); + assert_eq!(legacy.accounts[1].pubkey, solana_sdk_ids::sysvar::rent::id()); + assert_eq!(legacy.accounts[2].pubkey, solana_sdk_ids::sysvar::clock::id()); + assert!(legacy.accounts[3].is_signer); + + let inflation_collector = model(solana_pubkey::Pubkey::new_unique()); + let block_collector = model(solana_pubkey::Pubkey::new_unique()); + let v2 = super::initialize_v2_instruction( + &vote, + &node, + &voter, + &[1_u8; solana_vote_interface::state::BLS_PUBLIC_KEY_COMPRESSED_SIZE], + &[2_u8; solana_vote_interface::state::BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE], + &withdrawer, + 125, + 250, + &inflation_collector, + &block_collector, + ) + .unwrap_or_else(|error| panic!("unexpected Vote v2 initialization error: {error}")); + assert_eq!(v2.accounts.len(), 4); + assert!(v2.accounts[1].is_signer); + assert!(v2.accounts[2].is_writable); + assert!(v2.accounts[3].is_writable); + let expected_v2 = solana_vote_interface::instruction::VoteInstruction::InitializeAccountV2( + solana_vote_interface::state::VoteInitV2 { + node_pubkey: solana_pubkey::Pubkey::from_str(node.0.as_str()) + .unwrap_or_else(|error| panic!("invalid test node: {error}")), + authorized_voter: solana_pubkey::Pubkey::from_str(voter.0.as_str()) + .unwrap_or_else(|error| panic!("invalid test voter: {error}")), + authorized_voter_bls_pubkey: [1_u8; + solana_vote_interface::state::BLS_PUBLIC_KEY_COMPRESSED_SIZE], + authorized_voter_bls_proof_of_possession: [2_u8; + solana_vote_interface::state::BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE], + authorized_withdrawer: solana_pubkey::Pubkey::from_str(withdrawer.0.as_str()) + .unwrap_or_else(|error| panic!("invalid test withdrawer: {error}")), + inflation_rewards_commission_bps: 125, + block_revenue_commission_bps: 250, + }, + ); + assert_eq!(v2.data, serialize(&expected_v2)); + } + + #[test] + fn create_account_variants_preserve_official_order_space_and_spend() { + let from = model(solana_pubkey::Pubkey::new_unique()); + let vote = model(solana_pubkey::Pubkey::new_unique()); + let node = model(solana_pubkey::Pubkey::new_unique()); + let voter = model(solana_pubkey::Pubkey::new_unique()); + let withdrawer = model(solana_pubkey::Pubkey::new_unique()); + let intent = base_intent( + crate::ExSolanaCoreOperation::VoteCreateAccount { + from: from.clone(), + vote_account: vote.clone(), + node_pubkey: node.clone(), + authorized_voter: voter, + authorized_withdrawer: withdrawer, + commission_percent: 4, + lamports: 500, + }, + &[from, vote, node], + 500, + ); + let plan = crate::executor_solana_core_builder_build_prepared_plan(&intent) + .unwrap_or_else(|error| panic!("unexpected Vote creation error: {error}")); + assert_eq!(plan.instructions.len(), 2); + assert_eq!(plan.instructions[0].program_id.0, kb_program_ids::SYSTEM_PROGRAM_ID); + assert_eq!(plan.instructions[1].program_id.0, kb_program_ids::VOTE_PROGRAM_ID); + assert_eq!(plan.requested_spend_lamports, 500); + assert_eq!(plan.instructions[1].data.first().copied(), std::option::Option::Some(0)); + } + + #[test] + fn seeded_creation_validates_derivation_and_avoids_new_account_signature() { + let from = solana_pubkey::Pubkey::new_unique(); + let base = solana_pubkey::Pubkey::new_unique(); + let vote_program = solana_pubkey::Pubkey::from_str(kb_program_ids::VOTE_PROGRAM_ID) + .unwrap_or_else(|error| panic!("invalid Vote program id: {error}")); + let seed = "vote-seed"; + let vote = solana_pubkey::Pubkey::create_with_seed(&base, seed, &vote_program) + .unwrap_or_else(|error| panic!("unexpected Vote seed error: {error}")); + let node = model(solana_pubkey::Pubkey::new_unique()); + let from_model = model(from); + let base_model = model(base); + let vote_model = model(vote); + let intent = base_intent( + crate::ExSolanaCoreOperation::VoteCreateAccountWithSeed { + from: from_model.clone(), + vote_account: vote_model.clone(), + base: base_model.clone(), + seed: std::string::String::from(seed), + node_pubkey: node.clone(), + authorized_voter: model(solana_pubkey::Pubkey::new_unique()), + authorized_withdrawer: model(solana_pubkey::Pubkey::new_unique()), + commission_percent: 5, + lamports: 501, + }, + &[from_model, base_model, node], + 501, + ); + let plan = crate::executor_solana_core_builder_build_prepared_plan(&intent) + .unwrap_or_else(|error| panic!("unexpected seeded Vote creation error: {error}")); + assert_eq!(plan.instructions.len(), 2); + assert!(!plan.required_signers.iter().any(|entry| return entry.pubkey == vote_model)); + let mismatch = super::executor_solana_core_validate_seeded_address( + &solana_pubkey::Pubkey::new_unique(), + &base, + seed, + &vote_program, + "vote_account", + ); + assert!(mismatch.is_err()); + } + + #[test] + fn authority_variants_preserve_official_accounts_and_bls_contract() { + let vote = model(solana_pubkey::Pubkey::new_unique()); + let current = model(solana_pubkey::Pubkey::new_unique()); + let new_authority = model(solana_pubkey::Pubkey::new_unique()); + let intent = base_intent( + crate::ExSolanaCoreOperation::VoteAuthorizeChecked { + vote_account: vote, + current_authority: current.clone(), + new_authority: new_authority.clone(), + authorization: crate::ExSolanaCoreVoteAuthorizationKind::VoterWithBls { + bls_pubkey: vec![3_u8; 48], + bls_proof_of_possession: vec![4_u8; 96], + }, + }, + &[current, new_authority], + 0, + ); + let plan = crate::executor_solana_core_builder_build_prepared_plan(&intent) + .unwrap_or_else(|error| panic!("unexpected Vote authorize error: {error}")); + assert_eq!(plan.instructions[0].accounts.len(), 4); + assert_eq!( + plan.instructions[0].accounts[1].pubkey.0, + solana_sdk_ids::sysvar::clock::id().to_string() + ); + assert!(plan.instructions[0].accounts[2].is_signer); + assert!(plan.instructions[0].accounts[3].is_signer); + + let malformed = + super::vote_authorization(&crate::ExSolanaCoreVoteAuthorizationKind::VoterWithBls { + bls_pubkey: vec![0_u8; 47], + bls_proof_of_possession: vec![0_u8; 96], + }); + assert!(malformed.is_err()); + } + + #[test] + fn vote_and_switch_match_official_sysvar_contracts() { + let vote = model(solana_pubkey::Pubkey::new_unique()); + let voter = model(solana_pubkey::Pubkey::new_unique()); + let bank_hash = solana_hash::Hash::new_unique().to_string(); + let switch_hash = solana_hash::Hash::new_unique().to_string(); + let intent = base_intent( + crate::ExSolanaCoreOperation::VoteSwitch { + vote_account: vote, + authorized_voter: voter.clone(), + slots: vec![10, 20, 30], + bank_hash, + timestamp: std::option::Option::Some(123), + switch_proof_hash: switch_hash, + }, + &[voter], + 0, + ); + let plan = crate::executor_solana_core_builder_build_prepared_plan(&intent) + .unwrap_or_else(|error| panic!("unexpected Vote switch error: {error}")); + assert_eq!(plan.instructions[0].accounts.len(), 4); + assert_eq!( + plan.instructions[0].accounts[1].pubkey.0, + solana_sdk_ids::sysvar::slot_hashes::id().to_string() + ); + assert_eq!( + plan.instructions[0].accounts[2].pubkey.0, + solana_sdk_ids::sysvar::clock::id().to_string() + ); + assert!(plan.instructions[0].accounts[3].is_signer); + } + + #[test] + fn vote_state_and_tower_sync_validate_compact_contracts() { + let lockouts = vec![ + crate::ExSolanaCoreVoteLockout { slot: 100, confirmation_count: 2 }, + crate::ExSolanaCoreVoteLockout { slot: 120, confirmation_count: 1 }, + ]; + let bank_hash = solana_hash::Hash::new_unique().to_string(); + let state = super::vote_state_update( + lockouts.as_slice(), + std::option::Option::Some(90), + bank_hash.as_str(), + std::option::Option::Some(456), + ) + .unwrap_or_else(|error| panic!("unexpected Vote-state error: {error}")); + let block_id = solana_hash::Hash::new_unique().to_string(); + let tower = super::tower_sync( + lockouts.as_slice(), + std::option::Option::Some(90), + bank_hash.as_str(), + std::option::Option::Some(456), + block_id.as_str(), + ) + .unwrap_or_else(|error| panic!("unexpected tower-sync error: {error}")); + assert_eq!(state.lockouts.len(), 2); + assert_eq!(tower.lockouts.len(), 2); + + let duplicate = [ + crate::ExSolanaCoreVoteLockout { slot: 100, confirmation_count: 1 }, + crate::ExSolanaCoreVoteLockout { slot: 100, confirmation_count: 1 }, + ]; + assert!(super::lockout_deque(duplicate.as_slice(), std::option::Option::Some(90)).is_err()); + assert!(super::lockout_deque(lockouts.as_slice(), std::option::Option::Some(101)).is_err()); + } + + #[test] + fn every_vote_state_wire_variant_preserves_discriminator_accounts_and_signer() { + let vote = model(solana_pubkey::Pubkey::new_unique()); + let voter = model(solana_pubkey::Pubkey::new_unique()); + let bank_hash = solana_hash::Hash::new_unique().to_string(); + let switch_hash = solana_hash::Hash::new_unique().to_string(); + let block_id = solana_hash::Hash::new_unique().to_string(); + let lockouts = vec![ + crate::ExSolanaCoreVoteLockout { slot: 200, confirmation_count: 2 }, + crate::ExSolanaCoreVoteLockout { slot: 220, confirmation_count: 1 }, + ]; + let operations = vec![ + ( + crate::ExSolanaCoreOperation::VoteUpdateVoteState { + vote_account: vote.clone(), + authorized_voter: voter.clone(), + lockouts: lockouts.clone(), + root: std::option::Option::Some(190), + bank_hash: bank_hash.clone(), + timestamp: std::option::Option::Some(1), + }, + crate::EX_SOLANA_CORE_VOTE_UPDATE_VOTE_STATE_OPERATION, + 8_u32, + ), + ( + crate::ExSolanaCoreOperation::VoteUpdateVoteStateSwitch { + vote_account: vote.clone(), + authorized_voter: voter.clone(), + lockouts: lockouts.clone(), + root: std::option::Option::Some(190), + bank_hash: bank_hash.clone(), + timestamp: std::option::Option::Some(2), + switch_proof_hash: switch_hash.clone(), + }, + crate::EX_SOLANA_CORE_VOTE_UPDATE_VOTE_STATE_SWITCH_OPERATION, + 9_u32, + ), + ( + crate::ExSolanaCoreOperation::VoteCompactUpdateVoteState { + vote_account: vote.clone(), + authorized_voter: voter.clone(), + lockouts: lockouts.clone(), + root: std::option::Option::Some(190), + bank_hash: bank_hash.clone(), + timestamp: std::option::Option::Some(3), + }, + crate::EX_SOLANA_CORE_VOTE_COMPACT_UPDATE_VOTE_STATE_OPERATION, + 12_u32, + ), + ( + crate::ExSolanaCoreOperation::VoteCompactUpdateVoteStateSwitch { + vote_account: vote.clone(), + authorized_voter: voter.clone(), + lockouts: lockouts.clone(), + root: std::option::Option::Some(190), + bank_hash: bank_hash.clone(), + timestamp: std::option::Option::Some(4), + switch_proof_hash: switch_hash.clone(), + }, + crate::EX_SOLANA_CORE_VOTE_COMPACT_UPDATE_VOTE_STATE_SWITCH_OPERATION, + 13_u32, + ), + ( + crate::ExSolanaCoreOperation::VoteTowerSync { + vote_account: vote.clone(), + authorized_voter: voter.clone(), + lockouts: lockouts.clone(), + root: std::option::Option::Some(190), + bank_hash: bank_hash.clone(), + timestamp: std::option::Option::Some(5), + block_id: block_id.clone(), + }, + crate::EX_SOLANA_CORE_VOTE_TOWER_SYNC_OPERATION, + 14_u32, + ), + ( + crate::ExSolanaCoreOperation::VoteTowerSyncSwitch { + vote_account: vote.clone(), + authorized_voter: voter.clone(), + lockouts, + root: std::option::Option::Some(190), + bank_hash, + timestamp: std::option::Option::Some(6), + block_id, + switch_proof_hash: switch_hash, + }, + crate::EX_SOLANA_CORE_VOTE_TOWER_SYNC_SWITCH_OPERATION, + 15_u32, + ), + ]; + for (operation, operation_code, discriminator) in operations { + let intent = base_intent(operation, std::slice::from_ref(&voter), 0); + let plan = crate::executor_solana_core_builder_build_prepared_plan(&intent) + .unwrap_or_else(|error| panic!("unexpected Vote-state plan error: {error}")); + assert_eq!(plan.operation_code, operation_code); + assert_eq!(plan.instructions.len(), 1); + assert_eq!(plan.instructions[0].accounts.len(), 2); + assert!(plan.instructions[0].accounts[1].is_signer); + assert_discriminator(plan.instructions[0].data.as_slice(), discriminator); + } + } + + #[test] + fn validator_identity_and_commission_variants_preserve_official_contracts() { + let vote = model(solana_pubkey::Pubkey::new_unique()); + let withdrawer = model(solana_pubkey::Pubkey::new_unique()); + let identity = model(solana_pubkey::Pubkey::new_unique()); + let identity_intent = base_intent( + crate::ExSolanaCoreOperation::VoteUpdateValidatorIdentity { + vote_account: vote.clone(), + withdraw_authority: withdrawer.clone(), + new_validator_identity: identity.clone(), + }, + &[withdrawer.clone(), identity], + 0, + ); + let identity_plan = + crate::executor_solana_core_builder_build_prepared_plan(&identity_intent) + .unwrap_or_else(|error| panic!("unexpected Vote identity error: {error}")); + assert_eq!(identity_plan.instructions[0].accounts.len(), 3); + assert!(identity_plan.instructions[0].accounts[1].is_signer); + assert!(identity_plan.instructions[0].accounts[2].is_signer); + assert_discriminator(identity_plan.instructions[0].data.as_slice(), 4); + + let legacy_intent = base_intent( + crate::ExSolanaCoreOperation::VoteUpdateCommission { + vote_account: vote.clone(), + withdraw_authority: withdrawer.clone(), + commission_percent: 25, + }, + std::slice::from_ref(&withdrawer), + 0, + ); + let legacy_plan = crate::executor_solana_core_builder_build_prepared_plan(&legacy_intent) + .unwrap_or_else(|error| panic!("unexpected Vote commission error: {error}")); + assert_eq!(legacy_plan.instructions[0].accounts.len(), 2); + assert_discriminator(legacy_plan.instructions[0].data.as_slice(), 5); + + let bps_intent = base_intent( + crate::ExSolanaCoreOperation::VoteUpdateCommissionBps { + vote_account: vote, + withdraw_authority: withdrawer.clone(), + commission_kind: crate::ExSolanaCoreVoteCommissionKind::InflationRewards, + commission_bps: 1_250, + }, + std::slice::from_ref(&withdrawer), + 0, + ); + let bps_plan = crate::executor_solana_core_builder_build_prepared_plan(&bps_intent) + .unwrap_or_else(|error| panic!("unexpected Vote BPS commission error: {error}")); + assert_eq!(bps_plan.instructions[0].accounts.len(), 2); + assert_discriminator(bps_plan.instructions[0].data.as_slice(), 18); + } + + #[test] + fn commission_collector_withdraw_and_deposit_preserve_spend_semantics() { + let vote = model(solana_pubkey::Pubkey::new_unique()); + let authority = model(solana_pubkey::Pubkey::new_unique()); + let collector = model(solana_pubkey::Pubkey::new_unique()); + let collector_intent = base_intent( + crate::ExSolanaCoreOperation::VoteUpdateCommissionCollector { + vote_account: vote.clone(), + withdraw_authority: authority.clone(), + new_collector: collector, + commission_kind: crate::ExSolanaCoreVoteCommissionKind::BlockRevenue, + }, + std::slice::from_ref(&authority), + 0, + ); + let collector_plan = + crate::executor_solana_core_builder_build_prepared_plan(&collector_intent) + .unwrap_or_else(|error| panic!("unexpected collector update error: {error}")); + assert_eq!(collector_plan.instructions[0].accounts.len(), 3); + assert_eq!(collector_plan.requested_spend_lamports, 0); + + let recipient = model(solana_pubkey::Pubkey::new_unique()); + let withdraw = base_intent( + crate::ExSolanaCoreOperation::VoteWithdraw { + vote_account: vote.clone(), + withdraw_authority: authority.clone(), + to: recipient, + lamports: 50, + }, + std::slice::from_ref(&authority), + 50, + ); + let withdraw_plan = crate::executor_solana_core_builder_build_prepared_plan(&withdraw) + .unwrap_or_else(|error| panic!("unexpected Vote withdrawal error: {error}")); + assert_eq!(withdraw_plan.requested_spend_lamports, 50); + + let source = model(solana_pubkey::Pubkey::new_unique()); + let deposit = base_intent( + crate::ExSolanaCoreOperation::VoteDepositDelegatorRewards { + vote_account: vote, + source: source.clone(), + deposit_lamports: 51, + }, + std::slice::from_ref(&source), + 51, + ); + let deposit_plan = crate::executor_solana_core_builder_build_prepared_plan(&deposit) + .unwrap_or_else(|error| panic!("unexpected Vote deposit error: {error}")); + assert_eq!(deposit_plan.requested_spend_lamports, 51); + assert!(deposit_plan.instructions[0].accounts[1].is_signer); + } + + #[test] + fn malformed_amounts_commissions_hashes_slots_lockouts_and_seeds_are_rejected() { + assert!(super::validate_commission_percent(101).is_err()); + assert!(super::validate_commission_bps(10_001, "commission_bps").is_err()); + assert!(super::validate_slots(&[]).is_err()); + assert!(super::validate_slots(&[2, 1]).is_err()); + assert!(super::parse_hash("not-a-hash", "bank_hash").is_err()); + assert!(super::validate_seed("", "seed").is_err()); + assert!(super::validate_seed(&"x".repeat(33), "seed").is_err()); + assert!(crate::executor_solana_core_validate_positive_u64(0, "zero", "zero").is_err()); + let oversized = vec![ + crate::ExSolanaCoreVoteLockout { slot: 1, confirmation_count: 1 }; + solana_vote_interface::state::MAX_LOCKOUT_HISTORY + 1 + ]; + assert!(super::lockout_deque(oversized.as_slice(), std::option::Option::None).is_err()); + } +} diff --git a/kb-lib/src/executor/solana/core/zk_elgamal.rs b/kb-lib/src/executor/solana/core/zk_elgamal.rs new file mode 100644 index 0000000..1dae7c6 --- /dev/null +++ b/kb-lib/src/executor/solana/core/zk_elgamal.rs @@ -0,0 +1,451 @@ +// file: kb-lib/src/executor/solana/core/zk_elgamal.rs +// version: 1 + +//! Native ZK ElGamal Proof instruction builders. + +pub(crate) fn executor_solana_core_zk_elgamal_build_prepared_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, +) -> kb_core::Result { + return match &intent.operation { + crate::ExSolanaCoreOperation::ZkElGamalVerifyInline { + proof_type, + proof_data, + context_state, + } => build_inline_plan( + intent, + fee_payer, + *proof_type, + proof_data.as_slice(), + context_state.as_ref(), + ), + crate::ExSolanaCoreOperation::ZkElGamalVerifyFromAccount { + proof_type, + proof_account, + proof_offset, + context_state, + } => build_account_plan( + intent, + fee_payer, + *proof_type, + proof_account, + *proof_offset, + context_state.as_ref(), + ), + crate::ExSolanaCoreOperation::ZkElGamalCloseContextState { + context_state, + destination, + authority, + } => build_close_plan(intent, fee_payer, context_state, destination, authority), + _ => std::result::Result::Err(kb_core::Error::new( + "execution_zk_elgamal_operation_mismatch", + "ZK ElGamal builder received an unrelated Solana core operation", + )), + }; +} + +fn build_inline_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + proof_type: crate::ExSolanaCoreZkElGamalProofType, + proof_data: &[u8], + context_state: std::option::Option<&crate::ExSolanaCoreZkElGamalContextState>, +) -> kb_core::Result { + let expected = proof_type.proof_data_size(); + if proof_data.len() != expected { + return std::result::Result::Err(kb_core::Error::new( + "execution_zk_elgamal_proof_size_invalid", + format!( + "inline ZK ElGamal proof requires exactly {expected} bytes but received {}", + proof_data.len() + ), + )); + } + let accounts = match context_accounts(context_state) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let mut data = std::vec::Vec::with_capacity(1 + proof_data.len()); + data.push(proof_type.discriminator()); + data.extend_from_slice(proof_data); + let instruction = match instruction(accounts, data) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_ZK_ELGAMAL_VERIFY_INLINE_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + std::vec::Vec::new(), + ); +} + +fn build_account_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + proof_type: crate::ExSolanaCoreZkElGamalProofType, + proof_account: &crate::MdPubkey, + proof_offset: u32, + context_state: std::option::Option<&crate::ExSolanaCoreZkElGamalContextState>, +) -> kb_core::Result { + let proof_address = + match crate::executor_solana_core_parse_pubkey(proof_account, "proof_account") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let mut accounts = vec![solana_instruction::AccountMeta::new_readonly(proof_address, false)]; + let context_accounts = match context_accounts(context_state) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + if let std::option::Option::Some(context) = context_state { + if context.account == *proof_account { + return std::result::Result::Err(kb_core::Error::new( + "execution_zk_elgamal_proof_is_context", + "proof and context-state accounts must differ", + )); + } + } + accounts.extend(context_accounts); + let mut data = vec![proof_type.discriminator()]; + data.extend_from_slice(&proof_offset.to_le_bytes()); + let instruction = match instruction(accounts, data) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_ZK_ELGAMAL_VERIFY_FROM_ACCOUNT_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + std::vec::Vec::new(), + ); +} + +fn build_close_plan( + intent: &crate::ExSolanaCoreExecutionIntent, + fee_payer: &solana_pubkey::Pubkey, + context_state: &crate::MdPubkey, + destination: &crate::MdPubkey, + authority: &crate::MdPubkey, +) -> kb_core::Result { + if context_state == destination { + return std::result::Result::Err(kb_core::Error::new( + "execution_zk_elgamal_close_same_account", + "ZK ElGamal context-state and destination accounts must differ", + )); + } + let context_address = + match crate::executor_solana_core_parse_pubkey(context_state, "context_state") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let destination_address = + match crate::executor_solana_core_parse_pubkey(destination, "destination") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority_address = match crate::executor_solana_core_parse_pubkey(authority, "authority") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instruction = match instruction( + vec![ + solana_instruction::AccountMeta::new(context_address, false), + solana_instruction::AccountMeta::new(destination_address, false), + solana_instruction::AccountMeta::new_readonly(authority_address, true), + ], + vec![0], + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return crate::executor_solana_core_finalize_prepared_plan( + intent, + fee_payer, + crate::EX_SOLANA_CORE_ZK_ELGAMAL_CLOSE_CONTEXT_STATE_OPERATION, + vec![instruction], + 0, + std::option::Option::None, + vec![(authority.clone(), std::string::String::from("zk_elgamal_context_authority"))], + ); +} + +fn context_accounts( + context_state: std::option::Option<&crate::ExSolanaCoreZkElGamalContextState>, +) -> kb_core::Result> { + return match context_state { + std::option::Option::Some(context) => { + if context.account == context.authority { + return std::result::Result::Err(kb_core::Error::new( + "execution_zk_elgamal_context_authority_is_account", + "ZK ElGamal context-state account and authority must differ", + )); + } + let account = + match crate::executor_solana_core_parse_pubkey(&context.account, "context_state") { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let authority = match crate::executor_solana_core_parse_pubkey( + &context.authority, + "context_authority", + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + std::result::Result::Ok(vec![ + solana_instruction::AccountMeta::new(account, false), + solana_instruction::AccountMeta::new_readonly(authority, false), + ]) + }, + std::option::Option::None => std::result::Result::Ok(std::vec::Vec::new()), + }; +} + +fn instruction( + accounts: std::vec::Vec, + data: std::vec::Vec, +) -> kb_core::Result { + let program_id = match crate::executor_solana_core_parse_native_program_id( + kb_program_ids::ZK_ELGAMAL_PROOF_PROGRAM_ID, + ) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return std::result::Result::Ok(solana_instruction::Instruction { program_id, accounts, data }); +} + +#[cfg(test)] +mod tests { + fn model(value: solana_pubkey::Pubkey) -> crate::MdPubkey { + return crate::MdPubkey(value.to_string()); + } + + fn policy(signers: &[crate::MdPubkey]) -> crate::ExApiExecutionPolicy { + return crate::ExApiExecutionPolicy { + authorized_signers: signers.to_vec(), + ..crate::ExApiExecutionPolicy::default() + }; + } + + #[test] + fn inline_and_account_modes_match_official_layouts() { + let payer = model(solana_pubkey::Pubkey::new_unique()); + let proof_type = crate::ExSolanaCoreZkElGamalProofType::PubkeyValidity; + let inline = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("zk-inline"), + fee_payer: payer.clone(), + policy: policy(std::slice::from_ref(&payer)), + operation: crate::ExSolanaCoreOperation::ZkElGamalVerifyInline { + proof_type, + proof_data: vec![0; proof_type.proof_data_size()], + context_state: std::option::Option::None, + }, + }; + let inline_plan = crate::executor_solana_core_builder_build_prepared_plan(&inline) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + assert_eq!(inline_plan.instructions[0].data[0], 4); + assert_eq!(inline_plan.instructions[0].data.len(), 1 + proof_type.proof_data_size()); + assert!(inline_plan.instructions[0].accounts.is_empty()); + + let proof_account = solana_pubkey::Pubkey::new_unique(); + let context = solana_pubkey::Pubkey::new_unique(); + let authority = solana_pubkey::Pubkey::new_unique(); + let account = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("zk-account"), + fee_payer: payer.clone(), + policy: policy(&[payer]), + operation: crate::ExSolanaCoreOperation::ZkElGamalVerifyFromAccount { + proof_type: crate::ExSolanaCoreZkElGamalProofType::ZeroCiphertext, + proof_account: model(proof_account), + proof_offset: 44, + context_state: std::option::Option::Some( + crate::ExSolanaCoreZkElGamalContextState { + account: model(context), + authority: model(authority), + }, + ), + }, + }; + let account_plan = crate::executor_solana_core_builder_build_prepared_plan(&account) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + assert_eq!(account_plan.instructions[0].data, vec![1, 44, 0, 0, 0]); + assert_eq!(account_plan.instructions[0].accounts.len(), 3); + assert!(!account_plan.instructions[0].accounts[0].is_writable); + assert!(account_plan.instructions[0].accounts[1].is_writable); + } + + #[test] + fn every_proof_type_has_the_official_discriminator_and_nonzero_size() { + let variants = [ + crate::ExSolanaCoreZkElGamalProofType::ZeroCiphertext, + crate::ExSolanaCoreZkElGamalProofType::CiphertextCiphertextEquality, + crate::ExSolanaCoreZkElGamalProofType::CiphertextCommitmentEquality, + crate::ExSolanaCoreZkElGamalProofType::PubkeyValidity, + crate::ExSolanaCoreZkElGamalProofType::PercentageWithCap, + crate::ExSolanaCoreZkElGamalProofType::BatchedRangeProofU64, + crate::ExSolanaCoreZkElGamalProofType::BatchedRangeProofU128, + crate::ExSolanaCoreZkElGamalProofType::BatchedRangeProofU256, + crate::ExSolanaCoreZkElGamalProofType::GroupedCiphertext2HandlesValidity, + crate::ExSolanaCoreZkElGamalProofType::BatchedGroupedCiphertext2HandlesValidity, + crate::ExSolanaCoreZkElGamalProofType::GroupedCiphertext3HandlesValidity, + crate::ExSolanaCoreZkElGamalProofType::BatchedGroupedCiphertext3HandlesValidity, + ]; + for (index, proof_type) in variants.iter().enumerate() { + assert_eq!(proof_type.discriminator(), (index + 1) as u8); + assert!(proof_type.proof_data_size() > 0); + } + } + + #[test] + fn every_context_state_size_matches_the_official_generic_state() { + let variants = [ + ( + crate::ExSolanaCoreZkElGamalProofType::ZeroCiphertext, + std::mem::size_of::< + solana_zk_elgamal_proof_interface::state::ProofContextState< + solana_zk_elgamal_proof_interface::proof_data::ZeroCiphertextProofContext, + >, + >(), + ), + ( + crate::ExSolanaCoreZkElGamalProofType::CiphertextCiphertextEquality, + std::mem::size_of::< + solana_zk_elgamal_proof_interface::state::ProofContextState< + solana_zk_elgamal_proof_interface::proof_data::CiphertextCiphertextEqualityProofContext, + >, + >(), + ), + ( + crate::ExSolanaCoreZkElGamalProofType::CiphertextCommitmentEquality, + std::mem::size_of::< + solana_zk_elgamal_proof_interface::state::ProofContextState< + solana_zk_elgamal_proof_interface::proof_data::CiphertextCommitmentEqualityProofContext, + >, + >(), + ), + ( + crate::ExSolanaCoreZkElGamalProofType::PubkeyValidity, + std::mem::size_of::< + solana_zk_elgamal_proof_interface::state::ProofContextState< + solana_zk_elgamal_proof_interface::proof_data::PubkeyValidityProofContext, + >, + >(), + ), + ( + crate::ExSolanaCoreZkElGamalProofType::PercentageWithCap, + std::mem::size_of::< + solana_zk_elgamal_proof_interface::state::ProofContextState< + solana_zk_elgamal_proof_interface::proof_data::PercentageWithCapProofContext, + >, + >(), + ), + ( + crate::ExSolanaCoreZkElGamalProofType::BatchedRangeProofU64, + std::mem::size_of::< + solana_zk_elgamal_proof_interface::state::ProofContextState< + solana_zk_elgamal_proof_interface::proof_data::BatchedRangeProofContext, + >, + >(), + ), + ( + crate::ExSolanaCoreZkElGamalProofType::BatchedRangeProofU128, + std::mem::size_of::< + solana_zk_elgamal_proof_interface::state::ProofContextState< + solana_zk_elgamal_proof_interface::proof_data::BatchedRangeProofContext, + >, + >(), + ), + ( + crate::ExSolanaCoreZkElGamalProofType::BatchedRangeProofU256, + std::mem::size_of::< + solana_zk_elgamal_proof_interface::state::ProofContextState< + solana_zk_elgamal_proof_interface::proof_data::BatchedRangeProofContext, + >, + >(), + ), + ( + crate::ExSolanaCoreZkElGamalProofType::GroupedCiphertext2HandlesValidity, + std::mem::size_of::< + solana_zk_elgamal_proof_interface::state::ProofContextState< + solana_zk_elgamal_proof_interface::proof_data::GroupedCiphertext2HandlesValidityProofContext, + >, + >(), + ), + ( + crate::ExSolanaCoreZkElGamalProofType::BatchedGroupedCiphertext2HandlesValidity, + std::mem::size_of::< + solana_zk_elgamal_proof_interface::state::ProofContextState< + solana_zk_elgamal_proof_interface::proof_data::BatchedGroupedCiphertext2HandlesValidityProofContext, + >, + >(), + ), + ( + crate::ExSolanaCoreZkElGamalProofType::GroupedCiphertext3HandlesValidity, + std::mem::size_of::< + solana_zk_elgamal_proof_interface::state::ProofContextState< + solana_zk_elgamal_proof_interface::proof_data::GroupedCiphertext3HandlesValidityProofContext, + >, + >(), + ), + ( + crate::ExSolanaCoreZkElGamalProofType::BatchedGroupedCiphertext3HandlesValidity, + std::mem::size_of::< + solana_zk_elgamal_proof_interface::state::ProofContextState< + solana_zk_elgamal_proof_interface::proof_data::BatchedGroupedCiphertext3HandlesValidityProofContext, + >, + >(), + ), + ]; + for (proof_type, expected) in variants { + assert_eq!(proof_type.context_state_size(), expected); + assert!(expected > 33); + } + } + + #[test] + fn close_context_state_matches_official_accounts_and_signer() { + let context = solana_pubkey::Pubkey::new_unique(); + let destination = solana_pubkey::Pubkey::new_unique(); + let authority = solana_pubkey::Pubkey::new_unique(); + let payer = model(solana_pubkey::Pubkey::new_unique()); + let intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("zk-close"), + fee_payer: payer.clone(), + policy: policy(&[payer, model(authority)]), + operation: crate::ExSolanaCoreOperation::ZkElGamalCloseContextState { + context_state: model(context), + destination: model(destination), + authority: model(authority), + }, + }; + let plan = crate::executor_solana_core_builder_build_prepared_plan(&intent) + .unwrap_or_else(|error| panic!("unexpected error: {error}")); + assert_eq!(plan.instructions[0].data, vec![0]); + assert_eq!(plan.instructions[0].accounts.len(), 3); + assert!(plan.instructions[0].accounts[2].is_signer); + assert_eq!(plan.required_signers.len(), 2); + } + + #[test] + fn malformed_proof_shapes_are_rejected() { + let payer = model(solana_pubkey::Pubkey::new_unique()); + let intent = crate::ExSolanaCoreExecutionIntent { + intent_id: std::string::String::from("zk-invalid"), + fee_payer: payer.clone(), + policy: policy(&[payer]), + operation: crate::ExSolanaCoreOperation::ZkElGamalVerifyInline { + proof_type: crate::ExSolanaCoreZkElGamalProofType::ZeroCiphertext, + proof_data: vec![0], + context_state: std::option::Option::None, + }, + }; + assert!(crate::executor_solana_core_builder_build_prepared_plan(&intent).is_err()); + } +} diff --git a/kb-lib/src/lib.rs b/kb-lib/src/lib.rs index 0186c34..ef12016 100644 --- a/kb-lib/src/lib.rs +++ b/kb-lib/src/lib.rs @@ -1,5 +1,5 @@ // file: kb-lib/src/lib.rs -// version: 17 +// version: 18 //! Consolidated decoder, executor, materializer and shared model library. #![warn(missing_docs)] @@ -172,67 +172,51 @@ pub use self::decoder::DcLendingMarginfiV2Decoder; /// Reserved `kb_decoder_lock_raydium_lp` decoder. pub use self::decoder::DcLockRaydiumLpDecoder; /// Exact decoder for the Metaplex Token Metadata program. -/// Exact decoder for the Metaplex Token Metadata program. pub use self::decoder::DcMetadataMetaplexTokenMetadataDecoder; /// Canonical projection shared by Collection and Use Authority records. pub use self::decoder::DcMetadataMtmAuthorityRecordAccountSnapshot; /// Stable identity and policy of one canonical account snapshot. pub use self::decoder::DcMetadataMtmCanonicalAccountIdentity; -/// Lifecycle represented by one canonical Metaplex account snapshot. /// Lifecycle class of one canonical account layout. pub use self::decoder::DcMetadataMtmCanonicalAccountLifecycle; -/// Provenance for one canonical Metaplex account snapshot. /// Provenance retained for one canonical account observation. pub use self::decoder::DcMetadataMtmCanonicalAccountProvenance; -/// Execution policy attached to one canonical Metaplex account layout. /// Execution policy separated from historical materialization. pub use self::decoder::DcMetadataMtmCanonicalExecutionPolicy; -/// Materialization policy attached to one canonical Metaplex account layout. /// Materialization policy for active or historical state. pub use self::decoder::DcMetadataMtmCanonicalMaterializationPolicy; -/// Canonical Metaplex account snapshot. /// Canonical account envelope used by materializers and replay. pub use self::decoder::DcMetadataMtmCanonicalMetadataAccountSnapshot; -/// Canonical state held by one Metaplex account snapshot. /// Typed canonical Metaplex account state. pub use self::decoder::DcMetadataMtmCanonicalMetadataAccountState; -/// Source of one canonical Metaplex account observation. /// Origin of one canonical account observation. pub use self::decoder::DcMetadataMtmCanonicalObservationSource; /// Canonical projection shared by Metadata and Holder delegate records. pub use self::decoder::DcMetadataMtmDelegateRecordAccountSnapshot; -/// Parsed Metaplex edition account kind. /// Supported edition account layout. pub use self::decoder::DcMetadataMtmEditionAccountKind; -/// Parsed Metaplex edition account snapshot. /// Canonical bounded projection of one edition or master-edition account. pub use self::decoder::DcMetadataMtmEditionAccountSnapshot; -/// Parsed Metaplex edition marker account kind. /// Supported Edition Marker account layout. pub use self::decoder::DcMetadataMtmEditionMarkerAccountKind; -/// Parsed Metaplex edition marker account snapshot. /// Canonical bounded projection of one Edition Marker account. pub use self::decoder::DcMetadataMtmEditionMarkerAccountSnapshot; /// Bounded error returned while decoding a Metadata account. pub use self::decoder::DcMetadataMtmMetadataAccountDecodeError; /// One entry in the exhaustive published account-key audit. pub use self::decoder::DcMetadataMtmMetadataAccountLayoutAuditEntry; -/// Parsed Metaplex MetadataV1 account snapshot. /// Canonical bounded projection of one Metadata account. pub use self::decoder::DcMetadataMtmMetadataAccountSnapshot; /// One bounded historical reservation entry. pub use self::decoder::DcMetadataMtmReservationEntrySnapshot; -/// Parsed Metaplex reservation-list account kind. /// Historical Reservation List account generation. pub use self::decoder::DcMetadataMtmReservationListAccountKind; -/// Parsed Metaplex reservation-list account snapshot. /// Canonical decode-only projection of a historical Reservation List account. pub use self::decoder::DcMetadataMtmReservationListAccountSnapshot; /// Canonical projection of one Token Owned Escrow account. pub use self::decoder::DcMetadataMtmTokenOwnedEscrowAccountSnapshot; /// Authority form used by one Token Owned Escrow account. pub use self::decoder::DcMetadataMtmTokenOwnedEscrowAuthorityKind; -/// Parsed Metaplex token-record account snapshot. /// Canonical bounded projection of one programmable Token Record account. pub use self::decoder::DcMetadataMtmTokenRecordAccountSnapshot; /// Reserved `kb_decoder_metadata_spl_name_service` decoder. @@ -359,6 +343,226 @@ pub use self::decoder::decoder_metadata_metaplex_token_metadata_decode_use_autho pub use self::decoder::decoder_spl_elgamal_registry_parse_elgamal_registry_state; /// Parses one Token-2022 Mint, Account, or Multisig state. pub use self::decoder::decoder_spl_token2022_parse_token2022_state; +/// Exposes the Address Lookup Table close operation code. +pub use self::executor::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_CLOSE_OPERATION; +/// Exposes the Address Lookup Table create operation code. +pub use self::executor::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_CREATE_OPERATION; +/// Exposes the Address Lookup Table deactivate operation code. +pub use self::executor::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_DEACTIVATE_OPERATION; +/// Exposes the Address Lookup Table extend operation code. +pub use self::executor::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_EXTEND_OPERATION; +/// Exposes the Address Lookup Table freeze operation code. +pub use self::executor::EX_SOLANA_CORE_ADDRESS_LOOKUP_TABLE_FREEZE_OPERATION; +/// Exposes the Compute Budget heap-frame operation code. +pub use self::executor::EX_SOLANA_CORE_COMPUTE_BUDGET_REQUEST_HEAP_FRAME_OPERATION; +/// Exposes the Compute Budget loaded-account data-size operation code. +pub use self::executor::EX_SOLANA_CORE_COMPUTE_BUDGET_SET_LOADED_ACCOUNTS_DATA_SIZE_LIMIT_OPERATION; +/// Exposes the Compute Budget unit-limit operation code. +pub use self::executor::EX_SOLANA_CORE_COMPUTE_BUDGET_SET_UNIT_LIMIT_OPERATION; +/// Exposes the Compute Budget unit-price operation code. +pub use self::executor::EX_SOLANA_CORE_COMPUTE_BUDGET_SET_UNIT_PRICE_OPERATION; +/// Exposes the Config account creation operation code. +pub use self::executor::EX_SOLANA_CORE_CONFIG_CREATE_ACCOUNT_OPERATION; +/// Exposes the Config store operation code. +pub use self::executor::EX_SOLANA_CORE_CONFIG_STORE_OPERATION; +/// Exposes the Ed25519 inline verification operation code. +pub use self::executor::EX_SOLANA_CORE_ED25519_VERIFY_INLINE_OPERATION; +/// Exposes the Ed25519 offset-table verification operation code. +pub use self::executor::EX_SOLANA_CORE_ED25519_VERIFY_OFFSETS_OPERATION; +/// Exposes the feature activation operation code. +pub use self::executor::EX_SOLANA_CORE_FEATURE_ACTIVATE_OPERATION; +/// Exposes the feature pending-activation revoke operation code. +pub use self::executor::EX_SOLANA_CORE_FEATURE_REVOKE_PENDING_ACTIVATION_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::executor::EX_SOLANA_CORE_LOADER_V3_CLOSE_ANY_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::executor::EX_SOLANA_CORE_LOADER_V3_CLOSE_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::executor::EX_SOLANA_CORE_LOADER_V3_CREATE_BUFFER_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::executor::EX_SOLANA_CORE_LOADER_V3_DEPLOY_WITH_MAX_PROGRAM_LEN_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::executor::EX_SOLANA_CORE_LOADER_V3_EXTEND_PROGRAM_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::executor::EX_SOLANA_CORE_LOADER_V3_SET_BUFFER_AUTHORITY_CHECKED_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::executor::EX_SOLANA_CORE_LOADER_V3_SET_BUFFER_AUTHORITY_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::executor::EX_SOLANA_CORE_LOADER_V3_SET_UPGRADE_AUTHORITY_CHECKED_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::executor::EX_SOLANA_CORE_LOADER_V3_SET_UPGRADE_AUTHORITY_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::executor::EX_SOLANA_CORE_LOADER_V3_UPGRADE_OPERATION; +/// Exposes Loader v3 operation codes. +pub use self::executor::EX_SOLANA_CORE_LOADER_V3_WRITE_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::executor::EX_SOLANA_CORE_LOADER_V4_COPY_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::executor::EX_SOLANA_CORE_LOADER_V4_CREATE_BUFFER_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::executor::EX_SOLANA_CORE_LOADER_V4_DEPLOY_FROM_SOURCE_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::executor::EX_SOLANA_CORE_LOADER_V4_DEPLOY_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::executor::EX_SOLANA_CORE_LOADER_V4_FINALIZE_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::executor::EX_SOLANA_CORE_LOADER_V4_RETRACT_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::executor::EX_SOLANA_CORE_LOADER_V4_SET_PROGRAM_LENGTH_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::executor::EX_SOLANA_CORE_LOADER_V4_TRANSFER_AUTHORITY_OPERATION; +/// Exposes Loader v4 operation codes. +pub use self::executor::EX_SOLANA_CORE_LOADER_V4_WRITE_OPERATION; +/// Exposes every stable callable Solana core operation code. +pub use self::executor::EX_SOLANA_CORE_OPERATION_CODES; +/// Exposes the secp256k1 inline verification operation code. +pub use self::executor::EX_SOLANA_CORE_SECP256K1_VERIFY_INLINE_OPERATION; +/// Exposes the secp256k1 offset-table verification operation code. +pub use self::executor::EX_SOLANA_CORE_SECP256K1_VERIFY_OFFSETS_OPERATION; +/// Exposes the secp256r1 inline verification operation code. +pub use self::executor::EX_SOLANA_CORE_SECP256R1_VERIFY_INLINE_OPERATION; +/// Exposes the secp256r1 offset-table verification operation code. +pub use self::executor::EX_SOLANA_CORE_SECP256R1_VERIFY_OFFSETS_OPERATION; +/// Exposes the Slashing violation-report close operation code. +pub use self::executor::EX_SOLANA_CORE_SLASHING_CLOSE_VIOLATION_REPORT_OPERATION; +/// Exposes the Slashing duplicate-block proof submission operation code. +pub use self::executor::EX_SOLANA_CORE_SLASHING_SUBMIT_DUPLICATE_BLOCK_PROOF_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::executor::EX_SOLANA_CORE_STAKE_AUTHORIZE_CHECKED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::executor::EX_SOLANA_CORE_STAKE_AUTHORIZE_CHECKED_WITH_SEED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::executor::EX_SOLANA_CORE_STAKE_AUTHORIZE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::executor::EX_SOLANA_CORE_STAKE_AUTHORIZE_WITH_SEED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::executor::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_AND_DELEGATE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::executor::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_CHECKED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::executor::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::executor::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_WITH_SEED_AND_DELEGATE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::executor::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_WITH_SEED_CHECKED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::executor::EX_SOLANA_CORE_STAKE_CREATE_ACCOUNT_WITH_SEED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::executor::EX_SOLANA_CORE_STAKE_DEACTIVATE_DELINQUENT_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::executor::EX_SOLANA_CORE_STAKE_DEACTIVATE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::executor::EX_SOLANA_CORE_STAKE_DELEGATE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::executor::EX_SOLANA_CORE_STAKE_GET_MINIMUM_DELEGATION_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::executor::EX_SOLANA_CORE_STAKE_INITIALIZE_CHECKED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::executor::EX_SOLANA_CORE_STAKE_INITIALIZE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::executor::EX_SOLANA_CORE_STAKE_MERGE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::executor::EX_SOLANA_CORE_STAKE_MOVE_LAMPORTS_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::executor::EX_SOLANA_CORE_STAKE_MOVE_STAKE_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::executor::EX_SOLANA_CORE_STAKE_SET_LOCKUP_CHECKED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::executor::EX_SOLANA_CORE_STAKE_SET_LOCKUP_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::executor::EX_SOLANA_CORE_STAKE_SPLIT_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::executor::EX_SOLANA_CORE_STAKE_SPLIT_WITH_SEED_OPERATION; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::executor::EX_SOLANA_CORE_STAKE_WITHDRAW_OPERATION; +/// Exposes the System allocate operation code. +pub use self::executor::EX_SOLANA_CORE_SYSTEM_ALLOCATE_OPERATION; +/// Exposes the System seeded allocate operation code. +pub use self::executor::EX_SOLANA_CORE_SYSTEM_ALLOCATE_WITH_SEED_OPERATION; +/// Exposes the System assign operation code. +pub use self::executor::EX_SOLANA_CORE_SYSTEM_ASSIGN_OPERATION; +/// Exposes the System seeded assign operation code. +pub use self::executor::EX_SOLANA_CORE_SYSTEM_ASSIGN_WITH_SEED_OPERATION; +/// Exposes the System prefunded create-account operation code. +pub use self::executor::EX_SOLANA_CORE_SYSTEM_CREATE_ACCOUNT_ALLOW_PREFUND_OPERATION; +/// Exposes the System create-account operation code. +pub use self::executor::EX_SOLANA_CORE_SYSTEM_CREATE_ACCOUNT_OPERATION; +/// Exposes the System seeded create-account operation code. +pub use self::executor::EX_SOLANA_CORE_SYSTEM_CREATE_ACCOUNT_WITH_SEED_OPERATION; +/// Exposes the durable nonce advance operation code. +pub use self::executor::EX_SOLANA_CORE_SYSTEM_NONCE_ADVANCE_OPERATION; +/// Exposes the durable nonce authorize operation code. +pub use self::executor::EX_SOLANA_CORE_SYSTEM_NONCE_AUTHORIZE_OPERATION; +/// Exposes the durable nonce account creation operation code. +pub use self::executor::EX_SOLANA_CORE_SYSTEM_NONCE_CREATE_OPERATION; +/// Exposes the seeded durable nonce account creation operation code. +pub use self::executor::EX_SOLANA_CORE_SYSTEM_NONCE_CREATE_WITH_SEED_OPERATION; +/// Exposes the durable nonce initialize operation code. +pub use self::executor::EX_SOLANA_CORE_SYSTEM_NONCE_INITIALIZE_OPERATION; +/// Exposes the durable nonce upgrade operation code. +pub use self::executor::EX_SOLANA_CORE_SYSTEM_NONCE_UPGRADE_OPERATION; +/// Exposes the durable nonce withdraw operation code. +pub use self::executor::EX_SOLANA_CORE_SYSTEM_NONCE_WITHDRAW_OPERATION; +/// Exposes the System multi-transfer operation code. +pub use self::executor::EX_SOLANA_CORE_SYSTEM_TRANSFER_MANY_OPERATION; +/// Exposes the System transfer operation code. +pub use self::executor::EX_SOLANA_CORE_SYSTEM_TRANSFER_OPERATION; +/// Exposes the System seeded transfer operation code. +pub use self::executor::EX_SOLANA_CORE_SYSTEM_TRANSFER_WITH_SEED_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::executor::EX_SOLANA_CORE_VOTE_AUTHORIZE_CHECKED_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::executor::EX_SOLANA_CORE_VOTE_AUTHORIZE_CHECKED_WITH_SEED_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::executor::EX_SOLANA_CORE_VOTE_AUTHORIZE_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::executor::EX_SOLANA_CORE_VOTE_AUTHORIZE_WITH_SEED_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::executor::EX_SOLANA_CORE_VOTE_COMPACT_UPDATE_VOTE_STATE_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::executor::EX_SOLANA_CORE_VOTE_COMPACT_UPDATE_VOTE_STATE_SWITCH_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::executor::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::executor::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_V2_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::executor::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_WITH_SEED_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::executor::EX_SOLANA_CORE_VOTE_CREATE_ACCOUNT_WITH_SEED_V2_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::executor::EX_SOLANA_CORE_VOTE_DEPOSIT_DELEGATOR_REWARDS_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::executor::EX_SOLANA_CORE_VOTE_INITIALIZE_ACCOUNT_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::executor::EX_SOLANA_CORE_VOTE_INITIALIZE_ACCOUNT_V2_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::executor::EX_SOLANA_CORE_VOTE_SUBMIT_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::executor::EX_SOLANA_CORE_VOTE_SWITCH_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::executor::EX_SOLANA_CORE_VOTE_TOWER_SYNC_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::executor::EX_SOLANA_CORE_VOTE_TOWER_SYNC_SWITCH_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::executor::EX_SOLANA_CORE_VOTE_UPDATE_COMMISSION_BPS_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::executor::EX_SOLANA_CORE_VOTE_UPDATE_COMMISSION_COLLECTOR_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::executor::EX_SOLANA_CORE_VOTE_UPDATE_COMMISSION_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::executor::EX_SOLANA_CORE_VOTE_UPDATE_VALIDATOR_IDENTITY_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::executor::EX_SOLANA_CORE_VOTE_UPDATE_VOTE_STATE_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::executor::EX_SOLANA_CORE_VOTE_UPDATE_VOTE_STATE_SWITCH_OPERATION; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::executor::EX_SOLANA_CORE_VOTE_WITHDRAW_OPERATION; +/// Exposes the ZK ElGamal context-state close operation code. +pub use self::executor::EX_SOLANA_CORE_ZK_ELGAMAL_CLOSE_CONTEXT_STATE_OPERATION; +/// Exposes the ZK ElGamal proof-account verification operation code. +pub use self::executor::EX_SOLANA_CORE_ZK_ELGAMAL_VERIFY_FROM_ACCOUNT_OPERATION; +/// Exposes the ZK ElGamal inline proof verification operation code. +pub use self::executor::EX_SOLANA_CORE_ZK_ELGAMAL_VERIFY_INLINE_OPERATION; /// Exposes the reserved `ExAdapterSaberDecimalWrapperExecutor` implementation. pub use self::executor::ExAdapterSaberDecimalWrapperExecutor; /// Exposes the reserved `ExAdminJupiterLockExecutor` implementation. @@ -559,8 +763,34 @@ pub use self::executor::ExRouterOkxLabsV1Executor; pub use self::executor::ExRouterOkxLabsV2Executor; /// Exposes the reserved `ExRwaOndoGlobalMarketsExecutor` implementation. pub use self::executor::ExRwaOndoGlobalMarketsExecutor; -/// Exposes the reserved `ExSolanaCoreExecutor` implementation. +/// Exposes one Config Program key entry. +pub use self::executor::ExSolanaCoreConfigKey; +/// Exposes one Ed25519 verification offset record. +pub use self::executor::ExSolanaCoreEd25519VerificationOffsets; +/// Exposes the complete typed Solana core intent. +pub use self::executor::ExSolanaCoreExecutionIntent; +/// Exposes the Solana core executor. pub use self::executor::ExSolanaCoreExecutor; +/// Exposes typed Solana core operation arguments. +pub use self::executor::ExSolanaCoreOperation; +/// Exposes one secp256k1 verification offset record. +pub use self::executor::ExSolanaCoreSecp256k1VerificationOffsets; +/// Exposes one secp256r1 verification offset record. +pub use self::executor::ExSolanaCoreSecp256r1VerificationOffsets; +/// Exposes Stake Program operation codes and the authority-role selector. +pub use self::executor::ExSolanaCoreStakeAuthorizationKind; +/// Exposes one recipient in a System multi-transfer. +pub use self::executor::ExSolanaCoreSystemTransferRecipient; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::executor::ExSolanaCoreVoteAuthorizationKind; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::executor::ExSolanaCoreVoteCommissionKind; +/// Exposes Vote Program operation codes and typed Vote selectors. +pub use self::executor::ExSolanaCoreVoteLockout; +/// Exposes optional ZK ElGamal context-state accounts. +pub use self::executor::ExSolanaCoreZkElGamalContextState; +/// Exposes the official ZK ElGamal proof kinds. +pub use self::executor::ExSolanaCoreZkElGamalProofType; /// Exposes the reserved `ExSplAccountCompressionExecutor` implementation. pub use self::executor::ExSplAccountCompressionExecutor; /// Exposes the reserved `ExSplAssociatedTokenAccountExecutor` implementation. @@ -703,7 +933,6 @@ pub use self::materializer::materializer_metadata_materialize_token2022_snapshot pub use self::materializer::materializer_token_materialize_token2022_state_snapshot; /// Current canonical transaction document version. pub use self::model::MD_CANONICAL_TRANSACTION_FORMAT_VERSION; -/// Source-neutral replay contracts. /// Version of the normalized core replay contract. pub use self::model::MD_CORE_REPLAY_INPUT_CONTRACT_VERSION; /// Canonical address lookup table reference. @@ -1111,7 +1340,6 @@ pub(crate) use self::decoder::decoder_solana_core_read_u32_le; pub(crate) use self::decoder::decoder_solana_core_read_u64_le; /// Resolves positional instruction accounts and validates their core indexes. pub(crate) use self::decoder::decoder_solana_core_resolve_accounts; -/// Resolves a secp256k1 instruction reference. The runtime format has no current-instruction /// sentinel: every `u8` value is an explicit outer instruction index. pub(crate) use self::decoder::decoder_solana_core_resolve_u8_instruction_payload; /// Resolves an Ed25519 or secp256r1 instruction reference using the official `u16::MAX` sentinel. @@ -1182,6 +1410,44 @@ pub(crate) use self::decoder::decoder_spl_token2022_decode; pub(crate) use self::decoder::decoder_spl_token2022_entry_for_tag; /// Returns the first byte of one retained Token-2022 payload. pub(crate) use self::decoder::decoder_spl_token2022_payload_tag; +/// Canonical tracing target for this crate. +pub(crate) use self::executor::EX_SOLANA_CORE_TRACING_TARGET; +/// Crate-root access to `build_prepared_plan` from `address_lookup_table`. +pub(crate) use self::executor::executor_solana_core_address_lookup_table_build_prepared_plan; +/// Crate-root access to `build_prepared_plan` from `builder`. +pub(crate) use self::executor::executor_solana_core_builder_build_prepared_plan; +/// Crate-root access to `finalize_prepared_plan` from `builder`. +pub(crate) use self::executor::executor_solana_core_finalize_prepared_plan; +/// Builds one typed validation error without duplicating domain-local wrappers. +pub(crate) use self::executor::executor_solana_core_invalid; +/// Crate-root access to `build_prepared_plan` from `loader_v3`. +pub(crate) use self::executor::executor_solana_core_loader_v3_build_prepared_plan; +/// Crate-root access to `build_prepared_plan` from `loader_v4`. +pub(crate) use self::executor::executor_solana_core_loader_v4_build_prepared_plan; +/// Crate-root access to `build_prepared_plan` from `native_admin`. +pub(crate) use self::executor::executor_solana_core_native_admin_build_prepared_plan; +/// Parses one canonical native program identifier. +pub(crate) use self::executor::executor_solana_core_parse_native_program_id; +/// Parses one model public key with the canonical execution error contract. +pub(crate) use self::executor::executor_solana_core_parse_pubkey; +/// Crate-root access to `build_prepared_plan` from `precompiles`. +pub(crate) use self::executor::executor_solana_core_precompiles_build_prepared_plan; +/// Crate-root access to `build_prepared_plan` from `slashing`. +pub(crate) use self::executor::executor_solana_core_slashing_build_prepared_plan; +/// Crate-root access to `build_prepared_plan` from `stake`. +pub(crate) use self::executor::executor_solana_core_stake_build_prepared_plan; +/// Validates that two model public keys identify distinct accounts. +pub(crate) use self::executor::executor_solana_core_validate_distinct_pubkeys; +/// Validates an exact byte-slice length while preserving the caller error code. +pub(crate) use self::executor::executor_solana_core_validate_exact_length; +/// Validates that an unsigned execution amount is strictly positive. +pub(crate) use self::executor::executor_solana_core_validate_positive_u64; +/// Validates one `Pubkey::create_with_seed` derivation with caller-owned error codes. +pub(crate) use self::executor::executor_solana_core_validate_seeded_address; +/// Crate-root access to `build_prepared_plan` from `vote`. +pub(crate) use self::executor::executor_solana_core_vote_build_prepared_plan; +/// Crate-root access to `build_prepared_plan` from `zk_elgamal`. +pub(crate) use self::executor::executor_solana_core_zk_elgamal_build_prepared_plan; /// Canonical processor name for the native and SPL administration materializer. pub(crate) use self::materializer::MT_ADMIN_PROCESSOR_NAME; /// Canonical tracing target for the native and SPL administration materializer. diff --git a/kb-lib/tests/external_executor_api.rs b/kb-lib/tests/external_executor_api.rs index 5da1281..3f72d2f 100644 --- a/kb-lib/tests/external_executor_api.rs +++ b/kb-lib/tests/external_executor_api.rs @@ -1,5 +1,5 @@ // file: kb-lib/tests/external_executor_api.rs -// version: 2 +// version: 3 //! Downstream-style compilation contract for externally implemented executors. @@ -92,4 +92,29 @@ fn external_executor_uses_only_the_public_kb_lib_contract() { assert!(plan.required_signers.is_empty()); assert_eq!(plan.policy.simulation, kb_lib::ExApiExecutionSimulationPolicy::Required); assert!(plan.policy.dry_run); + let solana_core_executor = kb_lib::ExSolanaCoreExecutor; + let solana_core_program_id = + kb_lib::MdProgramId(kb_program_ids::COMPUTE_BUDGET_PROGRAM_ID.to_string()); + let solana_core_capability = kb_lib::ExApiTypedInstructionExecutor::capability( + &solana_core_executor, + &solana_core_program_id, + kb_lib::EX_SOLANA_CORE_COMPUTE_BUDGET_SET_UNIT_LIMIT_OPERATION, + ); + assert!(solana_core_capability.is_supported()); + let solana_core_intent = kb_lib::ExSolanaCoreExecutionIntent { + intent_id: "external-solana-core".to_string(), + fee_payer: kb_lib::MdPubkey(kb_program_ids::SYSTEM_PROGRAM_ID.to_string()), + policy: kb_lib::ExApiExecutionPolicy::default(), + operation: kb_lib::ExSolanaCoreOperation::ComputeBudgetSetUnitLimit { units: 500_000 }, + }; + let solana_core_plan = kb_lib::ExApiTypedInstructionExecutor::build_prepared_plan( + &solana_core_executor, + &solana_core_intent, + ) + .unwrap_or_else(|error| panic!("Solana core executor failed: {error}")); + assert_eq!(solana_core_plan.instructions.len(), 1); + assert_eq!( + solana_core_plan.operation_code, + kb_lib::EX_SOLANA_CORE_COMPUTE_BUDGET_SET_UNIT_LIMIT_OPERATION + ); } diff --git a/scripts/audit_khadhroony_workspace_rules.py b/scripts/audit_khadhroony_workspace_rules.py index 0b56c26..b977a68 100755 --- a/scripts/audit_khadhroony_workspace_rules.py +++ b/scripts/audit_khadhroony_workspace_rules.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # file: scripts/audit_khadhroony_workspace_rules.py -# version: 8 +# version: 9 """Audit mechanically verifiable rules specific to khadhroony-bot3.""" @@ -438,13 +438,13 @@ def audit_reserved_executor_scaffolds(root: pathlib.Path) -> list[Violation]: "reserved executor must build only an explicit zero-instruction plan", ) ) - if reserved_count != 104: + if reserved_count != 103: violations.append( Violation( "KH_EX007", "kb-lib/src/executor", 1, - f"expected 104 reserved executor boundaries, found {reserved_count}", + f"expected 103 reserved executor boundaries, found {reserved_count}", ) ) return violations diff --git a/scripts/audit_rust_general_rules.py b/scripts/audit_rust_general_rules.py index 679f4af..f405aa0 100755 --- a/scripts/audit_rust_general_rules.py +++ b/scripts/audit_rust_general_rules.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # file: scripts/audit_rust_general_rules.py -# version: 7 +# version: 8 """Audit mechanically verifiable Rust rules shared by all Rust projects.""" @@ -116,6 +116,10 @@ def audit_file(root: pathlib.Path, path: pathlib.Path) -> list[Violation]: previous_export_visibility: str | None = None previous_export_key: tuple[object, ...] | None = None blank_since_export = False + crate_visibility_seen = False + enforce_visibility_order = relative == "kb-lib/src/lib.rs" or relative.startswith( + "kb-lib/src/executor" + ) for index, line in enumerate(lines, 1): if not line.strip(): if previous_export_line is not None: @@ -125,6 +129,32 @@ def audit_file(root: pathlib.Path, path: pathlib.Path) -> list[Violation]: continue if line.startswith("pub use ") or line.startswith("pub(crate) use "): visibility = "pub(crate)" if line.startswith("pub(crate) use ") else "pub" + if enforce_visibility_order and visibility == "pub" and crate_visibility_seen: + violations.append( + Violation( + "RUST024", + relative, + index, + "`pub use` block must precede the `pub(crate) use` block", + ) + ) + if visibility == "pub(crate)": + crate_visibility_seen = True + if ( + enforce_visibility_order + and + previous_export_line is not None + and previous_export_visibility != visibility + and not blank_since_export + ): + violations.append( + Violation( + "RUST025", + relative, + index, + "re-export blocks with different visibility require one empty line", + ) + ) if ( previous_export_line is not None and previous_export_visibility == visibility