├── .gitignore ├── .pre-commit-config.yaml ├── .vscode └── settings.json ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── README.md ├── examples ├── .gitignore ├── README.md ├── anchor │ ├── drift │ │ ├── README.md │ │ ├── drift_client_consumer │ │ │ ├── Cargo.toml │ │ │ ├── src │ │ │ │ └── lib.rs │ │ │ └── tests │ │ │ │ └── test_program_id.rs │ │ ├── drift_interface │ │ │ ├── .gitignore │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ ├── accounts.rs │ │ │ │ ├── errors.rs │ │ │ │ ├── events.rs │ │ │ │ ├── instructions.rs │ │ │ │ ├── lib.rs │ │ │ │ └── typedefs.rs │ │ └── idl.json │ ├── ix_blank │ │ ├── anchor_ix_blank_interface │ │ │ ├── .gitignore │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ ├── instructions.rs │ │ │ │ └── lib.rs │ │ └── idl.json │ ├── ix_no_accounts │ │ ├── anchor_ix_no_accounts_interface │ │ │ ├── .gitignore │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ ├── instructions.rs │ │ │ │ └── lib.rs │ │ └── idl.json │ ├── ix_no_accounts_pubkey_arg │ │ ├── anchor_ix_no_accounts_pubkey_arg_interface │ │ │ ├── .gitignore │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ ├── instructions.rs │ │ │ │ └── lib.rs │ │ └── idl.json │ ├── ix_no_args │ │ ├── anchor_ix_no_args_interface │ │ │ ├── .gitignore │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ ├── instructions.rs │ │ │ │ └── lib.rs │ │ └── idl.json │ ├── ix_no_privilege │ │ ├── anchor_ix_no_privilege_interface │ │ │ ├── .gitignore │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ ├── instructions.rs │ │ │ │ └── lib.rs │ │ └── idl.json │ ├── marinade │ │ ├── README.md │ │ ├── idl.json │ │ ├── marinade_finance_client_consumer │ │ │ ├── Cargo.toml │ │ │ ├── README.md │ │ │ ├── src │ │ │ │ └── lib.rs │ │ │ └── tests │ │ │ │ ├── test-read-state.rs │ │ │ │ └── test-serde.rs │ │ └── marinade_finance_interface │ │ │ ├── .gitignore │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ ├── accounts.rs │ │ │ ├── instructions.rs │ │ │ ├── lib.rs │ │ │ └── typedefs.rs │ └── unstake_it │ │ ├── idl.json │ │ ├── unstake_client_consumer │ │ ├── Cargo.toml │ │ └── src │ │ │ └── main.rs │ │ ├── unstake_interface │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── accounts.rs │ │ │ ├── errors.rs │ │ │ ├── instructions.rs │ │ │ ├── lib.rs │ │ │ └── typedefs.rs │ │ └── unstake_onchain_consumer │ │ ├── Cargo.toml │ │ └── src │ │ ├── entrypoint.rs │ │ └── lib.rs ├── bincode │ ├── stake │ │ ├── idl.json │ │ ├── stake_program_client │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ │ └── lib.rs │ │ └── stake_program_interface │ │ │ ├── .gitignore │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ ├── instructions.rs │ │ │ ├── lib.rs │ │ │ └── typedefs.rs │ └── system │ │ ├── idl.json │ │ ├── system_program_client │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ │ └── system_program_interface │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ └── src │ │ ├── instructions.rs │ │ └── lib.rs └── shank │ ├── ix_blank │ ├── idl.json │ └── shank_ix_blank_interface │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ └── src │ │ ├── instructions.rs │ │ └── lib.rs │ ├── ix_no_accounts │ ├── idl.json │ └── shank_ix_no_accounts_interface │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ └── src │ │ ├── instructions.rs │ │ └── lib.rs │ ├── ix_no_accounts_pubkey_arg │ ├── idl.json │ └── shank_ix_no_accounts_pubkey_arg_interface │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ └── src │ │ ├── instructions.rs │ │ └── lib.rs │ ├── ix_no_args │ ├── idl.json │ └── shank_ix_no_args_interface │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ └── src │ │ ├── instructions.rs │ │ └── lib.rs │ ├── ix_no_privilege │ ├── idl.json │ └── shank_ix_no_privilege_interface │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ └── src │ │ ├── instructions.rs │ │ └── lib.rs │ ├── phoenix_v1 │ ├── README.md │ ├── idl.json │ └── phoenix_v1_interface │ │ ├── .gitignore │ │ ├── Cargo.toml │ │ └── src │ │ ├── errors.rs │ │ ├── instructions.rs │ │ ├── lib.rs │ │ └── typedefs.rs │ └── token_metadata │ ├── idl.json │ ├── mpl_token_metadata_client_consumer │ ├── Cargo.toml │ ├── src │ │ └── main.rs │ └── tests │ │ └── test-serde.rs │ ├── mpl_token_metadata_interface │ ├── .gitignore │ ├── Cargo.toml │ └── src │ │ ├── accounts.rs │ │ ├── errors.rs │ │ ├── instructions.rs │ │ ├── lib.rs │ │ └── typedefs.rs │ └── mpl_token_metadata_onchain_consumer │ ├── Cargo.toml │ └── src │ ├── entrypoint.rs │ └── lib.rs ├── install-precommit.sh ├── solores ├── Cargo.toml ├── README.md ├── src │ ├── idl_format │ │ ├── README.md │ │ ├── anchor │ │ │ ├── accounts │ │ │ │ ├── account.rs │ │ │ │ └── mod.rs │ │ │ ├── errors │ │ │ │ ├── error.rs │ │ │ │ └── mod.rs │ │ │ ├── events │ │ │ │ ├── event.rs │ │ │ │ └── mod.rs │ │ │ ├── instructions │ │ │ │ ├── instruction.rs │ │ │ │ └── mod.rs │ │ │ ├── mod.rs │ │ │ └── typedefs │ │ │ │ ├── mod.rs │ │ │ │ └── typedef.rs │ │ ├── bincode │ │ │ ├── errors │ │ │ │ ├── error.rs │ │ │ │ └── mod.rs │ │ │ ├── instructions │ │ │ │ ├── instruction.rs │ │ │ │ └── mod.rs │ │ │ ├── mod.rs │ │ │ └── typedefs │ │ │ │ ├── mod.rs │ │ │ │ └── typedef.rs │ │ ├── mod.rs │ │ └── shank │ │ │ ├── accounts.rs │ │ │ ├── errors │ │ │ ├── error.rs │ │ │ └── mod.rs │ │ │ ├── instructions │ │ │ ├── instruction.rs │ │ │ └── mod.rs │ │ │ ├── mod.rs │ │ │ └── typedefs │ │ │ ├── mod.rs │ │ │ └── typedef.rs │ ├── lib.rs │ ├── main.rs │ ├── utils.rs │ ├── write_cargotoml.rs │ ├── write_gitignore.rs │ └── write_src.rs └── tests │ └── test_gen_and_check_examples.rs └── test_utils ├── Cargo.toml └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | default_stages: [commit] 2 | repos: 3 | - repo: https://github.com/doublify/pre-commit-rust 4 | rev: v1.0 5 | hooks: 6 | - id: fmt 7 | - id: clippy 8 | args: ["--tests", "--", "-Dwarnings"] -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[json]": { 3 | "editor.tabSize": 2 4 | }, 5 | "[jsonc]": { 6 | "editor.tabSize": 2 7 | }, 8 | "editor.formatOnSave": true, 9 | "editor.defaultFormatter": "esbenp.prettier-vscode", 10 | "[rust]": { 11 | "editor.defaultFormatter": "rust-lang.rust-analyzer" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [0.8.0] - 2024-08-23 9 | 10 | ### Breaking 11 | 12 | - Updated all dependencies to latest compatible versions. 13 | 14 | ### Changed 15 | 16 | - Anchor event struct fields are now public. 17 | 18 | ### Added 19 | 20 | - `bytes_to_u8` feature that replaces all generated primitive instances of `bytes` with `u8`. 21 | 22 | ### Fixed 23 | 24 | - Anchor `accounts` and `typedefs` generated names now conform to Rust PascalCase convention. 25 | 26 | ## [0.7.0] - 2023-12-28 27 | 28 | ### Breaking 29 | 30 | - Changed instruction codegen internals to support `*_with_program_id()` 31 | - Changed `IdlFormat` trait def to make dependency and `Cargo.toml` writing more dyn 32 | - Now panics instead of continuing if accounts with the same name are detected in the any IDL instruction. 33 | 34 | ### Added 35 | 36 | - bincode support 37 | - `*_ix_with_program_id()`, `*_invoke_with_program_id()`, `*_invoke_signed_with_program_id()` 38 | 39 | ## [0.6.1] - 2023-12-27 40 | 41 | ### Fixed 42 | 43 | - imports for `instructions.rs` not importing `solana_program::pubkey::Pubkey` if no instruction has accounts but some args has pubkey 44 | 45 | ## [0.6.0] - 2023-12-27 46 | 47 | ### Breaking 48 | 49 | - Removed instruction function generics 50 | 51 | ### Changed 52 | 53 | - split `_verify_account_privileges()` to be composed of 2 separate functions: `_verify_writable_privileges()` and `_verify_signer_privileges()` 54 | 55 | ### Fixed 56 | 57 | - Add required `derive` feature to `bytemuck` dependency 58 | 59 | ## [0.5.0] - 2023-12-12 60 | 61 | ### Breaking 62 | 63 | - Program ID now defaults to `TH1S1SNoTAVAL1DPUBKEYDoNoTUSE11111111111111` instead of system program ID if ID not provided in IDL 64 | - `*IxData`, `*ProgramIx` and anchor accounts no longer implement `BorshSerialize` since it does not follow the borsh spec. The methods have been moved to their intrinsic impl. 65 | - Change `*IxData` and `*ProgramIx`s' `deserialize()` fn signature to accept `&[u8]` instead of `&mut &[u8]`. `&mut &[u8]` was previously used for borsh compatibility. 66 | - No longer generates `*_verify_account_privileges()` function if instruction has no privileged accounts (only non-signer and non-writable accounts). 67 | - No longer generates `*IxArgs` struct if no instruction args. 68 | - No longer generates `*Accounts` `*Keys` structs and `*_verify_account_keys()` function if instruction has no account inputs. 69 | - All reference types for `*Keys` and `*Accounts` have been changed to pass by value since they impl `Copy` 70 | - Replaced `solana-program` dependency with `bs58` 71 | 72 | ### Changed 73 | 74 | - `From for [AccountMeta; LEN]` now uses direct struct initialization instead of the `AccountMeta` constructor functions 75 | 76 | ## [0.4.0] - 2023-08-07 77 | 78 | ### Breaking 79 | 80 | - `*_verify_account_privileges()` functions now return `Err((&AccountInfo, ProgramError))` on err where `&AccountInfo` is the first offending account. 81 | 82 | ## [0.3.0] - 2023-08-03 83 | 84 | ### Breaking 85 | 86 | - Removed unused macro `gen_body_newtype_slice` 87 | 88 | ### Added 89 | 90 | - Simple support for Pod/zero copy typedefs with the `-z` option 91 | 92 | ## [0.2.2] - 2023-07-30 93 | 94 | ### Changed 95 | 96 | - made more stuff `pub` for the lib 97 | 98 | ### Added 99 | 100 | - `*_verify_account_privileges()` generated function for instructions 101 | - `*_verify_account_keys()` generated function for instructions 102 | 103 | ## [0.2.1] - 2023-07-30 104 | 105 | ### Changed 106 | 107 | - document `--program-id` default values in `--help` 108 | - all internal modules are now `pub` to allow people to use the library 109 | 110 | ## [0.2.0] - 2023-07-30 111 | 112 | ### Breaking 113 | 114 | - Removed variable lifetimes from the various `AccountInfo`s in `*Accounts`. They all now share the same lifetime `'info` 115 | - Changed the various `*IxData` structs to own the `*IxArgs` structs instead of a reference 116 | 117 | ### Added 118 | 119 | - `impl From<[Pubkey; *_IX_ACCOUNTS_LEN]> for *Keys` for easier indexing 120 | - `impl From<&[AccountInfo; *_IX_ACCOUNTS_LEN]> for *Accounts` for easier CPIs 121 | - `deserialize` method for `*IxData`. Not using `BorshDeserialize` trait due to breaking change in trait def between 0.9 and 0.10 122 | - `derive(PartialEq)` for all typedefs and `*IxArgs` and `*IxData` 123 | - `**Account` for anchor accounts newtype that includes the discriminant in borsh serde 124 | - Added `--program-id` option to allow setting of custom program IDs to accomodate anchor IDLs not containing their program IDs. 125 | 126 | ### Changed 127 | 128 | - internals refactored to enable dynamic modules for the generated crate 129 | - Anchor: standardize `*IxArgs` to be PascalCase per rust convention instead of camelCase in the IDL 130 | 131 | ### Fixed 132 | 133 | - Bug that wasn't including the generated `errors` module into the crate 134 | 135 | ## [0.1.4] - 2023-07-21 136 | 137 | ### Added 138 | 139 | - `--serde-vers` to configure `serde` as an optional dependency for the generated crate 140 | 141 | ### Changed 142 | 143 | - Allow toml maps to be passed to the various `--*-vers` options to allow for values like `"workspace = true"` 144 | 145 | ## [0.1.3] - 2023-06-19 146 | 147 | ### Changed 148 | 149 | - Upgrade default solana-program version to `^1.16` and borsh version to `^0.10` 150 | 151 | ## [0.1.2] - 2023-01-27 152 | 153 | ### Fixed 154 | 155 | - Handle inner `Accounts<'_>` struct for anchor. 156 | 157 | ## [0.1.1] - 2023-01-09 158 | 159 | ### Fixed 160 | 161 | - `defined` types being incorrectly converted to pascal case 162 | - `metadata` field is now optional for anchor IDLs and program address is set to `11111111111111111111111111111111`, with warning logged, if not present 163 | 164 | ### Added 165 | 166 | - Support for tuple enums 167 | 168 | ## [0.1.0] - 2022-12-15 169 | 170 | Initial release 171 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | 4 | members = [ 5 | "examples/anchor/ix_blank/*", 6 | "examples/anchor/ix_no_accounts/*", 7 | "examples/anchor/ix_no_accounts_pubkey_arg/*", 8 | "examples/anchor/ix_no_args/*", 9 | "examples/anchor/ix_no_privilege/*", 10 | "examples/anchor/drift/*", 11 | "examples/anchor/marinade/*", 12 | "examples/anchor/unstake_it/*", 13 | "examples/bincode/stake/*", 14 | "examples/bincode/system/*", 15 | "examples/shank/ix_blank/*", 16 | "examples/shank/ix_no_accounts/*", 17 | "examples/shank/ix_no_accounts_pubkey_arg/*", 18 | "examples/shank/ix_no_args/*", 19 | "examples/shank/ix_no_privilege/*", 20 | "examples/shank/phoenix_v1/*", 21 | "examples/shank/token_metadata/*", 22 | "solores", 23 | "test_utils" 24 | ] 25 | 26 | [workspace.dependencies] 27 | borsh = "^1.5" 28 | bs58 = ">=0.5" 29 | bytemuck = "^1.16" 30 | clap = "4.5.14" 31 | env_logger = "0.11.5" 32 | heck = "0.5.0" 33 | itertools = "^0.13" 34 | lazy_static = "^1.5" 35 | log = "0.4.22" 36 | log-panics = "^2.1" 37 | num-derive = "0.4.2" 38 | num-traits = "^0.2" 39 | prettyplease = "0.2.20" 40 | proc-macro2 = "^1.0" 41 | quote = "^1.0" 42 | rand = "^0.8" 43 | serde = "^1" 44 | serde_json = "^1" 45 | sha2 = "^0.10" 46 | solana-cli-config = "^2.0" 47 | solana-client = "^2.0" 48 | solana-program = "^2.0" 49 | solana-sdk = "^2.0" 50 | syn = "^2.0" 51 | thiserror = "^1.0" 52 | tokio = "^1" 53 | toml = "0.8.19" 54 | void = "^1.0" 55 | 56 | # workspace members 57 | drift_interface = { path = "./examples/anchor/drift/drift_interface" } 58 | marinade_finance_interface = { path = "./examples/anchor/marinade/marinade_finance_interface" } 59 | mpl_token_metadata_interface = { path = "./examples/shank/token_metadata/mpl_token_metadata_interface" } 60 | stake_program_interface = { path = "./examples/bincode/stake/stake_program_interface" } 61 | system_program_interface = { path = "./examples/bincode/system/system_program_interface" } 62 | test_utils = { path = "./test_utils" } 63 | unstake_interface = { path = "./examples/anchor/unstake_it/unstake_interface" } 64 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | Example output crates generated by `cargo test`. See `tests/test_gen_examples.rs` 4 | 5 | ## Adding A New Example 6 | 7 | To save disk space, we've decided to make all examples use workspace versions of dependencies. Because of this, the procedure to add a new example is: 8 | 9 | 1. Run `gen_example()` 10 | 2. Only add the new interface to workspace after `gen_example()` has completed 11 | 3. Now you can add `check_example()` for subsequent runs 12 | 13 | ## Testing 14 | 15 | Testing the examples consists of 2 stages: generating + checking the examples and then testing the consumer crates. 16 | 17 | ### Generating the example 18 | 19 | In workspace root: 20 | 21 | ```sh 22 | cd solores && cargo test --features test_gen_examples 23 | ``` 24 | 25 | ### Testing the consumer crates 26 | 27 | In workspace root: 28 | 29 | ```sh 30 | cargo test 31 | ``` 32 | -------------------------------------------------------------------------------- /examples/anchor/drift/README.md: -------------------------------------------------------------------------------- 1 | # drift v2 program 2 | 3 | IDL downloaded from https://github.com/drift-labs/protocol-v2/blob/master/sdk/src/idl/drift.json 4 | 5 | ## Notes 6 | 7 | - idl doesnt contain programId, so output crate will be set to default `TH1S1SNoTAVAL1DPUBKEYDoNoTUSE11111111111111` 8 | - only works with `solana-program = ^1.16` since it requires `borsh = ^0.10` which introduces the use of const generics in arrays -------------------------------------------------------------------------------- /examples/anchor/drift/drift_client_consumer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "drift_client_consumer" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | solana-program = { workspace = true } 9 | drift_interface = { workspace = true } 10 | -------------------------------------------------------------------------------- /examples/anchor/drift/drift_client_consumer/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! All code in tests 2 | -------------------------------------------------------------------------------- /examples/anchor/drift/drift_client_consumer/tests/test_program_id.rs: -------------------------------------------------------------------------------- 1 | mod expected_drift_program_id { 2 | solana_program::declare_id!("dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH"); 3 | } 4 | 5 | /// Check that custom program ID was successfully passed to 6 | /// `--program-id` 7 | pub fn test_check_program_id() { 8 | assert_eq!(expected_drift_program_id::ID, drift_interface::ID); 9 | } 10 | -------------------------------------------------------------------------------- /examples/anchor/drift/drift_interface/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock -------------------------------------------------------------------------------- /examples/anchor/drift/drift_interface/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "drift_interface" 3 | version = "2.31.1-beta.9" 4 | edition = "2021" 5 | 6 | [dependencies.borsh] 7 | workspace = true 8 | 9 | [dependencies.num-derive] 10 | workspace = true 11 | 12 | [dependencies.num-traits] 13 | workspace = true 14 | 15 | [dependencies.serde] 16 | optional = true 17 | workspace = true 18 | 19 | [dependencies.solana-program] 20 | workspace = true 21 | 22 | [dependencies.thiserror] 23 | workspace = true 24 | -------------------------------------------------------------------------------- /examples/anchor/drift/drift_interface/src/lib.rs: -------------------------------------------------------------------------------- 1 | solana_program::declare_id!("dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH"); 2 | pub mod accounts; 3 | pub use accounts::*; 4 | pub mod typedefs; 5 | pub use typedefs::*; 6 | pub mod instructions; 7 | pub use instructions::*; 8 | pub mod errors; 9 | pub use errors::*; 10 | pub mod events; 11 | pub use events::*; 12 | -------------------------------------------------------------------------------- /examples/anchor/ix_blank/anchor_ix_blank_interface/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock -------------------------------------------------------------------------------- /examples/anchor/ix_blank/anchor_ix_blank_interface/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "anchor_ix_blank_interface" 3 | version = "0.0.0" 4 | edition = "2021" 5 | 6 | [dependencies.borsh] 7 | workspace = true 8 | 9 | [dependencies.serde] 10 | optional = true 11 | workspace = true 12 | 13 | [dependencies.solana-program] 14 | workspace = true 15 | -------------------------------------------------------------------------------- /examples/anchor/ix_blank/anchor_ix_blank_interface/src/instructions.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | entrypoint::ProgramResult, 3 | instruction::Instruction, 4 | program::{invoke, invoke_signed}, 5 | pubkey::Pubkey, 6 | }; 7 | use std::io::Read; 8 | #[derive(Clone, Debug, PartialEq)] 9 | pub enum AnchorIxBlankProgramIx { 10 | BlankIx, 11 | } 12 | impl AnchorIxBlankProgramIx { 13 | pub fn deserialize(buf: &[u8]) -> std::io::Result { 14 | let mut reader = buf; 15 | let mut maybe_discm = [0u8; 8]; 16 | reader.read_exact(&mut maybe_discm)?; 17 | match maybe_discm { 18 | BLANK_IX_IX_DISCM => Ok(Self::BlankIx), 19 | _ => Err(std::io::Error::new( 20 | std::io::ErrorKind::Other, 21 | format!("discm {:?} not found", maybe_discm), 22 | )), 23 | } 24 | } 25 | pub fn serialize(&self, mut writer: W) -> std::io::Result<()> { 26 | match self { 27 | Self::BlankIx => writer.write_all(&BLANK_IX_IX_DISCM), 28 | } 29 | } 30 | pub fn try_to_vec(&self) -> std::io::Result> { 31 | let mut data = Vec::new(); 32 | self.serialize(&mut data)?; 33 | Ok(data) 34 | } 35 | } 36 | pub const BLANK_IX_IX_DISCM: [u8; 8] = [29, 47, 197, 250, 126, 165, 198, 197]; 37 | #[derive(Clone, Debug, PartialEq)] 38 | pub struct BlankIxIxData; 39 | impl BlankIxIxData { 40 | pub fn deserialize(buf: &[u8]) -> std::io::Result { 41 | let mut reader = buf; 42 | let mut maybe_discm = [0u8; 8]; 43 | reader.read_exact(&mut maybe_discm)?; 44 | if maybe_discm != BLANK_IX_IX_DISCM { 45 | return Err(std::io::Error::new( 46 | std::io::ErrorKind::Other, 47 | format!( 48 | "discm does not match. Expected: {:?}. Received: {:?}", 49 | BLANK_IX_IX_DISCM, maybe_discm 50 | ), 51 | )); 52 | } 53 | Ok(Self) 54 | } 55 | pub fn serialize(&self, mut writer: W) -> std::io::Result<()> { 56 | writer.write_all(&BLANK_IX_IX_DISCM) 57 | } 58 | pub fn try_to_vec(&self) -> std::io::Result> { 59 | let mut data = Vec::new(); 60 | self.serialize(&mut data)?; 61 | Ok(data) 62 | } 63 | } 64 | pub fn blank_ix_ix_with_program_id(program_id: Pubkey) -> std::io::Result { 65 | Ok(Instruction { 66 | program_id, 67 | accounts: Vec::new(), 68 | data: BlankIxIxData.try_to_vec()?, 69 | }) 70 | } 71 | pub fn blank_ix_ix() -> std::io::Result { 72 | blank_ix_ix_with_program_id(crate::ID) 73 | } 74 | pub fn blank_ix_invoke_with_program_id(program_id: Pubkey) -> ProgramResult { 75 | let ix = blank_ix_ix_with_program_id(program_id)?; 76 | invoke(&ix, &[]) 77 | } 78 | pub fn blank_ix_invoke() -> ProgramResult { 79 | blank_ix_invoke_with_program_id(crate::ID) 80 | } 81 | pub fn blank_ix_invoke_signed_with_program_id( 82 | program_id: Pubkey, 83 | seeds: &[&[&[u8]]], 84 | ) -> ProgramResult { 85 | let ix = blank_ix_ix_with_program_id(program_id)?; 86 | invoke_signed(&ix, &[], seeds) 87 | } 88 | pub fn blank_ix_invoke_signed(seeds: &[&[&[u8]]]) -> ProgramResult { 89 | blank_ix_invoke_signed_with_program_id(crate::ID, seeds) 90 | } 91 | -------------------------------------------------------------------------------- /examples/anchor/ix_blank/anchor_ix_blank_interface/src/lib.rs: -------------------------------------------------------------------------------- 1 | solana_program::declare_id!("TH1S1SNoTAVAL1DPUBKEYDoNoTUSE11111111111111"); 2 | pub mod instructions; 3 | pub use instructions::*; 4 | -------------------------------------------------------------------------------- /examples/anchor/ix_blank/idl.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0", 3 | "name": "anchor_ix_blank", 4 | "instructions": [ 5 | { 6 | "name": "blankIx" 7 | } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /examples/anchor/ix_no_accounts/anchor_ix_no_accounts_interface/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock -------------------------------------------------------------------------------- /examples/anchor/ix_no_accounts/anchor_ix_no_accounts_interface/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "anchor_ix_no_accounts_interface" 3 | version = "0.0.0" 4 | edition = "2021" 5 | 6 | [dependencies.borsh] 7 | workspace = true 8 | 9 | [dependencies.serde] 10 | optional = true 11 | workspace = true 12 | 13 | [dependencies.solana-program] 14 | workspace = true 15 | -------------------------------------------------------------------------------- /examples/anchor/ix_no_accounts/anchor_ix_no_accounts_interface/src/instructions.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use solana_program::{ 3 | entrypoint::ProgramResult, 4 | instruction::Instruction, 5 | program::{invoke, invoke_signed}, 6 | pubkey::Pubkey, 7 | }; 8 | use std::io::Read; 9 | #[derive(Clone, Debug, PartialEq)] 10 | pub enum AnchorIxNoAccountsProgramIx { 11 | NoAccountsIx(NoAccountsIxIxArgs), 12 | } 13 | impl AnchorIxNoAccountsProgramIx { 14 | pub fn deserialize(buf: &[u8]) -> std::io::Result { 15 | let mut reader = buf; 16 | let mut maybe_discm = [0u8; 8]; 17 | reader.read_exact(&mut maybe_discm)?; 18 | match maybe_discm { 19 | NO_ACCOUNTS_IX_IX_DISCM => Ok(Self::NoAccountsIx(NoAccountsIxIxArgs::deserialize( 20 | &mut reader, 21 | )?)), 22 | _ => Err(std::io::Error::new( 23 | std::io::ErrorKind::Other, 24 | format!("discm {:?} not found", maybe_discm), 25 | )), 26 | } 27 | } 28 | pub fn serialize(&self, mut writer: W) -> std::io::Result<()> { 29 | match self { 30 | Self::NoAccountsIx(args) => { 31 | writer.write_all(&NO_ACCOUNTS_IX_IX_DISCM)?; 32 | args.serialize(&mut writer) 33 | } 34 | } 35 | } 36 | pub fn try_to_vec(&self) -> std::io::Result> { 37 | let mut data = Vec::new(); 38 | self.serialize(&mut data)?; 39 | Ok(data) 40 | } 41 | } 42 | pub const NO_ACCOUNTS_IX_IX_DISCM: [u8; 8] = [195, 226, 242, 196, 225, 147, 32, 41]; 43 | #[derive(BorshDeserialize, BorshSerialize, Clone, Debug, PartialEq)] 44 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 45 | pub struct NoAccountsIxIxArgs { 46 | pub arg: u8, 47 | } 48 | #[derive(Clone, Debug, PartialEq)] 49 | pub struct NoAccountsIxIxData(pub NoAccountsIxIxArgs); 50 | impl From for NoAccountsIxIxData { 51 | fn from(args: NoAccountsIxIxArgs) -> Self { 52 | Self(args) 53 | } 54 | } 55 | impl NoAccountsIxIxData { 56 | pub fn deserialize(buf: &[u8]) -> std::io::Result { 57 | let mut reader = buf; 58 | let mut maybe_discm = [0u8; 8]; 59 | reader.read_exact(&mut maybe_discm)?; 60 | if maybe_discm != NO_ACCOUNTS_IX_IX_DISCM { 61 | return Err(std::io::Error::new( 62 | std::io::ErrorKind::Other, 63 | format!( 64 | "discm does not match. Expected: {:?}. Received: {:?}", 65 | NO_ACCOUNTS_IX_IX_DISCM, maybe_discm 66 | ), 67 | )); 68 | } 69 | Ok(Self(NoAccountsIxIxArgs::deserialize(&mut reader)?)) 70 | } 71 | pub fn serialize(&self, mut writer: W) -> std::io::Result<()> { 72 | writer.write_all(&NO_ACCOUNTS_IX_IX_DISCM)?; 73 | self.0.serialize(&mut writer) 74 | } 75 | pub fn try_to_vec(&self) -> std::io::Result> { 76 | let mut data = Vec::new(); 77 | self.serialize(&mut data)?; 78 | Ok(data) 79 | } 80 | } 81 | pub fn no_accounts_ix_ix_with_program_id( 82 | program_id: Pubkey, 83 | args: NoAccountsIxIxArgs, 84 | ) -> std::io::Result { 85 | let data: NoAccountsIxIxData = args.into(); 86 | Ok(Instruction { 87 | program_id, 88 | accounts: Vec::new(), 89 | data: data.try_to_vec()?, 90 | }) 91 | } 92 | pub fn no_accounts_ix_ix(args: NoAccountsIxIxArgs) -> std::io::Result { 93 | no_accounts_ix_ix_with_program_id(crate::ID, args) 94 | } 95 | pub fn no_accounts_ix_invoke_with_program_id( 96 | program_id: Pubkey, 97 | args: NoAccountsIxIxArgs, 98 | ) -> ProgramResult { 99 | let ix = no_accounts_ix_ix_with_program_id(program_id, args)?; 100 | invoke(&ix, &[]) 101 | } 102 | pub fn no_accounts_ix_invoke(args: NoAccountsIxIxArgs) -> ProgramResult { 103 | no_accounts_ix_invoke_with_program_id(crate::ID, args) 104 | } 105 | pub fn no_accounts_ix_invoke_signed_with_program_id( 106 | program_id: Pubkey, 107 | args: NoAccountsIxIxArgs, 108 | seeds: &[&[&[u8]]], 109 | ) -> ProgramResult { 110 | let ix = no_accounts_ix_ix_with_program_id(program_id, args)?; 111 | invoke_signed(&ix, &[], seeds) 112 | } 113 | pub fn no_accounts_ix_invoke_signed(args: NoAccountsIxIxArgs, seeds: &[&[&[u8]]]) -> ProgramResult { 114 | no_accounts_ix_invoke_signed_with_program_id(crate::ID, args, seeds) 115 | } 116 | -------------------------------------------------------------------------------- /examples/anchor/ix_no_accounts/anchor_ix_no_accounts_interface/src/lib.rs: -------------------------------------------------------------------------------- 1 | solana_program::declare_id!("TH1S1SNoTAVAL1DPUBKEYDoNoTUSE11111111111111"); 2 | pub mod instructions; 3 | pub use instructions::*; 4 | -------------------------------------------------------------------------------- /examples/anchor/ix_no_accounts/idl.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0", 3 | "name": "anchor_ix_no_accounts", 4 | "instructions": [ 5 | { 6 | "name": "noAccountsIx", 7 | "args": [ 8 | { 9 | "name": "arg", 10 | "type": "u8" 11 | } 12 | ] 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /examples/anchor/ix_no_accounts_pubkey_arg/anchor_ix_no_accounts_pubkey_arg_interface/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock -------------------------------------------------------------------------------- /examples/anchor/ix_no_accounts_pubkey_arg/anchor_ix_no_accounts_pubkey_arg_interface/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "anchor_ix_no_accounts_pubkey_arg_interface" 3 | version = "0.0.0" 4 | edition = "2021" 5 | 6 | [dependencies.borsh] 7 | workspace = true 8 | 9 | [dependencies.serde] 10 | optional = true 11 | workspace = true 12 | 13 | [dependencies.solana-program] 14 | workspace = true 15 | -------------------------------------------------------------------------------- /examples/anchor/ix_no_accounts_pubkey_arg/anchor_ix_no_accounts_pubkey_arg_interface/src/instructions.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use solana_program::{ 3 | entrypoint::ProgramResult, 4 | instruction::Instruction, 5 | program::{invoke, invoke_signed}, 6 | pubkey::Pubkey, 7 | }; 8 | use std::io::Read; 9 | #[derive(Clone, Debug, PartialEq)] 10 | pub enum AnchorIxNoAccountsPubkeyArgProgramIx { 11 | NoAccountsPubkeyArgIx(NoAccountsPubkeyArgIxIxArgs), 12 | } 13 | impl AnchorIxNoAccountsPubkeyArgProgramIx { 14 | pub fn deserialize(buf: &[u8]) -> std::io::Result { 15 | let mut reader = buf; 16 | let mut maybe_discm = [0u8; 8]; 17 | reader.read_exact(&mut maybe_discm)?; 18 | match maybe_discm { 19 | NO_ACCOUNTS_PUBKEY_ARG_IX_IX_DISCM => Ok(Self::NoAccountsPubkeyArgIx( 20 | NoAccountsPubkeyArgIxIxArgs::deserialize(&mut reader)?, 21 | )), 22 | _ => Err(std::io::Error::new( 23 | std::io::ErrorKind::Other, 24 | format!("discm {:?} not found", maybe_discm), 25 | )), 26 | } 27 | } 28 | pub fn serialize(&self, mut writer: W) -> std::io::Result<()> { 29 | match self { 30 | Self::NoAccountsPubkeyArgIx(args) => { 31 | writer.write_all(&NO_ACCOUNTS_PUBKEY_ARG_IX_IX_DISCM)?; 32 | args.serialize(&mut writer) 33 | } 34 | } 35 | } 36 | pub fn try_to_vec(&self) -> std::io::Result> { 37 | let mut data = Vec::new(); 38 | self.serialize(&mut data)?; 39 | Ok(data) 40 | } 41 | } 42 | pub const NO_ACCOUNTS_PUBKEY_ARG_IX_IX_DISCM: [u8; 8] = [184, 145, 219, 5, 131, 41, 20, 197]; 43 | #[derive(BorshDeserialize, BorshSerialize, Clone, Debug, PartialEq)] 44 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 45 | pub struct NoAccountsPubkeyArgIxIxArgs { 46 | pub arg: Pubkey, 47 | } 48 | #[derive(Clone, Debug, PartialEq)] 49 | pub struct NoAccountsPubkeyArgIxIxData(pub NoAccountsPubkeyArgIxIxArgs); 50 | impl From for NoAccountsPubkeyArgIxIxData { 51 | fn from(args: NoAccountsPubkeyArgIxIxArgs) -> Self { 52 | Self(args) 53 | } 54 | } 55 | impl NoAccountsPubkeyArgIxIxData { 56 | pub fn deserialize(buf: &[u8]) -> std::io::Result { 57 | let mut reader = buf; 58 | let mut maybe_discm = [0u8; 8]; 59 | reader.read_exact(&mut maybe_discm)?; 60 | if maybe_discm != NO_ACCOUNTS_PUBKEY_ARG_IX_IX_DISCM { 61 | return Err(std::io::Error::new( 62 | std::io::ErrorKind::Other, 63 | format!( 64 | "discm does not match. Expected: {:?}. Received: {:?}", 65 | NO_ACCOUNTS_PUBKEY_ARG_IX_IX_DISCM, maybe_discm 66 | ), 67 | )); 68 | } 69 | Ok(Self(NoAccountsPubkeyArgIxIxArgs::deserialize(&mut reader)?)) 70 | } 71 | pub fn serialize(&self, mut writer: W) -> std::io::Result<()> { 72 | writer.write_all(&NO_ACCOUNTS_PUBKEY_ARG_IX_IX_DISCM)?; 73 | self.0.serialize(&mut writer) 74 | } 75 | pub fn try_to_vec(&self) -> std::io::Result> { 76 | let mut data = Vec::new(); 77 | self.serialize(&mut data)?; 78 | Ok(data) 79 | } 80 | } 81 | pub fn no_accounts_pubkey_arg_ix_ix_with_program_id( 82 | program_id: Pubkey, 83 | args: NoAccountsPubkeyArgIxIxArgs, 84 | ) -> std::io::Result { 85 | let data: NoAccountsPubkeyArgIxIxData = args.into(); 86 | Ok(Instruction { 87 | program_id, 88 | accounts: Vec::new(), 89 | data: data.try_to_vec()?, 90 | }) 91 | } 92 | pub fn no_accounts_pubkey_arg_ix_ix( 93 | args: NoAccountsPubkeyArgIxIxArgs, 94 | ) -> std::io::Result { 95 | no_accounts_pubkey_arg_ix_ix_with_program_id(crate::ID, args) 96 | } 97 | pub fn no_accounts_pubkey_arg_ix_invoke_with_program_id( 98 | program_id: Pubkey, 99 | args: NoAccountsPubkeyArgIxIxArgs, 100 | ) -> ProgramResult { 101 | let ix = no_accounts_pubkey_arg_ix_ix_with_program_id(program_id, args)?; 102 | invoke(&ix, &[]) 103 | } 104 | pub fn no_accounts_pubkey_arg_ix_invoke(args: NoAccountsPubkeyArgIxIxArgs) -> ProgramResult { 105 | no_accounts_pubkey_arg_ix_invoke_with_program_id(crate::ID, args) 106 | } 107 | pub fn no_accounts_pubkey_arg_ix_invoke_signed_with_program_id( 108 | program_id: Pubkey, 109 | args: NoAccountsPubkeyArgIxIxArgs, 110 | seeds: &[&[&[u8]]], 111 | ) -> ProgramResult { 112 | let ix = no_accounts_pubkey_arg_ix_ix_with_program_id(program_id, args)?; 113 | invoke_signed(&ix, &[], seeds) 114 | } 115 | pub fn no_accounts_pubkey_arg_ix_invoke_signed( 116 | args: NoAccountsPubkeyArgIxIxArgs, 117 | seeds: &[&[&[u8]]], 118 | ) -> ProgramResult { 119 | no_accounts_pubkey_arg_ix_invoke_signed_with_program_id(crate::ID, args, seeds) 120 | } 121 | -------------------------------------------------------------------------------- /examples/anchor/ix_no_accounts_pubkey_arg/anchor_ix_no_accounts_pubkey_arg_interface/src/lib.rs: -------------------------------------------------------------------------------- 1 | solana_program::declare_id!("TH1S1SNoTAVAL1DPUBKEYDoNoTUSE11111111111111"); 2 | pub mod instructions; 3 | pub use instructions::*; 4 | -------------------------------------------------------------------------------- /examples/anchor/ix_no_accounts_pubkey_arg/idl.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0", 3 | "name": "anchor_ix_no_accounts_pubkey_arg", 4 | "instructions": [ 5 | { 6 | "name": "noAccountsPubkeyArgIx", 7 | "args": [ 8 | { 9 | "name": "arg", 10 | "type": "publicKey" 11 | } 12 | ] 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /examples/anchor/ix_no_args/anchor_ix_no_args_interface/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock -------------------------------------------------------------------------------- /examples/anchor/ix_no_args/anchor_ix_no_args_interface/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "anchor_ix_no_args_interface" 3 | version = "0.0.0" 4 | edition = "2021" 5 | 6 | [dependencies.borsh] 7 | workspace = true 8 | 9 | [dependencies.serde] 10 | optional = true 11 | workspace = true 12 | 13 | [dependencies.solana-program] 14 | workspace = true 15 | -------------------------------------------------------------------------------- /examples/anchor/ix_no_args/anchor_ix_no_args_interface/src/instructions.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | account_info::AccountInfo, 3 | entrypoint::ProgramResult, 4 | instruction::{AccountMeta, Instruction}, 5 | program::{invoke, invoke_signed}, 6 | program_error::ProgramError, 7 | pubkey::Pubkey, 8 | }; 9 | use std::io::Read; 10 | #[derive(Clone, Debug, PartialEq)] 11 | pub enum AnchorIxNoArgsProgramIx { 12 | NoArgsIx, 13 | } 14 | impl AnchorIxNoArgsProgramIx { 15 | pub fn deserialize(buf: &[u8]) -> std::io::Result { 16 | let mut reader = buf; 17 | let mut maybe_discm = [0u8; 8]; 18 | reader.read_exact(&mut maybe_discm)?; 19 | match maybe_discm { 20 | NO_ARGS_IX_IX_DISCM => Ok(Self::NoArgsIx), 21 | _ => Err(std::io::Error::new( 22 | std::io::ErrorKind::Other, 23 | format!("discm {:?} not found", maybe_discm), 24 | )), 25 | } 26 | } 27 | pub fn serialize(&self, mut writer: W) -> std::io::Result<()> { 28 | match self { 29 | Self::NoArgsIx => writer.write_all(&NO_ARGS_IX_IX_DISCM), 30 | } 31 | } 32 | pub fn try_to_vec(&self) -> std::io::Result> { 33 | let mut data = Vec::new(); 34 | self.serialize(&mut data)?; 35 | Ok(data) 36 | } 37 | } 38 | fn invoke_instruction<'info, A: Into<[AccountInfo<'info>; N]>, const N: usize>( 39 | ix: &Instruction, 40 | accounts: A, 41 | ) -> ProgramResult { 42 | let account_info: [AccountInfo<'info>; N] = accounts.into(); 43 | invoke(ix, &account_info) 44 | } 45 | fn invoke_instruction_signed<'info, A: Into<[AccountInfo<'info>; N]>, const N: usize>( 46 | ix: &Instruction, 47 | accounts: A, 48 | seeds: &[&[&[u8]]], 49 | ) -> ProgramResult { 50 | let account_info: [AccountInfo<'info>; N] = accounts.into(); 51 | invoke_signed(ix, &account_info, seeds) 52 | } 53 | pub const NO_ARGS_IX_IX_ACCOUNTS_LEN: usize = 1; 54 | #[derive(Copy, Clone, Debug)] 55 | pub struct NoArgsIxAccounts<'me, 'info> { 56 | pub b: &'me AccountInfo<'info>, 57 | } 58 | #[derive(Copy, Clone, Debug, PartialEq)] 59 | pub struct NoArgsIxKeys { 60 | pub b: Pubkey, 61 | } 62 | impl From> for NoArgsIxKeys { 63 | fn from(accounts: NoArgsIxAccounts) -> Self { 64 | Self { b: *accounts.b.key } 65 | } 66 | } 67 | impl From for [AccountMeta; NO_ARGS_IX_IX_ACCOUNTS_LEN] { 68 | fn from(keys: NoArgsIxKeys) -> Self { 69 | [AccountMeta { 70 | pubkey: keys.b, 71 | is_signer: false, 72 | is_writable: true, 73 | }] 74 | } 75 | } 76 | impl From<[Pubkey; NO_ARGS_IX_IX_ACCOUNTS_LEN]> for NoArgsIxKeys { 77 | fn from(pubkeys: [Pubkey; NO_ARGS_IX_IX_ACCOUNTS_LEN]) -> Self { 78 | Self { b: pubkeys[0] } 79 | } 80 | } 81 | impl<'info> From> for [AccountInfo<'info>; NO_ARGS_IX_IX_ACCOUNTS_LEN] { 82 | fn from(accounts: NoArgsIxAccounts<'_, 'info>) -> Self { 83 | [accounts.b.clone()] 84 | } 85 | } 86 | impl<'me, 'info> From<&'me [AccountInfo<'info>; NO_ARGS_IX_IX_ACCOUNTS_LEN]> 87 | for NoArgsIxAccounts<'me, 'info> 88 | { 89 | fn from(arr: &'me [AccountInfo<'info>; NO_ARGS_IX_IX_ACCOUNTS_LEN]) -> Self { 90 | Self { b: &arr[0] } 91 | } 92 | } 93 | pub const NO_ARGS_IX_IX_DISCM: [u8; 8] = [112, 123, 142, 129, 200, 92, 216, 55]; 94 | #[derive(Clone, Debug, PartialEq)] 95 | pub struct NoArgsIxIxData; 96 | impl NoArgsIxIxData { 97 | pub fn deserialize(buf: &[u8]) -> std::io::Result { 98 | let mut reader = buf; 99 | let mut maybe_discm = [0u8; 8]; 100 | reader.read_exact(&mut maybe_discm)?; 101 | if maybe_discm != NO_ARGS_IX_IX_DISCM { 102 | return Err(std::io::Error::new( 103 | std::io::ErrorKind::Other, 104 | format!( 105 | "discm does not match. Expected: {:?}. Received: {:?}", 106 | NO_ARGS_IX_IX_DISCM, maybe_discm 107 | ), 108 | )); 109 | } 110 | Ok(Self) 111 | } 112 | pub fn serialize(&self, mut writer: W) -> std::io::Result<()> { 113 | writer.write_all(&NO_ARGS_IX_IX_DISCM) 114 | } 115 | pub fn try_to_vec(&self) -> std::io::Result> { 116 | let mut data = Vec::new(); 117 | self.serialize(&mut data)?; 118 | Ok(data) 119 | } 120 | } 121 | pub fn no_args_ix_ix_with_program_id( 122 | program_id: Pubkey, 123 | keys: NoArgsIxKeys, 124 | ) -> std::io::Result { 125 | let metas: [AccountMeta; NO_ARGS_IX_IX_ACCOUNTS_LEN] = keys.into(); 126 | Ok(Instruction { 127 | program_id, 128 | accounts: Vec::from(metas), 129 | data: NoArgsIxIxData.try_to_vec()?, 130 | }) 131 | } 132 | pub fn no_args_ix_ix(keys: NoArgsIxKeys) -> std::io::Result { 133 | no_args_ix_ix_with_program_id(crate::ID, keys) 134 | } 135 | pub fn no_args_ix_invoke_with_program_id( 136 | program_id: Pubkey, 137 | accounts: NoArgsIxAccounts<'_, '_>, 138 | ) -> ProgramResult { 139 | let keys: NoArgsIxKeys = accounts.into(); 140 | let ix = no_args_ix_ix_with_program_id(program_id, keys)?; 141 | invoke_instruction(&ix, accounts) 142 | } 143 | pub fn no_args_ix_invoke(accounts: NoArgsIxAccounts<'_, '_>) -> ProgramResult { 144 | no_args_ix_invoke_with_program_id(crate::ID, accounts) 145 | } 146 | pub fn no_args_ix_invoke_signed_with_program_id( 147 | program_id: Pubkey, 148 | accounts: NoArgsIxAccounts<'_, '_>, 149 | seeds: &[&[&[u8]]], 150 | ) -> ProgramResult { 151 | let keys: NoArgsIxKeys = accounts.into(); 152 | let ix = no_args_ix_ix_with_program_id(program_id, keys)?; 153 | invoke_instruction_signed(&ix, accounts, seeds) 154 | } 155 | pub fn no_args_ix_invoke_signed( 156 | accounts: NoArgsIxAccounts<'_, '_>, 157 | seeds: &[&[&[u8]]], 158 | ) -> ProgramResult { 159 | no_args_ix_invoke_signed_with_program_id(crate::ID, accounts, seeds) 160 | } 161 | pub fn no_args_ix_verify_account_keys( 162 | accounts: NoArgsIxAccounts<'_, '_>, 163 | keys: NoArgsIxKeys, 164 | ) -> Result<(), (Pubkey, Pubkey)> { 165 | for (actual, expected) in [(*accounts.b.key, keys.b)] { 166 | if actual != expected { 167 | return Err((actual, expected)); 168 | } 169 | } 170 | Ok(()) 171 | } 172 | pub fn no_args_ix_verify_writable_privileges<'me, 'info>( 173 | accounts: NoArgsIxAccounts<'me, 'info>, 174 | ) -> Result<(), (&'me AccountInfo<'info>, ProgramError)> { 175 | for should_be_writable in [accounts.b] { 176 | if !should_be_writable.is_writable { 177 | return Err((should_be_writable, ProgramError::InvalidAccountData)); 178 | } 179 | } 180 | Ok(()) 181 | } 182 | pub fn no_args_ix_verify_account_privileges<'me, 'info>( 183 | accounts: NoArgsIxAccounts<'me, 'info>, 184 | ) -> Result<(), (&'me AccountInfo<'info>, ProgramError)> { 185 | no_args_ix_verify_writable_privileges(accounts)?; 186 | Ok(()) 187 | } 188 | -------------------------------------------------------------------------------- /examples/anchor/ix_no_args/anchor_ix_no_args_interface/src/lib.rs: -------------------------------------------------------------------------------- 1 | solana_program::declare_id!("TH1S1SNoTAVAL1DPUBKEYDoNoTUSE11111111111111"); 2 | pub mod instructions; 3 | pub use instructions::*; 4 | -------------------------------------------------------------------------------- /examples/anchor/ix_no_args/idl.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0", 3 | "name": "anchor_ix_no_args", 4 | "instructions": [ 5 | { 6 | "name": "noArgsIx", 7 | "accounts": [ 8 | { 9 | "name": "b", 10 | "isMut": true, 11 | "isSigner": false 12 | } 13 | ] 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /examples/anchor/ix_no_privilege/anchor_ix_no_privilege_interface/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock -------------------------------------------------------------------------------- /examples/anchor/ix_no_privilege/anchor_ix_no_privilege_interface/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "anchor_ix_no_privilege_interface" 3 | version = "0.0.0" 4 | edition = "2021" 5 | 6 | [dependencies.borsh] 7 | workspace = true 8 | 9 | [dependencies.serde] 10 | optional = true 11 | workspace = true 12 | 13 | [dependencies.solana-program] 14 | workspace = true 15 | -------------------------------------------------------------------------------- /examples/anchor/ix_no_privilege/anchor_ix_no_privilege_interface/src/lib.rs: -------------------------------------------------------------------------------- 1 | solana_program::declare_id!("TH1S1SNoTAVAL1DPUBKEYDoNoTUSE11111111111111"); 2 | pub mod instructions; 3 | pub use instructions::*; 4 | -------------------------------------------------------------------------------- /examples/anchor/ix_no_privilege/idl.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0", 3 | "name": "anchor_ix_no_privilege", 4 | "instructions": [ 5 | { 6 | "name": "noPrivilegedAccountIx", 7 | "accounts": [ 8 | { 9 | "name": "a", 10 | "isMut": false, 11 | "isSigner": false 12 | } 13 | ], 14 | "args": [ 15 | { 16 | "name": "arg", 17 | "type": "u8" 18 | } 19 | ] 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /examples/anchor/marinade/README.md: -------------------------------------------------------------------------------- 1 | # marinade liquid staking program 2 | 3 | IDL downloaded from https://github.com/marinade-finance/marinade-ts-sdk/blob/main/src/programs/idl/json/marinade_finance.json 4 | 5 | ## Notes 6 | 7 | - the original idl.json contained some typedefs that weren't in the src code: `enum CommonError` (different from the one in `errors.rs`) and `enum InitializeError`. These have been removed. 8 | - idl doesnt contain programId, so output crate will be set to default `TH1S1SNoTAVAL1DPUBKEYDoNoTUSE11111111111111` -------------------------------------------------------------------------------- /examples/anchor/marinade/marinade_finance_client_consumer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "marinade_finance_client_consumer" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | 9 | [dev-dependencies] 10 | borsh = { workspace = true } 11 | lazy_static = { workspace = true } 12 | marinade_finance_interface = { workspace = true, features = ["serde"] } 13 | serde_json = { workspace = true } 14 | solana-client = { workspace = true } 15 | solana-sdk = { workspace = true } 16 | tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } 17 | -------------------------------------------------------------------------------- /examples/anchor/marinade/marinade_finance_client_consumer/README.md: -------------------------------------------------------------------------------- 1 | # marinade_finance_client_consumer 2 | 3 | An example off-chain consumer of the generated `marinade_finance_interface` crate. 4 | -------------------------------------------------------------------------------- /examples/anchor/marinade/marinade_finance_client_consumer/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! All code in tests 2 | -------------------------------------------------------------------------------- /examples/anchor/marinade/marinade_finance_client_consumer/tests/test-read-state.rs: -------------------------------------------------------------------------------- 1 | use lazy_static::lazy_static; 2 | use marinade_finance_interface::StateAccount; 3 | use solana_client::nonblocking::rpc_client::RpcClient; 4 | 5 | lazy_static! { 6 | static ref RPC: RpcClient = RpcClient::new("https://api.mainnet-beta.solana.com".to_owned()); 7 | } 8 | 9 | mod marinade_state { 10 | solana_sdk::declare_id!("8szGkuLTAux9XMgZ2vtY39jVSowEcpBfFfD8hXSEqdGC"); 11 | } 12 | 13 | #[tokio::test] 14 | async fn test_read_serde_marinade_state() { 15 | let acc = RPC.get_account_data(&marinade_state::ID).await.unwrap(); 16 | let sa = StateAccount::deserialize(acc.as_slice()).unwrap(); 17 | serde_json::to_string(&sa.0).unwrap(); 18 | } 19 | -------------------------------------------------------------------------------- /examples/anchor/marinade/marinade_finance_client_consumer/tests/test-serde.rs: -------------------------------------------------------------------------------- 1 | use marinade_finance_interface::{ 2 | ChangeAuthorityData, ChangeAuthorityIxArgs, ChangeAuthorityIxData, DepositIxArgs, 3 | MarinadeFinanceProgramIx, CHANGE_AUTHORITY_IX_DISCM, DEPOSIT_IX_DISCM, 4 | }; 5 | use solana_sdk::pubkey::Pubkey; 6 | 7 | #[test] 8 | fn test_ix_data_borsh_roundtrip() { 9 | let sample = ChangeAuthorityIxData(ChangeAuthorityIxArgs { 10 | data: ChangeAuthorityData { 11 | admin: None, 12 | validator_manager: None, 13 | operational_sol_account: Some(Pubkey::new_unique()), 14 | treasury_msol_account: None, 15 | }, 16 | }); 17 | let serialized = sample.try_to_vec().unwrap(); 18 | assert_eq!(serialized[..8], CHANGE_AUTHORITY_IX_DISCM); 19 | let deserialized = ChangeAuthorityIxData::deserialize(&serialized).unwrap(); 20 | assert_eq!(sample, deserialized); 21 | } 22 | 23 | #[test] 24 | fn test_program_ix_borsh_roundtrip() { 25 | let program_ix = MarinadeFinanceProgramIx::Deposit(DepositIxArgs { lamports: 1000 }); 26 | let serialized = program_ix.try_to_vec().unwrap(); 27 | assert_eq!(serialized[..8], DEPOSIT_IX_DISCM); 28 | let deserialized = MarinadeFinanceProgramIx::deserialize(&serialized).unwrap(); 29 | assert_eq!(program_ix, deserialized); 30 | } 31 | -------------------------------------------------------------------------------- /examples/anchor/marinade/marinade_finance_interface/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock -------------------------------------------------------------------------------- /examples/anchor/marinade/marinade_finance_interface/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "marinade_finance_interface" 3 | version = "0.0.0" 4 | edition = "2021" 5 | 6 | [dependencies.borsh] 7 | workspace = true 8 | 9 | [dependencies.serde] 10 | optional = true 11 | workspace = true 12 | 13 | [dependencies.solana-program] 14 | workspace = true 15 | -------------------------------------------------------------------------------- /examples/anchor/marinade/marinade_finance_interface/src/accounts.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use borsh::{BorshDeserialize, BorshSerialize}; 3 | use solana_program::pubkey::Pubkey; 4 | pub const STATE_ACCOUNT_DISCM: [u8; 8] = [216, 146, 107, 94, 104, 75, 182, 177]; 5 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 6 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 7 | pub struct State { 8 | pub msol_mint: Pubkey, 9 | pub admin_authority: Pubkey, 10 | pub operational_sol_account: Pubkey, 11 | pub treasury_msol_account: Pubkey, 12 | pub reserve_bump_seed: u8, 13 | pub msol_mint_authority_bump_seed: u8, 14 | pub rent_exempt_for_token_acc: u64, 15 | pub reward_fee: Fee, 16 | pub stake_system: StakeSystem, 17 | pub validator_system: ValidatorSystem, 18 | pub liq_pool: LiqPool, 19 | pub available_reserve_balance: u64, 20 | pub msol_supply: u64, 21 | pub msol_price: u64, 22 | pub circulating_ticket_count: u64, 23 | pub circulating_ticket_balance: u64, 24 | pub lent_from_reserve: u64, 25 | pub min_deposit: u64, 26 | pub min_withdraw: u64, 27 | pub staking_sol_cap: u64, 28 | pub emergency_cooling_down: u64, 29 | } 30 | #[derive(Clone, Debug, PartialEq)] 31 | pub struct StateAccount(pub State); 32 | impl StateAccount { 33 | pub fn deserialize(buf: &[u8]) -> std::io::Result { 34 | use std::io::Read; 35 | let mut reader = buf; 36 | let mut maybe_discm = [0u8; 8]; 37 | reader.read_exact(&mut maybe_discm)?; 38 | if maybe_discm != STATE_ACCOUNT_DISCM { 39 | return Err(std::io::Error::new( 40 | std::io::ErrorKind::Other, 41 | format!( 42 | "discm does not match. Expected: {:?}. Received: {:?}", 43 | STATE_ACCOUNT_DISCM, maybe_discm 44 | ), 45 | )); 46 | } 47 | Ok(Self(State::deserialize(&mut reader)?)) 48 | } 49 | pub fn serialize(&self, mut writer: W) -> std::io::Result<()> { 50 | writer.write_all(&STATE_ACCOUNT_DISCM)?; 51 | self.0.serialize(&mut writer) 52 | } 53 | pub fn try_to_vec(&self) -> std::io::Result> { 54 | let mut data = Vec::new(); 55 | self.serialize(&mut data)?; 56 | Ok(data) 57 | } 58 | } 59 | pub const TICKET_ACCOUNT_DATA_ACCOUNT_DISCM: [u8; 8] = [133, 77, 18, 98, 211, 1, 231, 3]; 60 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 61 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 62 | pub struct TicketAccountData { 63 | pub state_address: Pubkey, 64 | pub beneficiary: Pubkey, 65 | pub lamports_amount: u64, 66 | pub created_epoch: u64, 67 | } 68 | #[derive(Clone, Debug, PartialEq)] 69 | pub struct TicketAccountDataAccount(pub TicketAccountData); 70 | impl TicketAccountDataAccount { 71 | pub fn deserialize(buf: &[u8]) -> std::io::Result { 72 | use std::io::Read; 73 | let mut reader = buf; 74 | let mut maybe_discm = [0u8; 8]; 75 | reader.read_exact(&mut maybe_discm)?; 76 | if maybe_discm != TICKET_ACCOUNT_DATA_ACCOUNT_DISCM { 77 | return Err(std::io::Error::new( 78 | std::io::ErrorKind::Other, 79 | format!( 80 | "discm does not match. Expected: {:?}. Received: {:?}", 81 | TICKET_ACCOUNT_DATA_ACCOUNT_DISCM, maybe_discm 82 | ), 83 | )); 84 | } 85 | Ok(Self(TicketAccountData::deserialize(&mut reader)?)) 86 | } 87 | pub fn serialize(&self, mut writer: W) -> std::io::Result<()> { 88 | writer.write_all(&TICKET_ACCOUNT_DATA_ACCOUNT_DISCM)?; 89 | self.0.serialize(&mut writer) 90 | } 91 | pub fn try_to_vec(&self) -> std::io::Result> { 92 | let mut data = Vec::new(); 93 | self.serialize(&mut data)?; 94 | Ok(data) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /examples/anchor/marinade/marinade_finance_interface/src/lib.rs: -------------------------------------------------------------------------------- 1 | solana_program::declare_id!("TH1S1SNoTAVAL1DPUBKEYDoNoTUSE11111111111111"); 2 | pub mod accounts; 3 | pub use accounts::*; 4 | pub mod typedefs; 5 | pub use typedefs::*; 6 | pub mod instructions; 7 | pub use instructions::*; 8 | -------------------------------------------------------------------------------- /examples/anchor/marinade/marinade_finance_interface/src/typedefs.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use solana_program::pubkey::Pubkey; 3 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 4 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 5 | pub struct Fee { 6 | pub basis_points: u32, 7 | } 8 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 9 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 10 | pub struct InitializeData { 11 | pub admin_authority: Pubkey, 12 | pub validator_manager_authority: Pubkey, 13 | pub min_stake: u64, 14 | pub reward_fee: Fee, 15 | pub liq_pool: LiqPoolInitializeData, 16 | pub additional_stake_record_space: u32, 17 | pub additional_validator_record_space: u32, 18 | pub slots_for_stake_delta: u64, 19 | } 20 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 21 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 22 | pub struct LiqPoolInitializeData { 23 | pub lp_liquidity_target: u64, 24 | pub lp_max_fee: Fee, 25 | pub lp_min_fee: Fee, 26 | pub lp_treasury_cut: Fee, 27 | } 28 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 29 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 30 | pub struct ChangeAuthorityData { 31 | pub admin: Option, 32 | pub validator_manager: Option, 33 | pub operational_sol_account: Option, 34 | pub treasury_msol_account: Option, 35 | } 36 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 37 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 38 | pub struct ConfigMarinadeParams { 39 | pub rewards_fee: Option, 40 | pub slots_for_stake_delta: Option, 41 | pub min_stake: Option, 42 | pub min_deposit: Option, 43 | pub min_withdraw: Option, 44 | pub staking_sol_cap: Option, 45 | pub liquidity_sol_cap: Option, 46 | pub auto_add_validator_enabled: Option, 47 | } 48 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 49 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 50 | pub struct LiqPool { 51 | pub lp_mint: Pubkey, 52 | pub lp_mint_authority_bump_seed: u8, 53 | pub sol_leg_bump_seed: u8, 54 | pub msol_leg_authority_bump_seed: u8, 55 | pub msol_leg: Pubkey, 56 | pub lp_liquidity_target: u64, 57 | pub lp_max_fee: Fee, 58 | pub lp_min_fee: Fee, 59 | pub treasury_cut: Fee, 60 | pub lp_supply: u64, 61 | pub lent_from_sol_leg: u64, 62 | pub liquidity_sol_cap: u64, 63 | } 64 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 65 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 66 | pub struct List { 67 | pub account: Pubkey, 68 | pub item_size: u32, 69 | pub count: u32, 70 | pub new_account: Pubkey, 71 | pub copied_count: u32, 72 | } 73 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 74 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 75 | pub struct StakeRecord { 76 | pub stake_account: Pubkey, 77 | pub last_update_delegated_lamports: u64, 78 | pub last_update_epoch: u64, 79 | pub is_emergency_unstaking: u8, 80 | } 81 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 82 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 83 | pub struct StakeSystem { 84 | pub stake_list: List, 85 | pub delayed_unstake_cooling_down: u64, 86 | pub stake_deposit_bump_seed: u8, 87 | pub stake_withdraw_bump_seed: u8, 88 | pub slots_for_stake_delta: u64, 89 | pub last_stake_delta_epoch: u64, 90 | pub min_stake: u64, 91 | pub extra_stake_delta_runs: u32, 92 | } 93 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 94 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 95 | pub struct ValidatorRecord { 96 | pub validator_account: Pubkey, 97 | pub active_balance: u64, 98 | pub score: u32, 99 | pub last_stake_delta_epoch: u64, 100 | pub duplication_flag_bump_seed: u8, 101 | } 102 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 103 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 104 | pub struct ValidatorSystem { 105 | pub validator_list: List, 106 | pub manager_authority: Pubkey, 107 | pub total_validator_score: u32, 108 | pub total_active_balance: u64, 109 | pub auto_add_validator_enabled: u8, 110 | } 111 | -------------------------------------------------------------------------------- /examples/anchor/unstake_it/unstake_client_consumer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "unstake_client_consumer" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | solana-cli-config = { workspace = true } 9 | solana-sdk = { workspace = true } 10 | unstake_interface = { workspace = true } 11 | -------------------------------------------------------------------------------- /examples/anchor/unstake_it/unstake_client_consumer/src/main.rs: -------------------------------------------------------------------------------- 1 | use solana_cli_config::{Config, CONFIG_FILE}; 2 | use solana_sdk::{ 3 | pubkey::Pubkey, signature::read_keypair_file, signer::Signer, system_program, sysvar, 4 | }; 5 | use unstake_interface::*; 6 | 7 | fn main() { 8 | let config_file = CONFIG_FILE.as_ref().unwrap(); 9 | let cli_config = Config::load(config_file).unwrap(); 10 | let kp = read_keypair_file(cli_config.keypair_path).unwrap(); 11 | 12 | let accounts = SetFeeKeys { 13 | fee_authority: kp.pubkey(), 14 | pool_account: Pubkey::new_unique(), 15 | fee_account: Pubkey::new_unique(), 16 | system_program: system_program::ID, 17 | rent: sysvar::rent::ID, 18 | }; 19 | let args = SetFeeIxArgs { 20 | fee: Fee { 21 | fee: FeeEnum::Flat { 22 | ratio: Rational { 23 | num: 1u64, 24 | denom: 10_000u64, 25 | }, 26 | }, 27 | }, 28 | }; 29 | let remove_creator_ver_ix = set_fee_ix(accounts, args).unwrap(); 30 | println!("{:?}", remove_creator_ver_ix); 31 | } 32 | -------------------------------------------------------------------------------- /examples/anchor/unstake_it/unstake_interface/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock -------------------------------------------------------------------------------- /examples/anchor/unstake_it/unstake_interface/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "unstake_interface" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies.borsh] 7 | workspace = true 8 | 9 | [dependencies.num-derive] 10 | workspace = true 11 | 12 | [dependencies.num-traits] 13 | workspace = true 14 | 15 | [dependencies.serde] 16 | optional = true 17 | workspace = true 18 | 19 | [dependencies.solana-program] 20 | workspace = true 21 | 22 | [dependencies.thiserror] 23 | workspace = true 24 | -------------------------------------------------------------------------------- /examples/anchor/unstake_it/unstake_interface/src/accounts.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use borsh::{BorshDeserialize, BorshSerialize}; 3 | use solana_program::pubkey::Pubkey; 4 | pub const FEE_ACCOUNT_DISCM: [u8; 8] = [24, 55, 150, 250, 168, 27, 101, 178]; 5 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 6 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 7 | pub struct Fee { 8 | pub fee: FeeEnum, 9 | } 10 | #[derive(Clone, Debug, PartialEq)] 11 | pub struct FeeAccount(pub Fee); 12 | impl FeeAccount { 13 | pub fn deserialize(buf: &[u8]) -> std::io::Result { 14 | use std::io::Read; 15 | let mut reader = buf; 16 | let mut maybe_discm = [0u8; 8]; 17 | reader.read_exact(&mut maybe_discm)?; 18 | if maybe_discm != FEE_ACCOUNT_DISCM { 19 | return Err(std::io::Error::new( 20 | std::io::ErrorKind::Other, 21 | format!( 22 | "discm does not match. Expected: {:?}. Received: {:?}", 23 | FEE_ACCOUNT_DISCM, maybe_discm 24 | ), 25 | )); 26 | } 27 | Ok(Self(Fee::deserialize(&mut reader)?)) 28 | } 29 | pub fn serialize(&self, mut writer: W) -> std::io::Result<()> { 30 | writer.write_all(&FEE_ACCOUNT_DISCM)?; 31 | self.0.serialize(&mut writer) 32 | } 33 | pub fn try_to_vec(&self) -> std::io::Result> { 34 | let mut data = Vec::new(); 35 | self.serialize(&mut data)?; 36 | Ok(data) 37 | } 38 | } 39 | pub const POOL_ACCOUNT_DISCM: [u8; 8] = [241, 154, 109, 4, 17, 177, 109, 188]; 40 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 41 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 42 | pub struct Pool { 43 | pub fee_authority: Pubkey, 44 | pub lp_mint: Pubkey, 45 | pub incoming_stake: u64, 46 | } 47 | #[derive(Clone, Debug, PartialEq)] 48 | pub struct PoolAccount(pub Pool); 49 | impl PoolAccount { 50 | pub fn deserialize(buf: &[u8]) -> std::io::Result { 51 | use std::io::Read; 52 | let mut reader = buf; 53 | let mut maybe_discm = [0u8; 8]; 54 | reader.read_exact(&mut maybe_discm)?; 55 | if maybe_discm != POOL_ACCOUNT_DISCM { 56 | return Err(std::io::Error::new( 57 | std::io::ErrorKind::Other, 58 | format!( 59 | "discm does not match. Expected: {:?}. Received: {:?}", 60 | POOL_ACCOUNT_DISCM, maybe_discm 61 | ), 62 | )); 63 | } 64 | Ok(Self(Pool::deserialize(&mut reader)?)) 65 | } 66 | pub fn serialize(&self, mut writer: W) -> std::io::Result<()> { 67 | writer.write_all(&POOL_ACCOUNT_DISCM)?; 68 | self.0.serialize(&mut writer) 69 | } 70 | pub fn try_to_vec(&self) -> std::io::Result> { 71 | let mut data = Vec::new(); 72 | self.serialize(&mut data)?; 73 | Ok(data) 74 | } 75 | } 76 | pub const PROTOCOL_FEE_ACCOUNT_DISCM: [u8; 8] = [121, 127, 98, 139, 72, 110, 44, 118]; 77 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 78 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 79 | pub struct ProtocolFee { 80 | pub destination: Pubkey, 81 | pub authority: Pubkey, 82 | pub fee_ratio: Rational, 83 | pub referrer_fee_ratio: Rational, 84 | } 85 | #[derive(Clone, Debug, PartialEq)] 86 | pub struct ProtocolFeeAccount(pub ProtocolFee); 87 | impl ProtocolFeeAccount { 88 | pub fn deserialize(buf: &[u8]) -> std::io::Result { 89 | use std::io::Read; 90 | let mut reader = buf; 91 | let mut maybe_discm = [0u8; 8]; 92 | reader.read_exact(&mut maybe_discm)?; 93 | if maybe_discm != PROTOCOL_FEE_ACCOUNT_DISCM { 94 | return Err(std::io::Error::new( 95 | std::io::ErrorKind::Other, 96 | format!( 97 | "discm does not match. Expected: {:?}. Received: {:?}", 98 | PROTOCOL_FEE_ACCOUNT_DISCM, maybe_discm 99 | ), 100 | )); 101 | } 102 | Ok(Self(ProtocolFee::deserialize(&mut reader)?)) 103 | } 104 | pub fn serialize(&self, mut writer: W) -> std::io::Result<()> { 105 | writer.write_all(&PROTOCOL_FEE_ACCOUNT_DISCM)?; 106 | self.0.serialize(&mut writer) 107 | } 108 | pub fn try_to_vec(&self) -> std::io::Result> { 109 | let mut data = Vec::new(); 110 | self.serialize(&mut data)?; 111 | Ok(data) 112 | } 113 | } 114 | pub const STAKE_ACCOUNT_RECORD_ACCOUNT_DISCM: [u8; 8] = [144, 205, 183, 241, 3, 250, 208, 215]; 115 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 116 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 117 | pub struct StakeAccountRecord { 118 | pub lamports_at_creation: u64, 119 | } 120 | #[derive(Clone, Debug, PartialEq)] 121 | pub struct StakeAccountRecordAccount(pub StakeAccountRecord); 122 | impl StakeAccountRecordAccount { 123 | pub fn deserialize(buf: &[u8]) -> std::io::Result { 124 | use std::io::Read; 125 | let mut reader = buf; 126 | let mut maybe_discm = [0u8; 8]; 127 | reader.read_exact(&mut maybe_discm)?; 128 | if maybe_discm != STAKE_ACCOUNT_RECORD_ACCOUNT_DISCM { 129 | return Err(std::io::Error::new( 130 | std::io::ErrorKind::Other, 131 | format!( 132 | "discm does not match. Expected: {:?}. Received: {:?}", 133 | STAKE_ACCOUNT_RECORD_ACCOUNT_DISCM, maybe_discm 134 | ), 135 | )); 136 | } 137 | Ok(Self(StakeAccountRecord::deserialize(&mut reader)?)) 138 | } 139 | pub fn serialize(&self, mut writer: W) -> std::io::Result<()> { 140 | writer.write_all(&STAKE_ACCOUNT_RECORD_ACCOUNT_DISCM)?; 141 | self.0.serialize(&mut writer) 142 | } 143 | pub fn try_to_vec(&self) -> std::io::Result> { 144 | let mut data = Vec::new(); 145 | self.serialize(&mut data)?; 146 | Ok(data) 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /examples/anchor/unstake_it/unstake_interface/src/errors.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | decode_error::DecodeError, 3 | msg, 4 | program_error::{PrintProgramError, ProgramError}, 5 | }; 6 | use thiserror::Error; 7 | #[derive(Clone, Copy, Debug, Eq, Error, num_derive::FromPrimitive, PartialEq)] 8 | pub enum UnstakeError { 9 | #[error("The provided LP token account is invalid")] 10 | InvalidLpTokenAccount = 6000, 11 | #[error("Could not find PDA bump")] 12 | PdaBumpNotCached = 6001, 13 | #[error( 14 | "The provided fee authority does not have the authority over the provided pool account" 15 | )] 16 | InvalidFeeAuthority = 6002, 17 | #[error( 18 | "The Authorized of the given stake account is None (possibly an uninitialized stake account was given)" 19 | )] 20 | StakeAccountAuthorizedNotRetrievable = 6003, 21 | #[error( 22 | "The Lockup of the given stake account is None (possibly an uninitialized stake account was given)" 23 | )] 24 | StakeAccountLockupNotRetrievable = 6004, 25 | #[error("The provided stake account is locked up")] 26 | StakeAccountLockupInForce = 6005, 27 | #[error("The provided description of fee violates the invariants")] 28 | InvalidFee = 6006, 29 | #[error("Internal Error")] 30 | InternalError = 6007, 31 | #[error("Not enough liquidity to service this unstake")] 32 | NotEnoughLiquidity = 6008, 33 | #[error("Liquidity to add too little")] 34 | LiquidityToAddTooLittle = 6009, 35 | #[error("Destination token account is not a wrapped SOL account")] 36 | DestinationNotWSol = 6010, 37 | #[error("Wrong protocol fee destination account")] 38 | WrongProtocolFeeDestination = 6011, 39 | #[error( 40 | "The provided protocol fee authority does not have the authority over the protocol fee account" 41 | )] 42 | InvalidProtocolFeeAuthority = 6012, 43 | } 44 | impl From for ProgramError { 45 | fn from(e: UnstakeError) -> Self { 46 | ProgramError::Custom(e as u32) 47 | } 48 | } 49 | impl DecodeError for UnstakeError { 50 | fn type_of() -> &'static str { 51 | "UnstakeError" 52 | } 53 | } 54 | impl PrintProgramError for UnstakeError { 55 | fn print(&self) 56 | where 57 | E: 'static 58 | + std::error::Error 59 | + DecodeError 60 | + PrintProgramError 61 | + num_traits::FromPrimitive, 62 | { 63 | msg!(&self.to_string()); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /examples/anchor/unstake_it/unstake_interface/src/lib.rs: -------------------------------------------------------------------------------- 1 | solana_program::declare_id!("unpXTU2Ndrc7WWNyEhQWe4udTzSibLPi25SXv2xbCHQ"); 2 | pub mod accounts; 3 | pub use accounts::*; 4 | pub mod typedefs; 5 | pub use typedefs::*; 6 | pub mod instructions; 7 | pub use instructions::*; 8 | pub mod errors; 9 | pub use errors::*; 10 | -------------------------------------------------------------------------------- /examples/anchor/unstake_it/unstake_interface/src/typedefs.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 3 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 4 | pub struct Rational { 5 | pub num: u64, 6 | pub denom: u64, 7 | } 8 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 9 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 10 | pub struct LiquidityLinearParams { 11 | pub max_liq_remaining: Rational, 12 | pub zero_liq_remaining: Rational, 13 | } 14 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 15 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 16 | pub enum FeeEnum { 17 | Flat { ratio: Rational }, 18 | LiquidityLinear { params: LiquidityLinearParams }, 19 | } 20 | -------------------------------------------------------------------------------- /examples/anchor/unstake_it/unstake_onchain_consumer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "unstake_onchain_consumer" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [features] 8 | no-entrypoint = [] 9 | 10 | [dependencies] 11 | borsh = { workspace = true } 12 | solana-program = { workspace = true } 13 | unstake_interface = { workspace = true } 14 | 15 | [lib] 16 | crate-type = ["cdylib", "lib"] 17 | 18 | [package.metadata.docs.rs] 19 | targets = ["x86_64-unknown-linux-gnu"] -------------------------------------------------------------------------------- /examples/anchor/unstake_it/unstake_onchain_consumer/src/entrypoint.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, program_error::ProgramError, 3 | pubkey::Pubkey, 4 | }; 5 | use unstake_interface::*; 6 | 7 | entrypoint!(process_instruction); 8 | fn process_instruction( 9 | _program_id: &Pubkey, 10 | accounts: &[AccountInfo], 11 | _ix_data: &[u8], 12 | ) -> ProgramResult { 13 | let cpi_slice: &[AccountInfo; UNSTAKE_IX_ACCOUNTS_LEN] = accounts 14 | .get(..UNSTAKE_IX_ACCOUNTS_LEN) 15 | .ok_or(ProgramError::NotEnoughAccountKeys)? 16 | .try_into() 17 | .unwrap(); 18 | let accounts: UnstakeAccounts = cpi_slice.into(); 19 | 20 | if let Err((acc, err)) = unstake_verify_account_privileges(accounts) { 21 | solana_program::msg!( 22 | "Writable/signer privilege escalation for {}: {}", 23 | acc.key, 24 | err 25 | ); 26 | return Err(err); 27 | } 28 | 29 | unstake_invoke(accounts) 30 | } 31 | -------------------------------------------------------------------------------- /examples/anchor/unstake_it/unstake_onchain_consumer/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(test), forbid(unsafe_code))] 2 | 3 | #[cfg(not(feature = "no-entrypoint"))] 4 | mod entrypoint; 5 | -------------------------------------------------------------------------------- /examples/bincode/stake/stake_program_client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stake_program_client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dev-dependencies] 7 | rand = { workspace = true } 8 | solana-program = { workspace = true } 9 | stake_program_interface = { workspace = true } -------------------------------------------------------------------------------- /examples/bincode/stake/stake_program_interface/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock -------------------------------------------------------------------------------- /examples/bincode/stake/stake_program_interface/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stake_program_interface" 3 | version = "1.17.13" 4 | edition = "2021" 5 | 6 | [dependencies.serde] 7 | workspace = true 8 | 9 | [dependencies.solana-program] 10 | workspace = true 11 | -------------------------------------------------------------------------------- /examples/bincode/stake/stake_program_interface/src/lib.rs: -------------------------------------------------------------------------------- 1 | solana_program::declare_id!("Stake11111111111111111111111111111111111111"); 2 | pub mod typedefs; 3 | pub use typedefs::*; 4 | pub mod instructions; 5 | pub use instructions::*; 6 | -------------------------------------------------------------------------------- /examples/bincode/stake/stake_program_interface/src/typedefs.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use solana_program::pubkey::Pubkey; 3 | #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] 4 | pub struct Authorized { 5 | pub staker: Pubkey, 6 | pub withdrawer: Pubkey, 7 | } 8 | #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] 9 | pub struct Lockup { 10 | pub unix_timestamp: i64, 11 | pub epoch: u64, 12 | pub custodian: Pubkey, 13 | } 14 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] 15 | pub enum StakeAuthorize { 16 | Staker, 17 | Withdrawer, 18 | } 19 | -------------------------------------------------------------------------------- /examples/bincode/system/system_program_client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "system_program_client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dev-dependencies] 7 | rand = { workspace = true } 8 | solana-program = { workspace = true } 9 | system_program_interface = { workspace = true } -------------------------------------------------------------------------------- /examples/bincode/system/system_program_client/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use rand::{thread_rng, Rng}; 4 | use solana_program::{pubkey::Pubkey, system_instruction, sysvar}; 5 | use system_program_interface::{ 6 | advance_nonce_account_ix, allocate_ix, allocate_with_seed_ix, assign_ix, 7 | assign_with_seed_ix, authorize_nonce_account_ix, create_account_ix, 8 | create_account_with_seed_ix, initialize_nonce_account_ix, transfer_ix, 9 | transfer_with_seed_ix, upgrade_nonce_account_ix, withdraw_nonce_account_ix, 10 | AdvanceNonceAccountKeys, AllocateIxArgs, AllocateKeys, AllocateWithSeedIxArgs, 11 | AllocateWithSeedKeys, AssignIxArgs, AssignKeys, AssignWithSeedIxArgs, AssignWithSeedKeys, 12 | AuthorizeNonceAccountIxArgs, AuthorizeNonceAccountKeys, CreateAccountIxArgs, 13 | CreateAccountKeys, CreateAccountWithSeedIxArgs, CreateAccountWithSeedKeys, 14 | InitializeNonceAccountIxArgs, InitializeNonceAccountKeys, TransferIxArgs, TransferKeys, 15 | TransferWithSeedIxArgs, TransferWithSeedKeys, UpgradeNonceAccountKeys, 16 | WithdrawNonceAccountIxArgs, WithdrawNonceAccountKeys, 17 | }; 18 | 19 | #[test] 20 | fn system_program_check_ix_serde() { 21 | let pk0 = Pubkey::new_unique(); 22 | let pk1 = Pubkey::new_unique(); 23 | let pk2 = Pubkey::new_unique(); 24 | let pk3 = Pubkey::new_unique(); 25 | 26 | let mut rng = thread_rng(); 27 | let u640: u64 = rng.gen(); 28 | let u641: u64 = rng.gen(); 29 | let seed = rng.gen::().to_string(); 30 | 31 | for (actual, expected) in [ 32 | ( 33 | &create_account_ix( 34 | CreateAccountKeys { from: pk0, to: pk1 }, 35 | CreateAccountIxArgs { 36 | lamports: u640, 37 | space: u641, 38 | owner: pk2, 39 | }, 40 | ), 41 | &system_instruction::create_account(&pk0, &pk1, u640, u641, &pk2), 42 | ), 43 | ( 44 | &assign_ix(AssignKeys { assign: pk0 }, AssignIxArgs { owner: pk1 }), 45 | &system_instruction::assign(&pk0, &pk1), 46 | ), 47 | ( 48 | &transfer_ix( 49 | TransferKeys { from: pk0, to: pk1 }, 50 | TransferIxArgs { lamports: u640 }, 51 | ), 52 | &system_instruction::transfer(&pk0, &pk1, u640), 53 | ), 54 | ( 55 | &create_account_with_seed_ix( 56 | CreateAccountWithSeedKeys { 57 | from: pk0, 58 | to: pk1, 59 | base: pk2, 60 | }, 61 | CreateAccountWithSeedIxArgs { 62 | base: pk2, 63 | seed: seed.clone(), 64 | lamports: u640, 65 | space: u641, 66 | owner: pk3, 67 | }, 68 | ), 69 | &system_instruction::create_account_with_seed( 70 | &pk0, &pk1, &pk2, &seed, u640, u641, &pk3, 71 | ), 72 | ), 73 | ( 74 | &advance_nonce_account_ix(AdvanceNonceAccountKeys { 75 | nonce: pk0, 76 | recent_blockhashes: sysvar::recent_blockhashes::ID, 77 | authority: pk1, 78 | }), 79 | &system_instruction::advance_nonce_account(&pk0, &pk1), 80 | ), 81 | ( 82 | &withdraw_nonce_account_ix( 83 | WithdrawNonceAccountKeys { 84 | nonce: pk0, 85 | to: pk2, 86 | recent_blockhashes: sysvar::recent_blockhashes::ID, 87 | rent: sysvar::rent::ID, 88 | authority: pk1, 89 | }, 90 | WithdrawNonceAccountIxArgs { lamports: u640 }, 91 | ), 92 | &system_instruction::withdraw_nonce_account(&pk0, &pk1, &pk2, u640), 93 | ), 94 | ( 95 | &initialize_nonce_account_ix( 96 | InitializeNonceAccountKeys { 97 | nonce: pk0, 98 | recent_blockhashes: sysvar::recent_blockhashes::ID, 99 | rent: sysvar::rent::ID, 100 | }, 101 | InitializeNonceAccountIxArgs { authority: pk1 }, 102 | ), 103 | &system_instruction::create_nonce_account(&pk2, &pk0, &pk1, u640)[1], 104 | ), 105 | ( 106 | &authorize_nonce_account_ix( 107 | AuthorizeNonceAccountKeys { 108 | nonce: pk0, 109 | authority: pk1, 110 | }, 111 | AuthorizeNonceAccountIxArgs { new_authority: pk2 }, 112 | ), 113 | &system_instruction::authorize_nonce_account(&pk0, &pk1, &pk2), 114 | ), 115 | ( 116 | &allocate_ix( 117 | AllocateKeys { allocate: pk0 }, 118 | AllocateIxArgs { space: u640 }, 119 | ), 120 | &system_instruction::allocate(&pk0, u640), 121 | ), 122 | ( 123 | &allocate_with_seed_ix( 124 | AllocateWithSeedKeys { 125 | allocate: pk0, 126 | base: pk1, 127 | }, 128 | AllocateWithSeedIxArgs { 129 | base: pk1, 130 | seed: seed.clone(), 131 | space: u640, 132 | owner: pk2, 133 | }, 134 | ), 135 | &system_instruction::allocate_with_seed(&pk0, &pk1, &seed, u640, &pk2), 136 | ), 137 | ( 138 | &assign_with_seed_ix( 139 | AssignWithSeedKeys { 140 | assign: pk0, 141 | base: pk1, 142 | }, 143 | AssignWithSeedIxArgs { 144 | base: pk1, 145 | seed: seed.clone(), 146 | owner: pk2, 147 | }, 148 | ), 149 | &system_instruction::assign_with_seed(&pk0, &pk1, &seed, &pk2), 150 | ), 151 | ( 152 | &transfer_with_seed_ix( 153 | TransferWithSeedKeys { 154 | from: pk0, 155 | base: pk1, 156 | to: pk2, 157 | }, 158 | TransferWithSeedIxArgs { 159 | lamports: u640, 160 | from_seed: seed.clone(), 161 | from_owner: pk3, 162 | }, 163 | ), 164 | &system_instruction::transfer_with_seed(&pk0, &pk1, seed.clone(), &pk3, &pk2, u640), 165 | ), 166 | ( 167 | &upgrade_nonce_account_ix(UpgradeNonceAccountKeys { nonce: pk0 }), 168 | &system_instruction::upgrade_nonce_account(pk0), 169 | ), 170 | ] { 171 | assert_eq!(actual, expected); 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /examples/bincode/system/system_program_interface/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock -------------------------------------------------------------------------------- /examples/bincode/system/system_program_interface/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "system_program_interface" 3 | version = "1.17.13" 4 | edition = "2021" 5 | 6 | [dependencies.serde] 7 | workspace = true 8 | 9 | [dependencies.solana-program] 10 | workspace = true 11 | -------------------------------------------------------------------------------- /examples/bincode/system/system_program_interface/src/lib.rs: -------------------------------------------------------------------------------- 1 | solana_program::declare_id!("11111111111111111111111111111111"); 2 | pub mod instructions; 3 | pub use instructions::*; 4 | -------------------------------------------------------------------------------- /examples/shank/ix_blank/idl.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0", 3 | "name": "shank_ix_blank", 4 | "instructions": [ 5 | { 6 | "name": "blankIx", 7 | "discriminant": { 8 | "type": "u8", 9 | "value": 69 10 | } 11 | } 12 | ], 13 | "metadata": { 14 | "origin": "shank", 15 | "address": "TH1S1SNoTAVAL1DPUBKEYDoNoTUSE11111111111111" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/shank/ix_blank/shank_ix_blank_interface/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock -------------------------------------------------------------------------------- /examples/shank/ix_blank/shank_ix_blank_interface/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shank_ix_blank_interface" 3 | version = "0.0.0" 4 | edition = "2021" 5 | 6 | [dependencies.borsh] 7 | workspace = true 8 | 9 | [dependencies.serde] 10 | optional = true 11 | workspace = true 12 | 13 | [dependencies.solana-program] 14 | workspace = true 15 | -------------------------------------------------------------------------------- /examples/shank/ix_blank/shank_ix_blank_interface/src/instructions.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | entrypoint::ProgramResult, 3 | instruction::Instruction, 4 | program::{invoke, invoke_signed}, 5 | pubkey::Pubkey, 6 | }; 7 | use std::io::Read; 8 | #[derive(Clone, Debug, PartialEq)] 9 | pub enum ShankIxBlankProgramIx { 10 | BlankIx, 11 | } 12 | impl ShankIxBlankProgramIx { 13 | pub fn deserialize(buf: &[u8]) -> std::io::Result { 14 | let mut reader = buf; 15 | let mut maybe_discm_buf = [0u8; 1]; 16 | reader.read_exact(&mut maybe_discm_buf)?; 17 | let maybe_discm = maybe_discm_buf[0]; 18 | match maybe_discm { 19 | BLANK_IX_IX_DISCM => Ok(Self::BlankIx), 20 | _ => Err(std::io::Error::new( 21 | std::io::ErrorKind::Other, 22 | format!("discm {:?} not found", maybe_discm), 23 | )), 24 | } 25 | } 26 | pub fn serialize(&self, mut writer: W) -> std::io::Result<()> { 27 | match self { 28 | Self::BlankIx => writer.write_all(&[BLANK_IX_IX_DISCM]), 29 | } 30 | } 31 | pub fn try_to_vec(&self) -> std::io::Result> { 32 | let mut data = Vec::new(); 33 | self.serialize(&mut data)?; 34 | Ok(data) 35 | } 36 | } 37 | pub const BLANK_IX_IX_DISCM: u8 = 69u8; 38 | #[derive(Clone, Debug, PartialEq)] 39 | pub struct BlankIxIxData; 40 | impl BlankIxIxData { 41 | pub fn deserialize(buf: &[u8]) -> std::io::Result { 42 | let mut reader = buf; 43 | let mut maybe_discm_buf = [0u8; 1]; 44 | reader.read_exact(&mut maybe_discm_buf)?; 45 | let maybe_discm = maybe_discm_buf[0]; 46 | if maybe_discm != BLANK_IX_IX_DISCM { 47 | return Err(std::io::Error::new( 48 | std::io::ErrorKind::Other, 49 | format!( 50 | "discm does not match. Expected: {:?}. Received: {:?}", 51 | BLANK_IX_IX_DISCM, maybe_discm 52 | ), 53 | )); 54 | } 55 | Ok(Self) 56 | } 57 | pub fn serialize(&self, mut writer: W) -> std::io::Result<()> { 58 | writer.write_all(&[BLANK_IX_IX_DISCM]) 59 | } 60 | pub fn try_to_vec(&self) -> std::io::Result> { 61 | let mut data = Vec::new(); 62 | self.serialize(&mut data)?; 63 | Ok(data) 64 | } 65 | } 66 | pub fn blank_ix_ix_with_program_id(program_id: Pubkey) -> std::io::Result { 67 | Ok(Instruction { 68 | program_id, 69 | accounts: Vec::new(), 70 | data: BlankIxIxData.try_to_vec()?, 71 | }) 72 | } 73 | pub fn blank_ix_ix() -> std::io::Result { 74 | blank_ix_ix_with_program_id(crate::ID) 75 | } 76 | pub fn blank_ix_invoke_with_program_id(program_id: Pubkey) -> ProgramResult { 77 | let ix = blank_ix_ix_with_program_id(program_id)?; 78 | invoke(&ix, &[]) 79 | } 80 | pub fn blank_ix_invoke() -> ProgramResult { 81 | blank_ix_invoke_with_program_id(crate::ID) 82 | } 83 | pub fn blank_ix_invoke_signed_with_program_id( 84 | program_id: Pubkey, 85 | seeds: &[&[&[u8]]], 86 | ) -> ProgramResult { 87 | let ix = blank_ix_ix_with_program_id(program_id)?; 88 | invoke_signed(&ix, &[], seeds) 89 | } 90 | pub fn blank_ix_invoke_signed(seeds: &[&[&[u8]]]) -> ProgramResult { 91 | blank_ix_invoke_signed_with_program_id(crate::ID, seeds) 92 | } 93 | -------------------------------------------------------------------------------- /examples/shank/ix_blank/shank_ix_blank_interface/src/lib.rs: -------------------------------------------------------------------------------- 1 | solana_program::declare_id!("TH1S1SNoTAVAL1DPUBKEYDoNoTUSE11111111111111"); 2 | pub mod instructions; 3 | pub use instructions::*; 4 | -------------------------------------------------------------------------------- /examples/shank/ix_no_accounts/idl.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0", 3 | "name": "shank_ix_no_accounts", 4 | "instructions": [ 5 | { 6 | "name": "noAccountsIx", 7 | "args": [ 8 | { 9 | "name": "arg", 10 | "type": "u8" 11 | } 12 | ], 13 | "discriminant": { 14 | "type": "u8", 15 | "value": 69 16 | } 17 | } 18 | ], 19 | "metadata": { 20 | "origin": "shank", 21 | "address": "TH1S1SNoTAVAL1DPUBKEYDoNoTUSE11111111111111" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/shank/ix_no_accounts/shank_ix_no_accounts_interface/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock -------------------------------------------------------------------------------- /examples/shank/ix_no_accounts/shank_ix_no_accounts_interface/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shank_ix_no_accounts_interface" 3 | version = "0.0.0" 4 | edition = "2021" 5 | 6 | [dependencies.borsh] 7 | workspace = true 8 | 9 | [dependencies.serde] 10 | optional = true 11 | workspace = true 12 | 13 | [dependencies.solana-program] 14 | workspace = true 15 | -------------------------------------------------------------------------------- /examples/shank/ix_no_accounts/shank_ix_no_accounts_interface/src/instructions.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use solana_program::{ 3 | entrypoint::ProgramResult, 4 | instruction::Instruction, 5 | program::{invoke, invoke_signed}, 6 | pubkey::Pubkey, 7 | }; 8 | use std::io::Read; 9 | #[derive(Clone, Debug, PartialEq)] 10 | pub enum ShankIxNoAccountsProgramIx { 11 | NoAccountsIx(NoAccountsIxIxArgs), 12 | } 13 | impl ShankIxNoAccountsProgramIx { 14 | pub fn deserialize(buf: &[u8]) -> std::io::Result { 15 | let mut reader = buf; 16 | let mut maybe_discm_buf = [0u8; 1]; 17 | reader.read_exact(&mut maybe_discm_buf)?; 18 | let maybe_discm = maybe_discm_buf[0]; 19 | match maybe_discm { 20 | NO_ACCOUNTS_IX_IX_DISCM => Ok(Self::NoAccountsIx(NoAccountsIxIxArgs::deserialize( 21 | &mut reader, 22 | )?)), 23 | _ => Err(std::io::Error::new( 24 | std::io::ErrorKind::Other, 25 | format!("discm {:?} not found", maybe_discm), 26 | )), 27 | } 28 | } 29 | pub fn serialize(&self, mut writer: W) -> std::io::Result<()> { 30 | match self { 31 | Self::NoAccountsIx(args) => { 32 | writer.write_all(&[NO_ACCOUNTS_IX_IX_DISCM])?; 33 | args.serialize(&mut writer) 34 | } 35 | } 36 | } 37 | pub fn try_to_vec(&self) -> std::io::Result> { 38 | let mut data = Vec::new(); 39 | self.serialize(&mut data)?; 40 | Ok(data) 41 | } 42 | } 43 | pub const NO_ACCOUNTS_IX_IX_DISCM: u8 = 69u8; 44 | #[derive(BorshDeserialize, BorshSerialize, Clone, Debug, PartialEq)] 45 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 46 | pub struct NoAccountsIxIxArgs { 47 | pub arg: u8, 48 | } 49 | #[derive(Clone, Debug, PartialEq)] 50 | pub struct NoAccountsIxIxData(pub NoAccountsIxIxArgs); 51 | impl From for NoAccountsIxIxData { 52 | fn from(args: NoAccountsIxIxArgs) -> Self { 53 | Self(args) 54 | } 55 | } 56 | impl NoAccountsIxIxData { 57 | pub fn deserialize(buf: &[u8]) -> std::io::Result { 58 | let mut reader = buf; 59 | let mut maybe_discm_buf = [0u8; 1]; 60 | reader.read_exact(&mut maybe_discm_buf)?; 61 | let maybe_discm = maybe_discm_buf[0]; 62 | if maybe_discm != NO_ACCOUNTS_IX_IX_DISCM { 63 | return Err(std::io::Error::new( 64 | std::io::ErrorKind::Other, 65 | format!( 66 | "discm does not match. Expected: {:?}. Received: {:?}", 67 | NO_ACCOUNTS_IX_IX_DISCM, maybe_discm 68 | ), 69 | )); 70 | } 71 | Ok(Self(NoAccountsIxIxArgs::deserialize(&mut reader)?)) 72 | } 73 | pub fn serialize(&self, mut writer: W) -> std::io::Result<()> { 74 | writer.write_all(&[NO_ACCOUNTS_IX_IX_DISCM])?; 75 | self.0.serialize(&mut writer) 76 | } 77 | pub fn try_to_vec(&self) -> std::io::Result> { 78 | let mut data = Vec::new(); 79 | self.serialize(&mut data)?; 80 | Ok(data) 81 | } 82 | } 83 | pub fn no_accounts_ix_ix_with_program_id( 84 | program_id: Pubkey, 85 | args: NoAccountsIxIxArgs, 86 | ) -> std::io::Result { 87 | let data: NoAccountsIxIxData = args.into(); 88 | Ok(Instruction { 89 | program_id, 90 | accounts: Vec::new(), 91 | data: data.try_to_vec()?, 92 | }) 93 | } 94 | pub fn no_accounts_ix_ix(args: NoAccountsIxIxArgs) -> std::io::Result { 95 | no_accounts_ix_ix_with_program_id(crate::ID, args) 96 | } 97 | pub fn no_accounts_ix_invoke_with_program_id( 98 | program_id: Pubkey, 99 | args: NoAccountsIxIxArgs, 100 | ) -> ProgramResult { 101 | let ix = no_accounts_ix_ix_with_program_id(program_id, args)?; 102 | invoke(&ix, &[]) 103 | } 104 | pub fn no_accounts_ix_invoke(args: NoAccountsIxIxArgs) -> ProgramResult { 105 | no_accounts_ix_invoke_with_program_id(crate::ID, args) 106 | } 107 | pub fn no_accounts_ix_invoke_signed_with_program_id( 108 | program_id: Pubkey, 109 | args: NoAccountsIxIxArgs, 110 | seeds: &[&[&[u8]]], 111 | ) -> ProgramResult { 112 | let ix = no_accounts_ix_ix_with_program_id(program_id, args)?; 113 | invoke_signed(&ix, &[], seeds) 114 | } 115 | pub fn no_accounts_ix_invoke_signed(args: NoAccountsIxIxArgs, seeds: &[&[&[u8]]]) -> ProgramResult { 116 | no_accounts_ix_invoke_signed_with_program_id(crate::ID, args, seeds) 117 | } 118 | -------------------------------------------------------------------------------- /examples/shank/ix_no_accounts/shank_ix_no_accounts_interface/src/lib.rs: -------------------------------------------------------------------------------- 1 | solana_program::declare_id!("TH1S1SNoTAVAL1DPUBKEYDoNoTUSE11111111111111"); 2 | pub mod instructions; 3 | pub use instructions::*; 4 | -------------------------------------------------------------------------------- /examples/shank/ix_no_accounts_pubkey_arg/idl.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0", 3 | "name": "shank_ix_no_accounts_pubkey_arg", 4 | "instructions": [ 5 | { 6 | "name": "noAccountsPubkeyArgIx", 7 | "args": [ 8 | { 9 | "name": "arg", 10 | "type": "publicKey" 11 | } 12 | ], 13 | "discriminant": { 14 | "type": "u8", 15 | "value": 69 16 | } 17 | } 18 | ], 19 | "metadata": { 20 | "origin": "shank", 21 | "address": "TH1S1SNoTAVAL1DPUBKEYDoNoTUSE11111111111111" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/shank/ix_no_accounts_pubkey_arg/shank_ix_no_accounts_pubkey_arg_interface/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock -------------------------------------------------------------------------------- /examples/shank/ix_no_accounts_pubkey_arg/shank_ix_no_accounts_pubkey_arg_interface/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shank_ix_no_accounts_pubkey_arg_interface" 3 | version = "0.0.0" 4 | edition = "2021" 5 | 6 | [dependencies.borsh] 7 | workspace = true 8 | 9 | [dependencies.serde] 10 | optional = true 11 | workspace = true 12 | 13 | [dependencies.solana-program] 14 | workspace = true 15 | -------------------------------------------------------------------------------- /examples/shank/ix_no_accounts_pubkey_arg/shank_ix_no_accounts_pubkey_arg_interface/src/instructions.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use solana_program::{ 3 | entrypoint::ProgramResult, 4 | instruction::Instruction, 5 | program::{invoke, invoke_signed}, 6 | pubkey::Pubkey, 7 | }; 8 | use std::io::Read; 9 | #[derive(Clone, Debug, PartialEq)] 10 | pub enum ShankIxNoAccountsPubkeyArgProgramIx { 11 | NoAccountsPubkeyArgIx(NoAccountsPubkeyArgIxIxArgs), 12 | } 13 | impl ShankIxNoAccountsPubkeyArgProgramIx { 14 | pub fn deserialize(buf: &[u8]) -> std::io::Result { 15 | let mut reader = buf; 16 | let mut maybe_discm_buf = [0u8; 1]; 17 | reader.read_exact(&mut maybe_discm_buf)?; 18 | let maybe_discm = maybe_discm_buf[0]; 19 | match maybe_discm { 20 | NO_ACCOUNTS_PUBKEY_ARG_IX_IX_DISCM => Ok(Self::NoAccountsPubkeyArgIx( 21 | NoAccountsPubkeyArgIxIxArgs::deserialize(&mut reader)?, 22 | )), 23 | _ => Err(std::io::Error::new( 24 | std::io::ErrorKind::Other, 25 | format!("discm {:?} not found", maybe_discm), 26 | )), 27 | } 28 | } 29 | pub fn serialize(&self, mut writer: W) -> std::io::Result<()> { 30 | match self { 31 | Self::NoAccountsPubkeyArgIx(args) => { 32 | writer.write_all(&[NO_ACCOUNTS_PUBKEY_ARG_IX_IX_DISCM])?; 33 | args.serialize(&mut writer) 34 | } 35 | } 36 | } 37 | pub fn try_to_vec(&self) -> std::io::Result> { 38 | let mut data = Vec::new(); 39 | self.serialize(&mut data)?; 40 | Ok(data) 41 | } 42 | } 43 | pub const NO_ACCOUNTS_PUBKEY_ARG_IX_IX_DISCM: u8 = 69u8; 44 | #[derive(BorshDeserialize, BorshSerialize, Clone, Debug, PartialEq)] 45 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 46 | pub struct NoAccountsPubkeyArgIxIxArgs { 47 | pub arg: Pubkey, 48 | } 49 | #[derive(Clone, Debug, PartialEq)] 50 | pub struct NoAccountsPubkeyArgIxIxData(pub NoAccountsPubkeyArgIxIxArgs); 51 | impl From for NoAccountsPubkeyArgIxIxData { 52 | fn from(args: NoAccountsPubkeyArgIxIxArgs) -> Self { 53 | Self(args) 54 | } 55 | } 56 | impl NoAccountsPubkeyArgIxIxData { 57 | pub fn deserialize(buf: &[u8]) -> std::io::Result { 58 | let mut reader = buf; 59 | let mut maybe_discm_buf = [0u8; 1]; 60 | reader.read_exact(&mut maybe_discm_buf)?; 61 | let maybe_discm = maybe_discm_buf[0]; 62 | if maybe_discm != NO_ACCOUNTS_PUBKEY_ARG_IX_IX_DISCM { 63 | return Err(std::io::Error::new( 64 | std::io::ErrorKind::Other, 65 | format!( 66 | "discm does not match. Expected: {:?}. Received: {:?}", 67 | NO_ACCOUNTS_PUBKEY_ARG_IX_IX_DISCM, maybe_discm 68 | ), 69 | )); 70 | } 71 | Ok(Self(NoAccountsPubkeyArgIxIxArgs::deserialize(&mut reader)?)) 72 | } 73 | pub fn serialize(&self, mut writer: W) -> std::io::Result<()> { 74 | writer.write_all(&[NO_ACCOUNTS_PUBKEY_ARG_IX_IX_DISCM])?; 75 | self.0.serialize(&mut writer) 76 | } 77 | pub fn try_to_vec(&self) -> std::io::Result> { 78 | let mut data = Vec::new(); 79 | self.serialize(&mut data)?; 80 | Ok(data) 81 | } 82 | } 83 | pub fn no_accounts_pubkey_arg_ix_ix_with_program_id( 84 | program_id: Pubkey, 85 | args: NoAccountsPubkeyArgIxIxArgs, 86 | ) -> std::io::Result { 87 | let data: NoAccountsPubkeyArgIxIxData = args.into(); 88 | Ok(Instruction { 89 | program_id, 90 | accounts: Vec::new(), 91 | data: data.try_to_vec()?, 92 | }) 93 | } 94 | pub fn no_accounts_pubkey_arg_ix_ix( 95 | args: NoAccountsPubkeyArgIxIxArgs, 96 | ) -> std::io::Result { 97 | no_accounts_pubkey_arg_ix_ix_with_program_id(crate::ID, args) 98 | } 99 | pub fn no_accounts_pubkey_arg_ix_invoke_with_program_id( 100 | program_id: Pubkey, 101 | args: NoAccountsPubkeyArgIxIxArgs, 102 | ) -> ProgramResult { 103 | let ix = no_accounts_pubkey_arg_ix_ix_with_program_id(program_id, args)?; 104 | invoke(&ix, &[]) 105 | } 106 | pub fn no_accounts_pubkey_arg_ix_invoke(args: NoAccountsPubkeyArgIxIxArgs) -> ProgramResult { 107 | no_accounts_pubkey_arg_ix_invoke_with_program_id(crate::ID, args) 108 | } 109 | pub fn no_accounts_pubkey_arg_ix_invoke_signed_with_program_id( 110 | program_id: Pubkey, 111 | args: NoAccountsPubkeyArgIxIxArgs, 112 | seeds: &[&[&[u8]]], 113 | ) -> ProgramResult { 114 | let ix = no_accounts_pubkey_arg_ix_ix_with_program_id(program_id, args)?; 115 | invoke_signed(&ix, &[], seeds) 116 | } 117 | pub fn no_accounts_pubkey_arg_ix_invoke_signed( 118 | args: NoAccountsPubkeyArgIxIxArgs, 119 | seeds: &[&[&[u8]]], 120 | ) -> ProgramResult { 121 | no_accounts_pubkey_arg_ix_invoke_signed_with_program_id(crate::ID, args, seeds) 122 | } 123 | -------------------------------------------------------------------------------- /examples/shank/ix_no_accounts_pubkey_arg/shank_ix_no_accounts_pubkey_arg_interface/src/lib.rs: -------------------------------------------------------------------------------- 1 | solana_program::declare_id!("TH1S1SNoTAVAL1DPUBKEYDoNoTUSE11111111111111"); 2 | pub mod instructions; 3 | pub use instructions::*; 4 | -------------------------------------------------------------------------------- /examples/shank/ix_no_args/idl.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0", 3 | "name": "shank_ix_no_args", 4 | "instructions": [ 5 | { 6 | "name": "noArgsIx", 7 | "accounts": [ 8 | { 9 | "name": "b", 10 | "isMut": true, 11 | "isSigner": false 12 | } 13 | ], 14 | "discriminant": { 15 | "type": "u8", 16 | "value": 69 17 | } 18 | } 19 | ], 20 | "metadata": { 21 | "origin": "shank", 22 | "address": "TH1S1SNoTAVAL1DPUBKEYDoNoTUSE11111111111111" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/shank/ix_no_args/shank_ix_no_args_interface/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock -------------------------------------------------------------------------------- /examples/shank/ix_no_args/shank_ix_no_args_interface/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shank_ix_no_args_interface" 3 | version = "0.0.0" 4 | edition = "2021" 5 | 6 | [dependencies.borsh] 7 | workspace = true 8 | 9 | [dependencies.serde] 10 | optional = true 11 | workspace = true 12 | 13 | [dependencies.solana-program] 14 | workspace = true 15 | -------------------------------------------------------------------------------- /examples/shank/ix_no_args/shank_ix_no_args_interface/src/instructions.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | account_info::AccountInfo, 3 | entrypoint::ProgramResult, 4 | instruction::{AccountMeta, Instruction}, 5 | program::{invoke, invoke_signed}, 6 | program_error::ProgramError, 7 | pubkey::Pubkey, 8 | }; 9 | use std::io::Read; 10 | #[derive(Clone, Debug, PartialEq)] 11 | pub enum ShankIxNoArgsProgramIx { 12 | NoArgsIx, 13 | } 14 | impl ShankIxNoArgsProgramIx { 15 | pub fn deserialize(buf: &[u8]) -> std::io::Result { 16 | let mut reader = buf; 17 | let mut maybe_discm_buf = [0u8; 1]; 18 | reader.read_exact(&mut maybe_discm_buf)?; 19 | let maybe_discm = maybe_discm_buf[0]; 20 | match maybe_discm { 21 | NO_ARGS_IX_IX_DISCM => Ok(Self::NoArgsIx), 22 | _ => Err(std::io::Error::new( 23 | std::io::ErrorKind::Other, 24 | format!("discm {:?} not found", maybe_discm), 25 | )), 26 | } 27 | } 28 | pub fn serialize(&self, mut writer: W) -> std::io::Result<()> { 29 | match self { 30 | Self::NoArgsIx => writer.write_all(&[NO_ARGS_IX_IX_DISCM]), 31 | } 32 | } 33 | pub fn try_to_vec(&self) -> std::io::Result> { 34 | let mut data = Vec::new(); 35 | self.serialize(&mut data)?; 36 | Ok(data) 37 | } 38 | } 39 | fn invoke_instruction<'info, A: Into<[AccountInfo<'info>; N]>, const N: usize>( 40 | ix: &Instruction, 41 | accounts: A, 42 | ) -> ProgramResult { 43 | let account_info: [AccountInfo<'info>; N] = accounts.into(); 44 | invoke(ix, &account_info) 45 | } 46 | fn invoke_instruction_signed<'info, A: Into<[AccountInfo<'info>; N]>, const N: usize>( 47 | ix: &Instruction, 48 | accounts: A, 49 | seeds: &[&[&[u8]]], 50 | ) -> ProgramResult { 51 | let account_info: [AccountInfo<'info>; N] = accounts.into(); 52 | invoke_signed(ix, &account_info, seeds) 53 | } 54 | pub const NO_ARGS_IX_IX_ACCOUNTS_LEN: usize = 1; 55 | #[derive(Copy, Clone, Debug)] 56 | pub struct NoArgsIxAccounts<'me, 'info> { 57 | pub b: &'me AccountInfo<'info>, 58 | } 59 | #[derive(Copy, Clone, Debug)] 60 | pub struct NoArgsIxKeys { 61 | pub b: Pubkey, 62 | } 63 | impl From> for NoArgsIxKeys { 64 | fn from(accounts: NoArgsIxAccounts) -> Self { 65 | Self { b: *accounts.b.key } 66 | } 67 | } 68 | impl From for [AccountMeta; NO_ARGS_IX_IX_ACCOUNTS_LEN] { 69 | fn from(keys: NoArgsIxKeys) -> Self { 70 | [AccountMeta { 71 | pubkey: keys.b, 72 | is_signer: false, 73 | is_writable: true, 74 | }] 75 | } 76 | } 77 | impl From<[Pubkey; NO_ARGS_IX_IX_ACCOUNTS_LEN]> for NoArgsIxKeys { 78 | fn from(pubkeys: [Pubkey; NO_ARGS_IX_IX_ACCOUNTS_LEN]) -> Self { 79 | Self { b: pubkeys[0] } 80 | } 81 | } 82 | impl<'info> From> for [AccountInfo<'info>; NO_ARGS_IX_IX_ACCOUNTS_LEN] { 83 | fn from(accounts: NoArgsIxAccounts<'_, 'info>) -> Self { 84 | [accounts.b.clone()] 85 | } 86 | } 87 | impl<'me, 'info> From<&'me [AccountInfo<'info>; NO_ARGS_IX_IX_ACCOUNTS_LEN]> 88 | for NoArgsIxAccounts<'me, 'info> 89 | { 90 | fn from(arr: &'me [AccountInfo<'info>; NO_ARGS_IX_IX_ACCOUNTS_LEN]) -> Self { 91 | Self { b: &arr[0] } 92 | } 93 | } 94 | pub const NO_ARGS_IX_IX_DISCM: u8 = 69u8; 95 | #[derive(Clone, Debug, PartialEq)] 96 | pub struct NoArgsIxIxData; 97 | impl NoArgsIxIxData { 98 | pub fn deserialize(buf: &[u8]) -> std::io::Result { 99 | let mut reader = buf; 100 | let mut maybe_discm_buf = [0u8; 1]; 101 | reader.read_exact(&mut maybe_discm_buf)?; 102 | let maybe_discm = maybe_discm_buf[0]; 103 | if maybe_discm != NO_ARGS_IX_IX_DISCM { 104 | return Err(std::io::Error::new( 105 | std::io::ErrorKind::Other, 106 | format!( 107 | "discm does not match. Expected: {:?}. Received: {:?}", 108 | NO_ARGS_IX_IX_DISCM, maybe_discm 109 | ), 110 | )); 111 | } 112 | Ok(Self) 113 | } 114 | pub fn serialize(&self, mut writer: W) -> std::io::Result<()> { 115 | writer.write_all(&[NO_ARGS_IX_IX_DISCM]) 116 | } 117 | pub fn try_to_vec(&self) -> std::io::Result> { 118 | let mut data = Vec::new(); 119 | self.serialize(&mut data)?; 120 | Ok(data) 121 | } 122 | } 123 | pub fn no_args_ix_ix_with_program_id( 124 | program_id: Pubkey, 125 | keys: NoArgsIxKeys, 126 | ) -> std::io::Result { 127 | let metas: [AccountMeta; NO_ARGS_IX_IX_ACCOUNTS_LEN] = keys.into(); 128 | Ok(Instruction { 129 | program_id, 130 | accounts: Vec::from(metas), 131 | data: NoArgsIxIxData.try_to_vec()?, 132 | }) 133 | } 134 | pub fn no_args_ix_ix(keys: NoArgsIxKeys) -> std::io::Result { 135 | no_args_ix_ix_with_program_id(crate::ID, keys) 136 | } 137 | pub fn no_args_ix_invoke_with_program_id( 138 | program_id: Pubkey, 139 | accounts: NoArgsIxAccounts<'_, '_>, 140 | ) -> ProgramResult { 141 | let keys: NoArgsIxKeys = accounts.into(); 142 | let ix = no_args_ix_ix_with_program_id(program_id, keys)?; 143 | invoke_instruction(&ix, accounts) 144 | } 145 | pub fn no_args_ix_invoke(accounts: NoArgsIxAccounts<'_, '_>) -> ProgramResult { 146 | no_args_ix_invoke_with_program_id(crate::ID, accounts) 147 | } 148 | pub fn no_args_ix_invoke_signed_with_program_id( 149 | program_id: Pubkey, 150 | accounts: NoArgsIxAccounts<'_, '_>, 151 | seeds: &[&[&[u8]]], 152 | ) -> ProgramResult { 153 | let keys: NoArgsIxKeys = accounts.into(); 154 | let ix = no_args_ix_ix_with_program_id(program_id, keys)?; 155 | invoke_instruction_signed(&ix, accounts, seeds) 156 | } 157 | pub fn no_args_ix_invoke_signed( 158 | accounts: NoArgsIxAccounts<'_, '_>, 159 | seeds: &[&[&[u8]]], 160 | ) -> ProgramResult { 161 | no_args_ix_invoke_signed_with_program_id(crate::ID, accounts, seeds) 162 | } 163 | pub fn no_args_ix_verify_account_keys( 164 | accounts: NoArgsIxAccounts<'_, '_>, 165 | keys: NoArgsIxKeys, 166 | ) -> Result<(), (Pubkey, Pubkey)> { 167 | for (actual, expected) in [(accounts.b.key, &keys.b)] { 168 | if actual != expected { 169 | return Err((*actual, *expected)); 170 | } 171 | } 172 | Ok(()) 173 | } 174 | pub fn no_args_ix_verify_writable_privileges<'me, 'info>( 175 | accounts: NoArgsIxAccounts<'me, 'info>, 176 | ) -> Result<(), (&'me AccountInfo<'info>, ProgramError)> { 177 | for should_be_writable in [accounts.b] { 178 | if !should_be_writable.is_writable { 179 | return Err((should_be_writable, ProgramError::InvalidAccountData)); 180 | } 181 | } 182 | Ok(()) 183 | } 184 | pub fn no_args_ix_verify_account_privileges<'me, 'info>( 185 | accounts: NoArgsIxAccounts<'me, 'info>, 186 | ) -> Result<(), (&'me AccountInfo<'info>, ProgramError)> { 187 | no_args_ix_verify_writable_privileges(accounts)?; 188 | Ok(()) 189 | } 190 | -------------------------------------------------------------------------------- /examples/shank/ix_no_args/shank_ix_no_args_interface/src/lib.rs: -------------------------------------------------------------------------------- 1 | solana_program::declare_id!("TH1S1SNoTAVAL1DPUBKEYDoNoTUSE11111111111111"); 2 | pub mod instructions; 3 | pub use instructions::*; 4 | -------------------------------------------------------------------------------- /examples/shank/ix_no_privilege/idl.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.0.0", 3 | "name": "shank_ix_no_privilege", 4 | "instructions": [ 5 | { 6 | "name": "noPrivilegedAccountIx", 7 | "accounts": [ 8 | { 9 | "name": "b", 10 | "isMut": false, 11 | "isSigner": false 12 | } 13 | ], 14 | "args": [ 15 | { 16 | "name": "arg", 17 | "type": "u8" 18 | } 19 | ], 20 | "discriminant": { 21 | "type": "u8", 22 | "value": 69 23 | } 24 | } 25 | ], 26 | "metadata": { 27 | "origin": "shank", 28 | "address": "TH1S1SNoTAVAL1DPUBKEYDoNoTUSE11111111111111" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/shank/ix_no_privilege/shank_ix_no_privilege_interface/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock -------------------------------------------------------------------------------- /examples/shank/ix_no_privilege/shank_ix_no_privilege_interface/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shank_ix_no_privilege_interface" 3 | version = "0.0.0" 4 | edition = "2021" 5 | 6 | [dependencies.borsh] 7 | workspace = true 8 | 9 | [dependencies.serde] 10 | optional = true 11 | workspace = true 12 | 13 | [dependencies.solana-program] 14 | workspace = true 15 | -------------------------------------------------------------------------------- /examples/shank/ix_no_privilege/shank_ix_no_privilege_interface/src/lib.rs: -------------------------------------------------------------------------------- 1 | solana_program::declare_id!("TH1S1SNoTAVAL1DPUBKEYDoNoTUSE11111111111111"); 2 | pub mod instructions; 3 | pub use instructions::*; 4 | -------------------------------------------------------------------------------- /examples/shank/phoenix_v1/README.md: -------------------------------------------------------------------------------- 1 | # phoenix_v1 2 | 3 | - Manually added hidden [`Ticks`](https://github.com/Ellipsis-Labs/phoenix-v1/blob/1bdb4f35ba76397f152da0575b22c9e215e66e33/src/quantities.rs#L229) struct def into IDL to make it work -------------------------------------------------------------------------------- /examples/shank/phoenix_v1/phoenix_v1_interface/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock -------------------------------------------------------------------------------- /examples/shank/phoenix_v1/phoenix_v1_interface/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "phoenix_v1_interface" 3 | version = "0.2.3" 4 | edition = "2021" 5 | 6 | [dependencies.borsh] 7 | workspace = true 8 | 9 | [dependencies.bytemuck] 10 | features = ["derive"] 11 | workspace = true 12 | 13 | [dependencies.num-derive] 14 | workspace = true 15 | 16 | [dependencies.num-traits] 17 | workspace = true 18 | 19 | [dependencies.serde] 20 | optional = true 21 | workspace = true 22 | 23 | [dependencies.solana-program] 24 | workspace = true 25 | 26 | [dependencies.thiserror] 27 | workspace = true 28 | -------------------------------------------------------------------------------- /examples/shank/phoenix_v1/phoenix_v1_interface/src/errors.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | decode_error::DecodeError, 3 | msg, 4 | program_error::{PrintProgramError, ProgramError}, 5 | }; 6 | use thiserror::Error; 7 | #[derive(Clone, Copy, Debug, Eq, Error, num_derive::FromPrimitive, PartialEq)] 8 | pub enum PhoenixV1Error { 9 | #[error("Invalid market parameters error")] 10 | InvalidMarketParameters = 0, 11 | #[error("Invalid market authority error")] 12 | InvalidMarketAuthority = 1, 13 | #[error("Market deserialization error")] 14 | FailedToLoadMarketFromAccount = 2, 15 | #[error("Market already initialized error")] 16 | MarketAlreadyInitialized = 3, 17 | #[error("Market is not initialized error")] 18 | MarketUninitialized = 4, 19 | #[error("Invalid state transition error")] 20 | InvalidStateTransition = 5, 21 | #[error("Invalid market signer error")] 22 | InvalidMarketSigner = 6, 23 | #[error("Invalid lot size error")] 24 | InvalidLotSize = 7, 25 | #[error("Invalid tick size error")] 26 | InvalidTickSize = 8, 27 | #[error("Invalid mint error")] 28 | InvalidMint = 9, 29 | #[error("Invalid base vault error")] 30 | InvalidBaseVault = 10, 31 | #[error("Invalid quote vault error")] 32 | InvalidQuoteVault = 11, 33 | #[error("Invalid base account error")] 34 | InvalidBaseAccount = 12, 35 | #[error("Invalid quote account error")] 36 | InvalidQuoteAccount = 13, 37 | #[error("Too many events error")] 38 | TooManyEvents = 14, 39 | #[error("New order error")] 40 | NewOrderError = 15, 41 | #[error("Reduce order error")] 42 | ReduceOrderError = 16, 43 | #[error("Cancel multiple orders error")] 44 | CancelMultipleOrdersError = 17, 45 | #[error("Withdraw funds error")] 46 | WithdrawFundsError = 18, 47 | #[error("Remove empty orders error")] 48 | RemoveEmptyOrdersError = 19, 49 | #[error("Trader not found error")] 50 | TraderNotFound = 20, 51 | #[error("Invalid seat status")] 52 | InvalidSeatStatus = 21, 53 | #[error("Failed to evict trader")] 54 | EvictionError = 22, 55 | #[error("Non empty scratch buffer")] 56 | NonEmptyScratchBuffer = 23, 57 | #[error("Failed to serialize event")] 58 | FailedToSerializeEvent = 24, 59 | #[error("Failed to flush buffer")] 60 | FailedToFlushBuffer = 25, 61 | } 62 | impl From for ProgramError { 63 | fn from(e: PhoenixV1Error) -> Self { 64 | ProgramError::Custom(e as u32) 65 | } 66 | } 67 | impl DecodeError for PhoenixV1Error { 68 | fn type_of() -> &'static str { 69 | "PhoenixV1Error" 70 | } 71 | } 72 | impl PrintProgramError for PhoenixV1Error { 73 | fn print(&self) 74 | where 75 | E: 'static 76 | + std::error::Error 77 | + DecodeError 78 | + PrintProgramError 79 | + num_traits::FromPrimitive, 80 | { 81 | msg!(&self.to_string()); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /examples/shank/phoenix_v1/phoenix_v1_interface/src/lib.rs: -------------------------------------------------------------------------------- 1 | solana_program::declare_id!("PhoeNiXZ8ByJGLkxNfZRnkUfjvmuYqLR89jjFHGqdXY"); 2 | pub mod typedefs; 3 | pub use typedefs::*; 4 | pub mod instructions; 5 | pub use instructions::*; 6 | pub mod errors; 7 | pub use errors::*; 8 | -------------------------------------------------------------------------------- /examples/shank/token_metadata/mpl_token_metadata_client_consumer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mpl_token_metadata_client_consumer" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | mpl_token_metadata_interface = { workspace = true } 9 | solana-cli-config = { workspace = true } 10 | solana-sdk = { workspace = true } 11 | 12 | [dev-dependencies] 13 | borsh = { workspace = true } -------------------------------------------------------------------------------- /examples/shank/token_metadata/mpl_token_metadata_client_consumer/src/main.rs: -------------------------------------------------------------------------------- 1 | use mpl_token_metadata_interface::*; 2 | use solana_cli_config::{Config, CONFIG_FILE}; 3 | use solana_sdk::{pubkey::Pubkey, signature::read_keypair_file, signer::Signer}; 4 | 5 | fn main() { 6 | let config_file = CONFIG_FILE.as_ref().unwrap(); 7 | let cli_config = Config::load(config_file).unwrap(); 8 | let kp = read_keypair_file(cli_config.keypair_path).unwrap(); 9 | 10 | let accounts = RemoveCreatorVerificationKeys { 11 | creator: kp.pubkey(), 12 | metadata: Pubkey::new_unique(), 13 | }; 14 | let remove_creator_ver_ix = remove_creator_verification_ix(accounts).unwrap(); 15 | println!("{:?}", remove_creator_ver_ix); 16 | } 17 | -------------------------------------------------------------------------------- /examples/shank/token_metadata/mpl_token_metadata_client_consumer/tests/test-serde.rs: -------------------------------------------------------------------------------- 1 | use mpl_token_metadata_interface::{ 2 | CreateMasterEditionArgs, CreateMasterEditionV3IxArgs, CreateMasterEditionV3IxData, 3 | MplTokenMetadataProgramIx, CREATE_MASTER_EDITION_V3_IX_DISCM, REVOKE_USE_AUTHORITY_IX_DISCM, 4 | }; 5 | 6 | #[test] 7 | fn test_ix_data_borsh_roundtrip() { 8 | let sample = CreateMasterEditionV3IxData(CreateMasterEditionV3IxArgs { 9 | create_master_edition_args: CreateMasterEditionArgs { 10 | max_supply: Some(69), 11 | }, 12 | }); 13 | let serialized = sample.try_to_vec().unwrap(); 14 | assert_eq!(serialized[0], CREATE_MASTER_EDITION_V3_IX_DISCM); 15 | let deserialized = CreateMasterEditionV3IxData::deserialize(&serialized).unwrap(); 16 | assert_eq!(sample, deserialized); 17 | } 18 | 19 | #[test] 20 | fn test_program_ix_borsh_roundtrip() { 21 | let program_ix = MplTokenMetadataProgramIx::RevokeUseAuthority; 22 | let serialized = program_ix.try_to_vec().unwrap(); 23 | assert_eq!(serialized[0], REVOKE_USE_AUTHORITY_IX_DISCM); 24 | let deserialized = MplTokenMetadataProgramIx::deserialize(&serialized).unwrap(); 25 | assert_eq!(program_ix, deserialized); 26 | } 27 | -------------------------------------------------------------------------------- /examples/shank/token_metadata/mpl_token_metadata_interface/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock -------------------------------------------------------------------------------- /examples/shank/token_metadata/mpl_token_metadata_interface/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mpl_token_metadata_interface" 3 | version = "1.2.5" 4 | edition = "2021" 5 | 6 | [dependencies.borsh] 7 | workspace = true 8 | 9 | [dependencies.num-derive] 10 | workspace = true 11 | 12 | [dependencies.num-traits] 13 | workspace = true 14 | 15 | [dependencies.serde] 16 | optional = true 17 | workspace = true 18 | 19 | [dependencies.solana-program] 20 | workspace = true 21 | 22 | [dependencies.thiserror] 23 | workspace = true 24 | -------------------------------------------------------------------------------- /examples/shank/token_metadata/mpl_token_metadata_interface/src/accounts.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use borsh::{BorshDeserialize, BorshSerialize}; 3 | use solana_program::pubkey::Pubkey; 4 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 5 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 6 | pub struct UseAuthorityRecord { 7 | pub key: Key, 8 | pub allowed_uses: u64, 9 | pub bump: u8, 10 | } 11 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 12 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 13 | pub struct CollectionAuthorityRecord { 14 | pub key: Key, 15 | pub bump: u8, 16 | } 17 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 18 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 19 | pub struct Metadata { 20 | pub key: Key, 21 | pub update_authority: Pubkey, 22 | pub mint: Pubkey, 23 | pub data: Data, 24 | pub primary_sale_happened: bool, 25 | pub is_mutable: bool, 26 | pub edition_nonce: Option, 27 | pub token_standard: Option, 28 | pub collection: Option, 29 | pub uses: Option, 30 | } 31 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 32 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 33 | pub struct MasterEditionV2 { 34 | pub key: Key, 35 | pub supply: u64, 36 | pub max_supply: Option, 37 | } 38 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 39 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 40 | pub struct MasterEditionV1 { 41 | pub key: Key, 42 | pub supply: u64, 43 | pub max_supply: Option, 44 | pub printing_mint: Pubkey, 45 | pub one_time_printing_authorization_mint: Pubkey, 46 | } 47 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 48 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 49 | pub struct Edition { 50 | pub key: Key, 51 | pub parent: Pubkey, 52 | pub edition: u64, 53 | } 54 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 55 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 56 | pub struct ReservationListV2 { 57 | pub key: Key, 58 | pub master_edition: Pubkey, 59 | pub supply_snapshot: Option, 60 | pub reservations: Vec, 61 | pub total_reservation_spots: u64, 62 | pub current_reservation_spots: u64, 63 | } 64 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 65 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 66 | pub struct ReservationListV1 { 67 | pub key: Key, 68 | pub master_edition: Pubkey, 69 | pub supply_snapshot: Option, 70 | pub reservations: Vec, 71 | } 72 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 73 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 74 | pub struct EditionMarker { 75 | pub key: Key, 76 | pub ledger: [u8; 31], 77 | } 78 | -------------------------------------------------------------------------------- /examples/shank/token_metadata/mpl_token_metadata_interface/src/lib.rs: -------------------------------------------------------------------------------- 1 | solana_program::declare_id!("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"); 2 | pub mod accounts; 3 | pub use accounts::*; 4 | pub mod typedefs; 5 | pub use typedefs::*; 6 | pub mod instructions; 7 | pub use instructions::*; 8 | pub mod errors; 9 | pub use errors::*; 10 | -------------------------------------------------------------------------------- /examples/shank/token_metadata/mpl_token_metadata_interface/src/typedefs.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use solana_program::pubkey::Pubkey; 3 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 4 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 5 | pub struct MintPrintingTokensViaTokenArgs { 6 | pub supply: u64, 7 | } 8 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 9 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 10 | pub struct SetReservationListArgs { 11 | pub reservations: Vec, 12 | pub total_reservation_spots: Option, 13 | pub offset: u64, 14 | pub total_spot_offset: u64, 15 | } 16 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 17 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 18 | pub struct UpdateMetadataAccountArgs { 19 | pub data: Option, 20 | pub update_authority: Option, 21 | pub primary_sale_happened: Option, 22 | } 23 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 24 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 25 | pub struct UpdateMetadataAccountArgsV2 { 26 | pub data: Option, 27 | pub update_authority: Option, 28 | pub primary_sale_happened: Option, 29 | pub is_mutable: Option, 30 | } 31 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 32 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 33 | pub struct CreateMetadataAccountArgs { 34 | pub data: Data, 35 | pub is_mutable: bool, 36 | } 37 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 38 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 39 | pub struct CreateMetadataAccountArgsV2 { 40 | pub data: DataV2, 41 | pub is_mutable: bool, 42 | } 43 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 44 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 45 | pub struct CreateMasterEditionArgs { 46 | pub max_supply: Option, 47 | } 48 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 49 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 50 | pub struct MintNewEditionFromMasterEditionViaTokenArgs { 51 | pub edition: u64, 52 | } 53 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 54 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 55 | pub struct ApproveUseAuthorityArgs { 56 | pub number_of_uses: u64, 57 | } 58 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 59 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 60 | pub struct UtilizeArgs { 61 | pub number_of_uses: u64, 62 | } 63 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 64 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 65 | pub struct Data { 66 | pub name: String, 67 | pub symbol: String, 68 | pub uri: String, 69 | pub seller_fee_basis_points: u16, 70 | pub creators: Option>, 71 | } 72 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 73 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 74 | pub struct DataV2 { 75 | pub name: String, 76 | pub symbol: String, 77 | pub uri: String, 78 | pub seller_fee_basis_points: u16, 79 | pub creators: Option>, 80 | pub collection: Option, 81 | pub uses: Option, 82 | } 83 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 84 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 85 | pub struct Uses { 86 | pub use_method: UseMethod, 87 | pub remaining: u64, 88 | pub total: u64, 89 | } 90 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 91 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 92 | pub struct Collection { 93 | pub verified: bool, 94 | pub key: Pubkey, 95 | } 96 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 97 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 98 | pub struct Creator { 99 | pub address: Pubkey, 100 | pub verified: bool, 101 | pub share: u8, 102 | } 103 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 104 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 105 | pub struct Reservation { 106 | pub address: Pubkey, 107 | pub spots_remaining: u64, 108 | pub total_spots: u64, 109 | } 110 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 111 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 112 | pub struct ReservationV1 { 113 | pub address: Pubkey, 114 | pub spots_remaining: u8, 115 | pub total_spots: u8, 116 | } 117 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 118 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 119 | pub enum Key { 120 | Uninitialized, 121 | EditionV1, 122 | MasterEditionV1, 123 | ReservationListV1, 124 | MetadataV1, 125 | ReservationListV2, 126 | MasterEditionV2, 127 | EditionMarker, 128 | UseAuthorityRecord, 129 | CollectionAuthorityRecord, 130 | } 131 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 132 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 133 | pub enum UseMethod { 134 | Burn, 135 | Multiple, 136 | Single, 137 | } 138 | #[derive(Clone, Debug, BorshDeserialize, BorshSerialize, PartialEq)] 139 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 140 | pub enum TokenStandard { 141 | NonFungible, 142 | FungibleAsset, 143 | Fungible, 144 | NonFungibleEdition, 145 | } 146 | -------------------------------------------------------------------------------- /examples/shank/token_metadata/mpl_token_metadata_onchain_consumer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mpl_token_metadata_onchain_consumer" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [features] 8 | no-entrypoint = [] 9 | 10 | [dependencies] 11 | borsh = { workspace = true } 12 | mpl_token_metadata_interface = { workspace = true } 13 | solana-program = { workspace = true } 14 | 15 | [lib] 16 | crate-type = ["cdylib", "lib"] 17 | 18 | [package.metadata.docs.rs] 19 | targets = ["x86_64-unknown-linux-gnu"] -------------------------------------------------------------------------------- /examples/shank/token_metadata/mpl_token_metadata_onchain_consumer/src/entrypoint.rs: -------------------------------------------------------------------------------- 1 | use borsh::BorshDeserialize; 2 | use mpl_token_metadata_interface::*; 3 | use solana_program::{ 4 | account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, program_error::ProgramError, 5 | pubkey::Pubkey, 6 | }; 7 | 8 | entrypoint!(process_instruction); 9 | fn process_instruction( 10 | _program_id: &Pubkey, 11 | accounts: &[AccountInfo], 12 | ix_data: &[u8], 13 | ) -> ProgramResult { 14 | let cpi_slice: &[AccountInfo; CREATE_MASTER_EDITION_IX_ACCOUNTS_LEN] = accounts 15 | .get(..CREATE_MASTER_EDITION_IX_ACCOUNTS_LEN) 16 | .ok_or(ProgramError::NotEnoughAccountKeys)? 17 | .try_into() 18 | .unwrap(); 19 | let cpi_accounts: CreateMasterEditionAccounts = cpi_slice.into(); 20 | 21 | if let Err((acc, err)) = create_master_edition_verify_account_privileges(cpi_accounts) { 22 | solana_program::msg!( 23 | "Writable/signer privilege escalation for {}: {}", 24 | acc.key, 25 | err 26 | ); 27 | return Err(err); 28 | } 29 | 30 | let mut ix_data_reader = ix_data; 31 | let create_master_edition_ix_args_without_discm = 32 | CreateMasterEditionIxArgs::deserialize(&mut ix_data_reader)?; 33 | 34 | create_master_edition_invoke(cpi_accounts, create_master_edition_ix_args_without_discm) 35 | } 36 | -------------------------------------------------------------------------------- /examples/shank/token_metadata/mpl_token_metadata_onchain_consumer/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(test), forbid(unsafe_code))] 2 | 3 | #[cfg(not(feature = "no-entrypoint"))] 4 | mod entrypoint; 5 | -------------------------------------------------------------------------------- /install-precommit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | wget -O pre-commit.pyz https://github.com/pre-commit/pre-commit/releases/download/v2.16.0/pre-commit-2.16.0.pyz && \ 4 | python3 pre-commit.pyz install && \ 5 | python3 pre-commit.pyz install --hook-type commit-msg && \ 6 | rm pre-commit.pyz 7 | -------------------------------------------------------------------------------- /solores/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solores" 3 | description = "Solana IDL to Rust client / CPI interface generator" 4 | license = "MIT OR Apache-2.0" 5 | repository = "https://github.com/igneous-labs/solores" 6 | version = "0.8.0" 7 | edition = "2021" 8 | include = ["src/", "README.md", "tests/"] 9 | categories = [ 10 | "command-line-utilities", 11 | "cryptography::cryptocurrencies", 12 | "development-tools", 13 | ] 14 | keywords = [ 15 | "anchor", 16 | "codegen", 17 | "metaplex", 18 | "shank", 19 | "solana", 20 | "idl" 21 | ] 22 | 23 | [features] 24 | default = [] 25 | test_gen_examples = [] 26 | bytes_to_u8 = [] 27 | 28 | [lib] 29 | name = "solores" 30 | 31 | [[bin]] 32 | name = "solores" 33 | 34 | [dependencies] 35 | bs58 = { workspace = true } 36 | clap = { workspace = true, features = ["derive"] } 37 | env_logger = { workspace = true } 38 | heck = { workspace = true } 39 | itertools = { workspace = true } 40 | prettyplease = { workspace = true } 41 | log = { workspace = true } 42 | log-panics = { workspace = true, features = ["with-backtrace"]} 43 | proc-macro2 = { workspace = true } 44 | quote = { workspace = true } 45 | serde = { workspace = true, features = ["derive"] } 46 | serde_json = { workspace = true } 47 | sha2 = { workspace = true } 48 | syn = { workspace = true, features = ["full"] } 49 | toml = { workspace = true } 50 | void = { workspace = true } 51 | 52 | [dev-dependencies] 53 | test_utils = { workspace = true } 54 | -------------------------------------------------------------------------------- /solores/README.md: -------------------------------------------------------------------------------- 1 | # solores 2 | 3 | Please refer to the [main repo's README](https://github.com/igneous-labs/solores) for more information. 4 | -------------------------------------------------------------------------------- /solores/src/idl_format/README.md: -------------------------------------------------------------------------------- 1 | # idl_format 2 | 3 | The main `IdlFormat` trait defines 4 | - how to deserialize the IDL file 5 | - how to generate rust code from the deserialized struct 6 | 7 | Currently each IDL format is completely isolated from each other in its own folder with minimum common code shared between them. This allows for independent evolution of each format at the cost of (a lot of) duplicated code. Might refactor this in the future. -------------------------------------------------------------------------------- /solores/src/idl_format/anchor/accounts/account.rs: -------------------------------------------------------------------------------- 1 | use heck::{ToPascalCase, ToShoutySnakeCase}; 2 | use proc_macro2::TokenStream; 3 | use quote::{format_ident, quote}; 4 | use serde::Deserialize; 5 | use sha2::{Digest, Sha256}; 6 | 7 | use crate::idl_format::anchor::typedefs::NamedType; 8 | use crate::utils::conditional_pascal_case; 9 | 10 | #[derive(Deserialize)] 11 | pub struct NamedAccount(pub NamedType); 12 | 13 | impl NamedAccount { 14 | pub fn to_token_stream(&self, cli_args: &crate::Args) -> TokenStream { 15 | let name = &self.0.name; 16 | // discriminant 17 | let account_discm_ident = format_ident!("{}_ACCOUNT_DISCM", name.to_shouty_snake_case()); 18 | // pre-image: "account:{AccountStructName}" 19 | let discm = <[u8; 8]>::try_from( 20 | &Sha256::digest(format!("account:{}", name.to_pascal_case()).as_bytes()).as_slice() 21 | [..8], 22 | ) 23 | .unwrap(); 24 | let discm_tokens: TokenStream = format!("{:?}", discm).parse().unwrap(); 25 | 26 | let struct_def = self.0.to_token_stream(cli_args); 27 | 28 | let struct_ident = format_ident!("{}", conditional_pascal_case(name)); 29 | let account_ident = format_ident!("{}Account", conditional_pascal_case(name)); 30 | quote! { 31 | pub const #account_discm_ident: [u8; 8] = #discm_tokens; 32 | 33 | #struct_def 34 | 35 | #[derive(Clone, Debug, PartialEq)] 36 | pub struct #account_ident(pub #struct_ident); 37 | 38 | impl #account_ident { 39 | pub fn deserialize(buf: &[u8]) -> std::io::Result { 40 | use std::io::Read; 41 | let mut reader = buf; 42 | let mut maybe_discm = [0u8; 8]; 43 | reader.read_exact(&mut maybe_discm)?; 44 | if maybe_discm != #account_discm_ident { 45 | return Err( 46 | std::io::Error::new( 47 | std::io::ErrorKind::Other, format!("discm does not match. Expected: {:?}. Received: {:?}", #account_discm_ident, maybe_discm) 48 | ) 49 | ); 50 | } 51 | Ok(Self(#struct_ident::deserialize(&mut reader)?)) 52 | } 53 | 54 | pub fn serialize(&self, mut writer: W) -> std::io::Result<()> { 55 | writer.write_all(&#account_discm_ident)?; 56 | self.0.serialize(&mut writer) 57 | } 58 | 59 | pub fn try_to_vec(&self) -> std::io::Result> { 60 | let mut data = Vec::new(); 61 | self.serialize(&mut data)?; 62 | Ok(data) 63 | } 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /solores/src/idl_format/anchor/accounts/mod.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | 4 | use crate::idl_format::IdlCodegenModule; 5 | 6 | mod account; 7 | pub use account::*; 8 | 9 | pub struct AccountsCodegenModule<'a> { 10 | pub cli_args: &'a crate::Args, 11 | pub named_accounts: &'a [NamedAccount], 12 | } 13 | 14 | impl IdlCodegenModule for AccountsCodegenModule<'_> { 15 | fn name(&self) -> &str { 16 | "accounts" 17 | } 18 | 19 | fn gen_head(&self) -> TokenStream { 20 | let mut res = quote! { 21 | use borsh::{BorshDeserialize, BorshSerialize}; 22 | }; 23 | for a in self.named_accounts { 24 | if self.cli_args.zero_copy.iter().any(|e| e == &a.0.name) { 25 | res.extend(quote! { 26 | use bytemuck::{Pod, Zeroable}; 27 | }); 28 | break; 29 | } 30 | } 31 | let mut has_pubkey = false; 32 | let mut has_defined = false; 33 | for a in self.named_accounts { 34 | if a.0.r#type.has_pubkey_field() && !has_pubkey { 35 | has_pubkey = true; 36 | res.extend(quote! { 37 | use solana_program::pubkey::Pubkey; 38 | }); 39 | } 40 | if a.0.r#type.has_defined_field() && !has_defined { 41 | has_defined = true; 42 | res.extend(quote! { 43 | use crate::*; 44 | }) 45 | } 46 | if has_defined && has_pubkey { 47 | break; 48 | } 49 | } 50 | res 51 | } 52 | 53 | fn gen_body(&self) -> TokenStream { 54 | self.named_accounts 55 | .iter() 56 | .map(|e| e.to_token_stream(self.cli_args)) 57 | .collect() 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /solores/src/idl_format/anchor/errors/error.rs: -------------------------------------------------------------------------------- 1 | use heck::ToPascalCase; 2 | use proc_macro2::{Span, TokenStream}; 3 | use quote::{format_ident, quote, ToTokens}; 4 | use serde::Deserialize; 5 | use syn::LitInt; 6 | 7 | #[derive(Deserialize)] 8 | pub struct ErrorEnumVariant { 9 | code: u32, 10 | name: String, 11 | msg: String, 12 | } 13 | 14 | impl ToTokens for ErrorEnumVariant { 15 | fn to_tokens(&self, tokens: &mut TokenStream) { 16 | let variant_ident = format_ident!("{}", self.name.to_pascal_case()); 17 | let msg = &self.msg; 18 | let code_literal = LitInt::new(&self.code.to_string(), Span::call_site()); 19 | tokens.extend(quote! { 20 | #[error(#msg)] 21 | #variant_ident = #code_literal, 22 | }) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /solores/src/idl_format/anchor/errors/mod.rs: -------------------------------------------------------------------------------- 1 | use heck::ToPascalCase; 2 | use proc_macro2::TokenStream; 3 | use quote::{format_ident, quote, ToTokens}; 4 | 5 | use crate::idl_format::IdlCodegenModule; 6 | 7 | mod error; 8 | pub use error::*; 9 | 10 | pub struct ErrorsCodegenModule<'a> { 11 | pub program_name: &'a str, 12 | pub variants: &'a [ErrorEnumVariant], 13 | } 14 | 15 | impl IdlCodegenModule for ErrorsCodegenModule<'_> { 16 | fn name(&self) -> &str { 17 | "errors" 18 | } 19 | 20 | fn gen_head(&self) -> TokenStream { 21 | quote! { 22 | use solana_program::{ 23 | decode_error::DecodeError, 24 | msg, 25 | program_error::{PrintProgramError, ProgramError}, 26 | }; 27 | use thiserror::Error; 28 | } 29 | } 30 | 31 | fn gen_body(&self) -> TokenStream { 32 | let error_enum_variants: TokenStream = self 33 | .variants 34 | .iter() 35 | .map(|e| e.into_token_stream()) 36 | .collect(); 37 | 38 | let error_enum_ident_str = format!("{}Error", self.program_name.to_pascal_case()); 39 | let error_enum_ident = format_ident!("{}", &error_enum_ident_str); 40 | quote! { 41 | #[derive(Clone, Copy, Debug, Eq, Error, num_derive::FromPrimitive, PartialEq)] 42 | pub enum #error_enum_ident { 43 | #error_enum_variants 44 | } 45 | 46 | impl From<#error_enum_ident> for ProgramError { 47 | fn from(e: #error_enum_ident) -> Self { 48 | ProgramError::Custom(e as u32) 49 | } 50 | } 51 | 52 | impl DecodeError for #error_enum_ident { 53 | fn type_of() -> &'static str { 54 | #error_enum_ident_str 55 | } 56 | } 57 | 58 | impl PrintProgramError for #error_enum_ident { 59 | fn print(&self) 60 | where 61 | E: 'static 62 | + std::error::Error 63 | + DecodeError 64 | + PrintProgramError 65 | + num_traits::FromPrimitive, 66 | { 67 | msg!(&self.to_string()); 68 | } 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /solores/src/idl_format/anchor/events/event.rs: -------------------------------------------------------------------------------- 1 | use heck::{ToPascalCase, ToShoutySnakeCase}; 2 | use proc_macro2::{Ident, TokenStream}; 3 | use quote::{format_ident, quote, ToTokens}; 4 | use serde::Deserialize; 5 | use sha2::{Digest, Sha256}; 6 | 7 | use crate::idl_format::anchor::typedefs::TypedefField; 8 | 9 | #[derive(Deserialize)] 10 | pub struct Event(pub EventType); 11 | 12 | #[derive(Deserialize)] 13 | pub struct EventType { 14 | pub name: String, 15 | // NB: theres also an `index` field that's ignored for now since we dk what it does: 16 | // https://github.com/coral-xyz/anchor/blob/8f30f00ec363b7e82aa0b3c7041e912919b33cf5/lang/attribute/event/src/lib.rs#L62C1-L64 17 | pub fields: Vec, 18 | } 19 | 20 | impl EventType { 21 | pub fn struct_ident(&self) -> Ident { 22 | format_ident!("{}", self.name.to_pascal_case()) 23 | } 24 | } 25 | 26 | impl ToTokens for Event { 27 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 28 | // discriminant 29 | let event_discm_ident = format_ident!("{}_EVENT_DISCM", self.0.name.to_shouty_snake_case()); 30 | // pre-image: "event:{EventName}" 31 | let discm = <[u8; 8]>::try_from( 32 | &Sha256::digest(format!("event:{}", self.0.name).as_bytes()).as_slice()[..8], 33 | ) 34 | .unwrap(); 35 | let discm_tokens: TokenStream = format!("{:?}", discm).parse().unwrap(); 36 | 37 | let struct_def = &self.0; 38 | 39 | let struct_ident = self.0.struct_ident(); 40 | let event_ident = format_ident!("{}Event", struct_ident); 41 | tokens.extend(quote! { 42 | pub const #event_discm_ident: [u8; 8] = #discm_tokens; 43 | 44 | #struct_def 45 | 46 | #[derive(Clone, Debug, PartialEq)] 47 | pub struct #event_ident(pub #struct_ident); 48 | 49 | impl BorshSerialize for #event_ident { 50 | fn serialize(&self, writer: &mut W) -> std::io::Result<()> { 51 | #event_discm_ident.serialize(writer)?; 52 | self.0.serialize(writer) 53 | } 54 | } 55 | 56 | impl #event_ident { 57 | pub fn deserialize(buf: &mut &[u8]) -> std::io::Result { 58 | let maybe_discm = <[u8; 8]>::deserialize(buf)?; 59 | if maybe_discm != #event_discm_ident { 60 | return Err( 61 | std::io::Error::new( 62 | std::io::ErrorKind::Other, format!("discm does not match. Expected: {:?}. Received: {:?}", #event_discm_ident, maybe_discm) 63 | ) 64 | ); 65 | } 66 | Ok(Self(#struct_ident::deserialize(buf)?)) 67 | } 68 | } 69 | }); 70 | } 71 | } 72 | 73 | impl ToTokens for EventType { 74 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 75 | let struct_ident = self.struct_ident(); 76 | let struct_fields = &self.fields; 77 | tokens.extend(quote! { 78 | #[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize)] 79 | pub struct #struct_ident { 80 | #(pub #struct_fields),* 81 | } 82 | }); 83 | } 84 | } 85 | 86 | #[cfg(test)] 87 | mod tests { 88 | use super::*; 89 | use crate::idl_format::anchor::typedefs::TypedefFieldType; 90 | 91 | #[test] 92 | fn test_event_type_to_tokens_with_pub_fields() { 93 | // Define some fields for the EventType struct. 94 | let field1 = TypedefField { 95 | name: "field1".to_string(), 96 | r#type: TypedefFieldType::PrimitiveOrPubkey("u32".into()), 97 | }; 98 | 99 | let field2 = TypedefField { 100 | name: "field2".to_string(), 101 | r#type: TypedefFieldType::PrimitiveOrPubkey("String".into()), 102 | }; 103 | 104 | // Create an EventType with the fields. 105 | let event_type = EventType { 106 | name: "TestEvent".to_string(), 107 | fields: vec![field1, field2], 108 | }; 109 | 110 | // Generate the tokens. 111 | let mut tokens = proc_macro2::TokenStream::new(); 112 | event_type.to_tokens(&mut tokens); 113 | 114 | // Convert the tokens to a string for comparison. 115 | let generated_code = tokens.to_string(); 116 | 117 | // Check that the generated code includes "pub" for each field. 118 | assert!(generated_code.contains("pub field1 : u32")); 119 | assert!(generated_code.contains("pub field2 : String")); 120 | 121 | // Check that the struct name is correct. 122 | assert!(generated_code.contains("pub struct TestEvent")); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /solores/src/idl_format/anchor/events/mod.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::{quote, ToTokens}; 3 | 4 | use crate::idl_format::IdlCodegenModule; 5 | 6 | mod event; 7 | pub use event::*; 8 | 9 | pub struct EventsCodegenModule<'a>(pub &'a [Event]); 10 | 11 | impl IdlCodegenModule for EventsCodegenModule<'_> { 12 | fn name(&self) -> &str { 13 | "events" 14 | } 15 | 16 | fn gen_head(&self) -> TokenStream { 17 | let mut res = quote! { 18 | use borsh::{BorshDeserialize, BorshSerialize}; 19 | }; 20 | let mut has_pubkey = false; 21 | let mut has_defined = false; 22 | for a in self.0 { 23 | for field in &a.0.fields { 24 | if field.r#type.is_or_has_pubkey() && !has_pubkey { 25 | has_pubkey = true; 26 | res.extend(quote! { 27 | use solana_program::pubkey::Pubkey; 28 | }); 29 | } 30 | if field.r#type.is_or_has_defined() && !has_defined { 31 | has_defined = true; 32 | res.extend(quote! { 33 | use crate::*; 34 | }) 35 | } 36 | } 37 | if has_defined && has_pubkey { 38 | break; 39 | } 40 | } 41 | res 42 | } 43 | 44 | fn gen_body(&self) -> TokenStream { 45 | self.0.iter().map(|e| e.into_token_stream()).collect() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /solores/src/idl_format/anchor/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | use heck::ToPascalCase; 2 | use proc_macro2::TokenStream; 3 | use quote::{format_ident, quote, ToTokens}; 4 | 5 | use crate::idl_format::IdlCodegenModule; 6 | 7 | mod instruction; 8 | pub use instruction::*; 9 | 10 | pub struct IxCodegenModule<'a> { 11 | pub program_name: &'a str, 12 | pub instructions: &'a [NamedInstruction], 13 | } 14 | 15 | impl IdlCodegenModule for IxCodegenModule<'_> { 16 | fn name(&self) -> &str { 17 | "instructions" 18 | } 19 | 20 | fn gen_head(&self) -> TokenStream { 21 | let mut res = quote! {}; 22 | let has_args = self 23 | .instructions 24 | .iter() 25 | .map(|ix| ix.has_ix_args()) 26 | .any(|b| b); 27 | if has_args { 28 | res.extend(quote! { 29 | use borsh::{BorshDeserialize, BorshSerialize}; 30 | }); 31 | } 32 | let has_accounts = self 33 | .instructions 34 | .iter() 35 | .map(|ix| ix.has_accounts()) 36 | .any(|b| b); 37 | 38 | let mut solana_program_imports = if has_accounts { 39 | quote! { 40 | account_info::AccountInfo, 41 | entrypoint::ProgramResult, 42 | instruction::{AccountMeta, Instruction}, 43 | program::{invoke, invoke_signed}, 44 | pubkey::Pubkey, 45 | } 46 | } else { 47 | quote! { 48 | entrypoint::ProgramResult, 49 | instruction::Instruction, 50 | program::{invoke, invoke_signed}, 51 | pubkey::Pubkey, 52 | } 53 | }; 54 | let has_privileged_accounts = self 55 | .instructions 56 | .iter() 57 | .map(|ix| ix.has_privileged_accounts()) 58 | .any(|b| b); 59 | if has_privileged_accounts { 60 | solana_program_imports.extend(quote! { 61 | program_error::ProgramError, 62 | }); 63 | } 64 | 65 | res.extend(quote! { 66 | use solana_program::{#solana_program_imports}; 67 | use std::io::Read; 68 | }); 69 | let has_defined_type = self 70 | .instructions 71 | .iter() 72 | .map(|ix| ix.args_has_defined_type()) 73 | .any(|b| b); 74 | if has_defined_type { 75 | res.extend(quote! { 76 | use crate::*; 77 | }); 78 | } 79 | 80 | // program ix enum 81 | let program_ix_enum_ident = 82 | format_ident!("{}ProgramIx", self.program_name.to_pascal_case()); 83 | let program_ix_enum_variants = self.instructions.iter().map(enum_variant); 84 | let serialize_variant_match_arms = 85 | self.instructions.iter().map(serialize_variant_match_arm); 86 | let deserialize_variant_match_arms = 87 | self.instructions.iter().map(deserialize_variant_match_arm); 88 | 89 | res.extend(quote! { 90 | #[derive(Clone, Debug, PartialEq)] 91 | pub enum #program_ix_enum_ident { 92 | #(#program_ix_enum_variants),* 93 | } 94 | 95 | impl #program_ix_enum_ident { 96 | pub fn deserialize(buf: &[u8]) -> std::io::Result { 97 | let mut reader = buf; 98 | let mut maybe_discm = [0u8; 8]; 99 | reader.read_exact(&mut maybe_discm)?; 100 | match maybe_discm { 101 | #(#deserialize_variant_match_arms),*, 102 | _ => Err( 103 | std::io::Error::new( 104 | std::io::ErrorKind::Other, format!("discm {:?} not found", maybe_discm) 105 | ) 106 | ), 107 | } 108 | } 109 | 110 | pub fn serialize(&self, mut writer: W) -> std::io::Result<()> { 111 | match self { 112 | #(#serialize_variant_match_arms),*, 113 | } 114 | } 115 | 116 | pub fn try_to_vec(&self) -> std::io::Result> { 117 | let mut data = Vec::new(); 118 | self.serialize(&mut data)?; 119 | Ok(data) 120 | } 121 | } 122 | }); 123 | 124 | if has_accounts { 125 | res.extend(quote! { 126 | fn invoke_instruction<'info, A: Into<[AccountInfo<'info>; N]>, const N: usize>( 127 | ix: &Instruction, 128 | accounts: A, 129 | ) -> ProgramResult { 130 | let account_info: [AccountInfo<'info>; N] = accounts.into(); 131 | invoke(ix, &account_info) 132 | } 133 | fn invoke_instruction_signed<'info, A: Into<[AccountInfo<'info>; N]>, const N: usize>( 134 | ix: &Instruction, 135 | accounts: A, 136 | seeds: &[&[&[u8]]], 137 | ) -> ProgramResult { 138 | let account_info: [AccountInfo<'info>; N] = accounts.into(); 139 | invoke_signed(ix, &account_info, seeds) 140 | } 141 | }); 142 | } 143 | 144 | res 145 | } 146 | 147 | fn gen_body(&self) -> TokenStream { 148 | self.instructions 149 | .iter() 150 | .map(|e| e.into_token_stream()) 151 | .collect() 152 | } 153 | } 154 | 155 | pub fn enum_variant(ix: &NamedInstruction) -> TokenStream { 156 | let variant_ident = format_ident!("{}", ix.name.to_pascal_case()); 157 | let mut res = quote!( 158 | #variant_ident 159 | ); 160 | if ix.has_ix_args() { 161 | let ix_args_ident = ix.ix_args_ident(); 162 | res.extend(quote! { 163 | (#ix_args_ident) 164 | }) 165 | } 166 | res 167 | } 168 | 169 | pub fn serialize_variant_match_arm(ix: &NamedInstruction) -> TokenStream { 170 | let variant_ident = format_ident!("{}", ix.name.to_pascal_case()); 171 | let discm_ident = ix.discm_ident(); 172 | let serialize_expr = if ix.has_ix_args() { 173 | quote! {{ 174 | writer.write_all(&#discm_ident)?; 175 | args.serialize(&mut writer) 176 | }} 177 | } else { 178 | quote! { writer.write_all(&#discm_ident) } 179 | }; 180 | let mut left_matched = quote! { Self::#variant_ident }; 181 | if ix.has_ix_args() { 182 | left_matched.extend(quote! { (args) }); 183 | } 184 | quote! { 185 | #left_matched => #serialize_expr 186 | } 187 | } 188 | 189 | pub fn deserialize_variant_match_arm(ix: &NamedInstruction) -> TokenStream { 190 | let variant_ident = format_ident!("{}", ix.name.to_pascal_case()); 191 | let discm_ident = ix.discm_ident(); 192 | let mut variant_expr = quote! { 193 | Self::#variant_ident 194 | }; 195 | if ix.has_ix_args() { 196 | let ix_args_ident = ix.ix_args_ident(); 197 | variant_expr.extend(quote! { 198 | (#ix_args_ident::deserialize(&mut reader)?) 199 | }) 200 | } 201 | quote! { 202 | #discm_ident => Ok(#variant_expr) 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /solores/src/idl_format/anchor/mod.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | use toml::{map::Map, Value}; 3 | 4 | use crate::write_cargotoml::{ 5 | DependencyValue, FeaturesDependencyValue, OptionalDependencyValue, BORSH_CRATE, BYTEMUCK_CRATE, 6 | NUM_DERIVE_CRATE, NUM_TRAITS_CRATE, SERDE_CRATE, SOLANA_PROGRAM_CRATE, THISERROR_CRATE, 7 | }; 8 | 9 | use super::{IdlCodegenModule, IdlFormat}; 10 | 11 | use self::{ 12 | accounts::{AccountsCodegenModule, NamedAccount}, 13 | errors::{ErrorEnumVariant, ErrorsCodegenModule}, 14 | events::{Event, EventsCodegenModule}, 15 | instructions::{IxCodegenModule, NamedInstruction}, 16 | typedefs::{NamedType, TypedefsCodegenModule}, 17 | }; 18 | 19 | pub mod accounts; 20 | pub mod errors; 21 | pub mod events; 22 | pub mod instructions; 23 | pub mod typedefs; 24 | 25 | #[derive(Deserialize)] 26 | pub struct AnchorIdl { 27 | pub name: String, 28 | pub version: String, 29 | pub metadata: Option, 30 | pub accounts: Option>, 31 | pub types: Option>, 32 | pub instructions: Option>, 33 | pub errors: Option>, 34 | pub events: Option>, 35 | } 36 | 37 | #[derive(Deserialize)] 38 | pub struct Metadata { 39 | pub address: String, 40 | } 41 | 42 | impl IdlFormat for AnchorIdl { 43 | fn program_name(&self) -> &str { 44 | &self.name 45 | } 46 | 47 | fn program_version(&self) -> &str { 48 | &self.version 49 | } 50 | 51 | fn program_address(&self) -> Option<&str> { 52 | self.metadata.as_ref().map(|m| m.address.as_ref()) 53 | } 54 | 55 | /// Anchor IDLs dont seem to have an identifier, 56 | /// assume unindentified IDLs are anchor by default. 57 | /// -> Make sure to try deserializing Anchor last 58 | fn is_correct_idl_format(&self) -> bool { 59 | true 60 | } 61 | 62 | fn modules<'me>(&'me self, args: &'me crate::Args) -> Vec> { 63 | let mut res: Vec> = Vec::new(); 64 | if let Some(v) = &self.accounts { 65 | res.push(Box::new(AccountsCodegenModule { 66 | cli_args: args, 67 | named_accounts: v, 68 | })); 69 | } 70 | if let Some(v) = &self.r#types { 71 | res.push(Box::new(TypedefsCodegenModule { 72 | cli_args: args, 73 | named_types: v, 74 | })); 75 | } 76 | if let Some(v) = &self.instructions { 77 | res.push(Box::new(IxCodegenModule { 78 | program_name: self.program_name(), 79 | instructions: v, 80 | })); 81 | } 82 | if let Some(v) = &self.errors { 83 | res.push(Box::new(ErrorsCodegenModule { 84 | program_name: self.program_name(), 85 | variants: v, 86 | })); 87 | } 88 | if let Some(v) = &self.events { 89 | res.push(Box::new(EventsCodegenModule(v))); 90 | } 91 | res 92 | } 93 | 94 | fn dependencies(&self, args: &crate::Args) -> Map { 95 | let mut map = Map::new(); 96 | map.insert(BORSH_CRATE.into(), DependencyValue(&args.borsh_vers).into()); 97 | if !args.zero_copy.is_empty() { 98 | map.insert( 99 | BYTEMUCK_CRATE.into(), 100 | FeaturesDependencyValue { 101 | dependency: DependencyValue(&args.bytemuck_vers), 102 | features: vec!["derive".into()], 103 | } 104 | .into(), 105 | ); 106 | } 107 | map.insert( 108 | SOLANA_PROGRAM_CRATE.into(), 109 | DependencyValue(&args.solana_program_vers).into(), 110 | ); 111 | map.insert( 112 | SERDE_CRATE.into(), 113 | OptionalDependencyValue(DependencyValue(&args.serde_vers)).into(), 114 | ); 115 | if self.errors.is_some() { 116 | map.insert( 117 | THISERROR_CRATE.into(), 118 | DependencyValue(&args.thiserror_vers).into(), 119 | ); 120 | map.insert( 121 | NUM_DERIVE_CRATE.into(), 122 | DependencyValue(&args.num_derive_vers).into(), 123 | ); 124 | map.insert( 125 | NUM_TRAITS_CRATE.into(), 126 | DependencyValue(&args.num_traits_vers).into(), 127 | ); 128 | } 129 | map 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /solores/src/idl_format/anchor/typedefs/mod.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | 4 | use crate::idl_format::IdlCodegenModule; 5 | 6 | mod typedef; 7 | pub use typedef::*; 8 | 9 | pub struct TypedefsCodegenModule<'a> { 10 | pub cli_args: &'a crate::Args, 11 | pub named_types: &'a [NamedType], 12 | } 13 | 14 | impl IdlCodegenModule for TypedefsCodegenModule<'_> { 15 | fn name(&self) -> &str { 16 | "typedefs" 17 | } 18 | 19 | fn gen_head(&self) -> TokenStream { 20 | let mut res = quote! { 21 | use borsh::{BorshDeserialize, BorshSerialize}; 22 | }; 23 | for a in self.named_types { 24 | if self.cli_args.zero_copy.iter().any(|e| e == &a.name) { 25 | res.extend(quote! { 26 | use bytemuck::{Pod, Zeroable}; 27 | }); 28 | break; 29 | } 30 | } 31 | for t in self.named_types { 32 | if t.r#type.has_pubkey_field() { 33 | res.extend(quote! { 34 | use solana_program::pubkey::Pubkey; 35 | }); 36 | break; 37 | } 38 | } 39 | res 40 | } 41 | 42 | fn gen_body(&self) -> TokenStream { 43 | self.named_types 44 | .iter() 45 | .map(|e| e.to_token_stream(self.cli_args)) 46 | .collect() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /solores/src/idl_format/bincode/errors/error.rs: -------------------------------------------------------------------------------- 1 | use heck::ToPascalCase; 2 | use proc_macro2::{Span, TokenStream}; 3 | use quote::{format_ident, quote, ToTokens}; 4 | use serde::Deserialize; 5 | use syn::LitInt; 6 | 7 | #[derive(Deserialize)] 8 | pub struct ErrorEnumVariant { 9 | code: u32, 10 | name: String, 11 | msg: String, 12 | } 13 | 14 | impl ToTokens for ErrorEnumVariant { 15 | fn to_tokens(&self, tokens: &mut TokenStream) { 16 | let variant_ident = format_ident!("{}", self.name.to_pascal_case()); 17 | let msg = &self.msg; 18 | let code_literal = LitInt::new(&self.code.to_string(), Span::call_site()); 19 | tokens.extend(quote! { 20 | #[error(#msg)] 21 | #variant_ident = #code_literal, 22 | }) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /solores/src/idl_format/bincode/errors/mod.rs: -------------------------------------------------------------------------------- 1 | use heck::ToPascalCase; 2 | use proc_macro2::TokenStream; 3 | use quote::{format_ident, quote, ToTokens}; 4 | 5 | use crate::idl_format::IdlCodegenModule; 6 | 7 | mod error; 8 | pub use error::*; 9 | 10 | pub struct ErrorsCodegenModule<'a> { 11 | pub program_name: &'a str, 12 | pub variants: &'a [ErrorEnumVariant], 13 | } 14 | 15 | impl IdlCodegenModule for ErrorsCodegenModule<'_> { 16 | fn name(&self) -> &str { 17 | "errors" 18 | } 19 | 20 | fn gen_head(&self) -> TokenStream { 21 | quote! { 22 | use solana_program::{ 23 | decode_error::DecodeError, 24 | msg, 25 | program_error::{PrintProgramError, ProgramError}, 26 | }; 27 | use thiserror::Error; 28 | } 29 | } 30 | 31 | fn gen_body(&self) -> TokenStream { 32 | let error_enum_variants: TokenStream = self 33 | .variants 34 | .iter() 35 | .map(|e| e.into_token_stream()) 36 | .collect(); 37 | 38 | let error_enum_ident_str = format!("{}Error", self.program_name.to_pascal_case()); 39 | let error_enum_ident = format_ident!("{}", &error_enum_ident_str); 40 | quote! { 41 | #[derive(Clone, Copy, Debug, Eq, Error, num_derive::FromPrimitive, PartialEq)] 42 | pub enum #error_enum_ident { 43 | #error_enum_variants 44 | } 45 | 46 | impl From<#error_enum_ident> for ProgramError { 47 | fn from(e: #error_enum_ident) -> Self { 48 | ProgramError::Custom(e as u32) 49 | } 50 | } 51 | 52 | impl DecodeError for #error_enum_ident { 53 | fn type_of() -> &'static str { 54 | #error_enum_ident_str 55 | } 56 | } 57 | 58 | impl PrintProgramError for #error_enum_ident { 59 | fn print(&self) 60 | where 61 | E: 'static 62 | + std::error::Error 63 | + DecodeError 64 | + PrintProgramError 65 | + num_traits::FromPrimitive, 66 | { 67 | msg!(&self.to_string()); 68 | } 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /solores/src/idl_format/bincode/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | use heck::ToPascalCase; 2 | use proc_macro2::{Ident, TokenStream}; 3 | use quote::{format_ident, quote, ToTokens}; 4 | 5 | use crate::idl_format::IdlCodegenModule; 6 | 7 | mod instruction; 8 | pub use instruction::*; 9 | 10 | pub struct IxCodegenModule<'a> { 11 | pub program_name: &'a str, 12 | pub instructions: &'a [NamedInstruction], 13 | } 14 | 15 | impl<'a> IxCodegenModule<'a> { 16 | pub fn program_ix_enum_ident(&self) -> Ident { 17 | format_ident!("{}ProgramIx", self.program_name.to_pascal_case()) 18 | } 19 | } 20 | 21 | impl IdlCodegenModule for IxCodegenModule<'_> { 22 | fn name(&self) -> &str { 23 | "instructions" 24 | } 25 | 26 | fn gen_head(&self) -> TokenStream { 27 | let mut res = quote! {}; 28 | let has_accounts = self 29 | .instructions 30 | .iter() 31 | .map(|ix| ix.has_accounts()) 32 | .any(|b| b); 33 | let mut solana_program_imports = if has_accounts { 34 | quote! { 35 | account_info::AccountInfo, 36 | entrypoint::ProgramResult, 37 | instruction::{AccountMeta, Instruction}, 38 | program::{invoke, invoke_signed}, 39 | pubkey::Pubkey, 40 | } 41 | } else { 42 | quote! { 43 | entrypoint::ProgramResult, 44 | instruction::Instruction, 45 | program::{invoke, invoke_signed}, 46 | pubkey::Pubkey, 47 | } 48 | }; 49 | let has_privileged_accounts = self 50 | .instructions 51 | .iter() 52 | .map(|ix| ix.has_privileged_accounts()) 53 | .any(|b| b); 54 | if has_privileged_accounts { 55 | solana_program_imports.extend(quote! { 56 | program_error::ProgramError, 57 | }); 58 | } 59 | res.extend(quote! { 60 | use serde::{Serialize, Deserialize}; 61 | use solana_program::{#solana_program_imports}; 62 | }); 63 | 64 | let has_defined_type = self 65 | .instructions 66 | .iter() 67 | .map(|ix| ix.args_has_defined_type()) 68 | .any(|b| b); 69 | if has_defined_type { 70 | res.extend(quote! { 71 | use crate::*; 72 | }); 73 | } 74 | 75 | // program ix enum 76 | let program_ix_enum_ident = self.program_ix_enum_ident(); 77 | let program_ix_enum_variants = self.instructions.iter().map(enum_variant); 78 | 79 | res.extend(quote! { 80 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] 81 | pub enum #program_ix_enum_ident { 82 | #(#program_ix_enum_variants),* 83 | } 84 | }); 85 | 86 | if has_accounts { 87 | res.extend(quote! { 88 | fn invoke_instruction<'info, A: Into<[AccountInfo<'info>; N]>, const N: usize>( 89 | ix: &Instruction, 90 | accounts: A, 91 | ) -> ProgramResult { 92 | let account_info: [AccountInfo<'info>; N] = accounts.into(); 93 | invoke(ix, &account_info) 94 | } 95 | fn invoke_instruction_signed<'info, A: Into<[AccountInfo<'info>; N]>, const N: usize>( 96 | ix: &Instruction, 97 | accounts: A, 98 | seeds: &[&[&[u8]]], 99 | ) -> ProgramResult { 100 | let account_info: [AccountInfo<'info>; N] = accounts.into(); 101 | invoke_signed(ix, &account_info, seeds) 102 | } 103 | }); 104 | } 105 | 106 | res 107 | } 108 | 109 | fn gen_body(&self) -> TokenStream { 110 | let program_ix_enum_ident = self.program_ix_enum_ident(); 111 | self.instructions 112 | .iter() 113 | .enumerate() 114 | .map(|(i, ix)| { 115 | NamedInstructionFull { 116 | ix, 117 | index: i, 118 | program_ix_enum_ident: &program_ix_enum_ident, 119 | } 120 | .into_token_stream() 121 | }) 122 | .collect() 123 | } 124 | } 125 | 126 | pub fn enum_variant(ix: &NamedInstruction) -> TokenStream { 127 | let variant_ident = ix.enum_variant_ident(); 128 | let mut res = quote!( 129 | #variant_ident 130 | ); 131 | if ix.has_ix_args() { 132 | let ix_args_ident = ix.ix_args_ident(); 133 | res.extend(quote! { 134 | (#ix_args_ident) 135 | }) 136 | } 137 | res 138 | } 139 | -------------------------------------------------------------------------------- /solores/src/idl_format/bincode/mod.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | use toml::{map::Map, Value}; 3 | 4 | use crate::write_cargotoml::{ 5 | DependencyValue, NUM_DERIVE_CRATE, NUM_TRAITS_CRATE, SERDE_CRATE, SOLANA_PROGRAM_CRATE, 6 | THISERROR_CRATE, 7 | }; 8 | 9 | use super::{IdlCodegenModule, IdlFormat}; 10 | 11 | use self::{ 12 | errors::{ErrorEnumVariant, ErrorsCodegenModule}, 13 | instructions::{IxCodegenModule, NamedInstruction}, 14 | typedefs::{NamedType, TypedefsCodegenModule}, 15 | }; 16 | 17 | pub mod errors; 18 | pub mod instructions; 19 | pub mod typedefs; 20 | 21 | #[derive(Deserialize)] 22 | pub struct BincodeIdl { 23 | pub name: String, 24 | pub version: String, 25 | pub metadata: Metadata, 26 | pub instructions: Option>, 27 | pub types: Option>, 28 | pub errors: Option>, 29 | } 30 | 31 | #[derive(Deserialize)] 32 | pub struct Metadata { 33 | pub address: String, 34 | pub origin: String, 35 | } 36 | 37 | impl IdlFormat for BincodeIdl { 38 | fn program_name(&self) -> &str { 39 | &self.name 40 | } 41 | 42 | fn program_version(&self) -> &str { 43 | &self.version 44 | } 45 | 46 | fn program_address(&self) -> Option<&str> { 47 | Some(&self.metadata.address) 48 | } 49 | 50 | fn is_correct_idl_format(&self) -> bool { 51 | self.metadata.origin == "bincode" 52 | } 53 | 54 | fn modules<'me>(&'me self, args: &'me crate::Args) -> Vec> { 55 | let mut res: Vec> = Vec::new(); 56 | if let Some(v) = &self.r#types { 57 | res.push(Box::new(TypedefsCodegenModule { 58 | cli_args: args, 59 | named_types: v, 60 | })); 61 | } 62 | if let Some(v) = &self.instructions { 63 | res.push(Box::new(IxCodegenModule { 64 | program_name: self.program_name(), 65 | instructions: v, 66 | })); 67 | } 68 | if let Some(v) = &self.errors { 69 | res.push(Box::new(ErrorsCodegenModule { 70 | program_name: self.program_name(), 71 | variants: v, 72 | })); 73 | } 74 | res 75 | } 76 | 77 | fn dependencies(&self, args: &crate::Args) -> Map { 78 | let mut map = Map::new(); 79 | map.insert( 80 | SOLANA_PROGRAM_CRATE.into(), 81 | DependencyValue(&args.solana_program_vers).into(), 82 | ); 83 | map.insert(SERDE_CRATE.into(), DependencyValue(&args.serde_vers).into()); 84 | if self.errors.is_some() { 85 | map.insert( 86 | THISERROR_CRATE.into(), 87 | DependencyValue(&args.thiserror_vers).into(), 88 | ); 89 | map.insert( 90 | NUM_DERIVE_CRATE.into(), 91 | DependencyValue(&args.num_derive_vers).into(), 92 | ); 93 | map.insert( 94 | NUM_TRAITS_CRATE.into(), 95 | DependencyValue(&args.num_traits_vers).into(), 96 | ); 97 | } 98 | map 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /solores/src/idl_format/bincode/typedefs/mod.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | 4 | mod typedef; 5 | pub use typedef::*; 6 | 7 | use crate::idl_format::IdlCodegenModule; 8 | 9 | pub struct TypedefsCodegenModule<'a> { 10 | pub cli_args: &'a crate::Args, 11 | pub named_types: &'a [NamedType], 12 | } 13 | 14 | impl IdlCodegenModule for TypedefsCodegenModule<'_> { 15 | fn name(&self) -> &str { 16 | "typedefs" 17 | } 18 | 19 | fn gen_head(&self) -> TokenStream { 20 | let mut res = quote! { 21 | use serde::{Deserialize, Serialize}; 22 | }; 23 | for a in self.named_types { 24 | if self.cli_args.zero_copy.iter().any(|e| e == &a.name) { 25 | res.extend(quote! { 26 | use bytemuck::{Pod, Zeroable}; 27 | }); 28 | break; 29 | } 30 | } 31 | for t in self.named_types { 32 | if t.r#type.has_pubkey_field() { 33 | res.extend(quote! { 34 | use solana_program::pubkey::Pubkey; 35 | }); 36 | break; 37 | } 38 | } 39 | res 40 | } 41 | 42 | fn gen_body(&self) -> TokenStream { 43 | self.named_types 44 | .iter() 45 | .map(|e| e.to_token_stream(self.cli_args)) 46 | .collect() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /solores/src/idl_format/mod.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("./README.md")] 2 | 3 | use proc_macro2::TokenStream; 4 | use toml::{map::Map, Value}; 5 | 6 | pub mod anchor; 7 | pub mod bincode; 8 | pub mod shank; 9 | 10 | pub trait IdlCodegenModule { 11 | /// The module file's name e.g. "errors" 12 | fn name(&self) -> &str; 13 | 14 | /// Generate the headers to prefix the module file with. 15 | /// Typically import statements 16 | fn gen_head(&self) -> TokenStream; 17 | 18 | /// Generate the main body content of the module file 19 | fn gen_body(&self) -> TokenStream; 20 | } 21 | 22 | pub trait IdlFormat { 23 | fn program_name(&self) -> &str; 24 | 25 | fn program_version(&self) -> &str; 26 | 27 | fn program_address(&self) -> Option<&str>; 28 | 29 | fn is_correct_idl_format(&self) -> bool; 30 | 31 | fn dependencies(&self, args: &crate::Args) -> Map; 32 | 33 | fn modules<'me>(&'me self, args: &'me crate::Args) -> Vec>; 34 | } 35 | -------------------------------------------------------------------------------- /solores/src/idl_format/shank/accounts.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | 4 | use crate::idl_format::IdlCodegenModule; 5 | 6 | use super::typedefs::NamedType; 7 | 8 | pub struct AccountsCodegenModule<'a> { 9 | pub cli_args: &'a crate::Args, 10 | pub named_types: &'a [NamedType], 11 | } 12 | 13 | impl IdlCodegenModule for AccountsCodegenModule<'_> { 14 | fn name(&self) -> &str { 15 | "accounts" 16 | } 17 | 18 | fn gen_head(&self) -> TokenStream { 19 | let mut res = quote! { 20 | use borsh::{BorshDeserialize, BorshSerialize}; 21 | }; 22 | for a in self.named_types { 23 | if self.cli_args.zero_copy.iter().any(|e| e == &a.name) { 24 | res.extend(quote! { 25 | use bytemuck::{Pod, Zeroable}; 26 | }); 27 | break; 28 | } 29 | } 30 | let mut has_pubkey = false; 31 | let mut has_defined = false; 32 | for a in self.named_types { 33 | if a.r#type.has_pubkey_field() && !has_pubkey { 34 | has_pubkey = true; 35 | res.extend(quote! { 36 | use solana_program::pubkey::Pubkey; 37 | }); 38 | } 39 | if a.r#type.has_defined_field() && !has_defined { 40 | has_defined = true; 41 | res.extend(quote! { 42 | use crate::*; 43 | }) 44 | } 45 | if has_defined && has_pubkey { 46 | break; 47 | } 48 | } 49 | res 50 | } 51 | 52 | fn gen_body(&self) -> TokenStream { 53 | self.named_types 54 | .iter() 55 | .map(|e| e.to_token_stream(self.cli_args)) 56 | .collect() 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /solores/src/idl_format/shank/errors/error.rs: -------------------------------------------------------------------------------- 1 | use heck::ToPascalCase; 2 | use proc_macro2::{Span, TokenStream}; 3 | use quote::{format_ident, quote, ToTokens}; 4 | use serde::Deserialize; 5 | use syn::LitInt; 6 | 7 | #[derive(Deserialize)] 8 | pub struct ErrorEnumVariant { 9 | code: u32, 10 | name: String, 11 | msg: String, 12 | } 13 | 14 | impl ToTokens for ErrorEnumVariant { 15 | fn to_tokens(&self, tokens: &mut TokenStream) { 16 | let variant_ident = format_ident!("{}", self.name.to_pascal_case()); 17 | let msg = &self.msg; 18 | let code_literal = LitInt::new(&self.code.to_string(), Span::call_site()); 19 | tokens.extend(quote! { 20 | #[error(#msg)] 21 | #variant_ident = #code_literal, 22 | }) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /solores/src/idl_format/shank/errors/mod.rs: -------------------------------------------------------------------------------- 1 | use heck::ToPascalCase; 2 | use proc_macro2::TokenStream; 3 | use quote::{format_ident, quote, ToTokens}; 4 | 5 | use crate::idl_format::IdlCodegenModule; 6 | 7 | mod error; 8 | pub use error::*; 9 | 10 | pub struct ErrorsCodegenModule<'a> { 11 | pub program_name: &'a str, 12 | pub variants: &'a [ErrorEnumVariant], 13 | } 14 | 15 | impl IdlCodegenModule for ErrorsCodegenModule<'_> { 16 | fn name(&self) -> &str { 17 | "errors" 18 | } 19 | 20 | fn gen_head(&self) -> TokenStream { 21 | quote! { 22 | use solana_program::{ 23 | decode_error::DecodeError, 24 | msg, 25 | program_error::{PrintProgramError, ProgramError}, 26 | }; 27 | use thiserror::Error; 28 | } 29 | } 30 | 31 | fn gen_body(&self) -> TokenStream { 32 | let error_enum_variants: TokenStream = self 33 | .variants 34 | .iter() 35 | .map(|e| e.into_token_stream()) 36 | .collect(); 37 | 38 | let error_enum_ident_str = format!("{}Error", self.program_name.to_pascal_case()); 39 | let error_enum_ident = format_ident!("{}", &error_enum_ident_str); 40 | quote! { 41 | #[derive(Clone, Copy, Debug, Eq, Error, num_derive::FromPrimitive, PartialEq)] 42 | pub enum #error_enum_ident { 43 | #error_enum_variants 44 | } 45 | 46 | impl From<#error_enum_ident> for ProgramError { 47 | fn from(e: #error_enum_ident) -> Self { 48 | ProgramError::Custom(e as u32) 49 | } 50 | } 51 | 52 | impl DecodeError for #error_enum_ident { 53 | fn type_of() -> &'static str { 54 | #error_enum_ident_str 55 | } 56 | } 57 | 58 | impl PrintProgramError for #error_enum_ident { 59 | fn print(&self) 60 | where 61 | E: 'static 62 | + std::error::Error 63 | + DecodeError 64 | + PrintProgramError 65 | + num_traits::FromPrimitive, 66 | { 67 | msg!(&self.to_string()); 68 | } 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /solores/src/idl_format/shank/instructions/mod.rs: -------------------------------------------------------------------------------- 1 | use heck::ToPascalCase; 2 | use proc_macro2::TokenStream; 3 | use quote::{format_ident, quote, ToTokens}; 4 | 5 | use crate::idl_format::IdlCodegenModule; 6 | 7 | mod instruction; 8 | pub use instruction::*; 9 | 10 | pub struct IxCodegenModule<'a> { 11 | pub program_name: &'a str, 12 | pub instructions: &'a [NamedInstruction], 13 | } 14 | 15 | impl IdlCodegenModule for IxCodegenModule<'_> { 16 | fn name(&self) -> &str { 17 | "instructions" 18 | } 19 | 20 | fn gen_head(&self) -> TokenStream { 21 | let mut res = quote! {}; 22 | let has_args = self 23 | .instructions 24 | .iter() 25 | .map(|ix| ix.has_ix_args()) 26 | .any(|b| b); 27 | if has_args { 28 | res.extend(quote! { 29 | use borsh::{BorshDeserialize, BorshSerialize}; 30 | }); 31 | } 32 | let has_accounts = self 33 | .instructions 34 | .iter() 35 | .map(|ix| ix.has_accounts()) 36 | .any(|b| b); 37 | let mut solana_program_imports = if has_accounts { 38 | quote! { 39 | account_info::AccountInfo, 40 | entrypoint::ProgramResult, 41 | instruction::{AccountMeta, Instruction}, 42 | program::{invoke, invoke_signed}, 43 | pubkey::Pubkey, 44 | } 45 | } else { 46 | quote! { 47 | entrypoint::ProgramResult, 48 | instruction::Instruction, 49 | program::{invoke, invoke_signed}, 50 | pubkey::Pubkey, 51 | } 52 | }; 53 | let has_privileged_accounts = self 54 | .instructions 55 | .iter() 56 | .map(|ix| ix.has_privileged_accounts()) 57 | .any(|b| b); 58 | if has_privileged_accounts { 59 | solana_program_imports.extend(quote! { 60 | program_error::ProgramError, 61 | }); 62 | } 63 | res.extend(quote! { 64 | use solana_program::{#solana_program_imports}; 65 | use std::io::Read; 66 | }); 67 | let has_defined_type = self 68 | .instructions 69 | .iter() 70 | .map(|ix| ix.args_has_defined_type()) 71 | .any(|b| b); 72 | if has_defined_type { 73 | res.extend(quote! { 74 | use crate::*; 75 | }); 76 | } 77 | 78 | // program ix enum 79 | let program_ix_enum_ident = 80 | format_ident!("{}ProgramIx", self.program_name.to_pascal_case()); 81 | let program_ix_enum_variants = self.instructions.iter().map(enum_variant); 82 | let serialize_variant_match_arms = 83 | self.instructions.iter().map(serialize_variant_match_arm); 84 | let deserialize_variant_match_arms = 85 | self.instructions.iter().map(deserialize_variant_match_arm); 86 | 87 | res.extend(quote! { 88 | #[derive(Clone, Debug, PartialEq)] 89 | pub enum #program_ix_enum_ident { 90 | #(#program_ix_enum_variants),* 91 | } 92 | 93 | impl #program_ix_enum_ident { 94 | pub fn deserialize(buf: &[u8]) -> std::io::Result { 95 | let mut reader = buf; 96 | let mut maybe_discm_buf = [0u8; 1]; 97 | reader.read_exact(&mut maybe_discm_buf)?; 98 | let maybe_discm = maybe_discm_buf[0]; 99 | match maybe_discm { 100 | #(#deserialize_variant_match_arms),*, 101 | _ => Err( 102 | std::io::Error::new( 103 | std::io::ErrorKind::Other, format!("discm {:?} not found", maybe_discm) 104 | ) 105 | ), 106 | } 107 | } 108 | 109 | pub fn serialize(&self, mut writer: W) -> std::io::Result<()> { 110 | match self { 111 | #(#serialize_variant_match_arms),*, 112 | } 113 | } 114 | 115 | pub fn try_to_vec(&self) -> std::io::Result> { 116 | let mut data = Vec::new(); 117 | self.serialize(&mut data)?; 118 | Ok(data) 119 | } 120 | } 121 | }); 122 | 123 | if has_accounts { 124 | res.extend(quote! { 125 | fn invoke_instruction<'info, A: Into<[AccountInfo<'info>; N]>, const N: usize>( 126 | ix: &Instruction, 127 | accounts: A, 128 | ) -> ProgramResult { 129 | let account_info: [AccountInfo<'info>; N] = accounts.into(); 130 | invoke(ix, &account_info) 131 | } 132 | fn invoke_instruction_signed<'info, A: Into<[AccountInfo<'info>; N]>, const N: usize>( 133 | ix: &Instruction, 134 | accounts: A, 135 | seeds: &[&[&[u8]]], 136 | ) -> ProgramResult { 137 | let account_info: [AccountInfo<'info>; N] = accounts.into(); 138 | invoke_signed(ix, &account_info, seeds) 139 | } 140 | }); 141 | } 142 | 143 | res 144 | } 145 | 146 | fn gen_body(&self) -> TokenStream { 147 | self.instructions 148 | .iter() 149 | .map(|e| e.into_token_stream()) 150 | .collect() 151 | } 152 | } 153 | 154 | pub fn enum_variant(ix: &NamedInstruction) -> TokenStream { 155 | let variant_ident = format_ident!("{}", ix.name.to_pascal_case()); 156 | let mut res = quote!( 157 | #variant_ident 158 | ); 159 | if ix.has_ix_args() { 160 | let ix_args_ident = ix.ix_args_ident(); 161 | res.extend(quote! { 162 | (#ix_args_ident) 163 | }) 164 | } 165 | res 166 | } 167 | 168 | pub fn serialize_variant_match_arm(ix: &NamedInstruction) -> TokenStream { 169 | let variant_ident = format_ident!("{}", ix.name.to_pascal_case()); 170 | let discm_ident = ix.discm_ident(); 171 | let serialize_expr = if ix.has_ix_args() { 172 | quote! {{ 173 | writer.write_all(&[#discm_ident])?; 174 | args.serialize(&mut writer) 175 | }} 176 | } else { 177 | quote! { writer.write_all(&[#discm_ident]) } 178 | }; 179 | let mut left_matched = quote! { Self::#variant_ident }; 180 | if ix.has_ix_args() { 181 | left_matched.extend(quote! { (args) }); 182 | } 183 | quote! { 184 | #left_matched => #serialize_expr 185 | } 186 | } 187 | 188 | pub fn deserialize_variant_match_arm(ix: &NamedInstruction) -> TokenStream { 189 | let variant_ident = format_ident!("{}", ix.name.to_pascal_case()); 190 | let discm_ident = ix.discm_ident(); 191 | let mut variant_expr = quote! { 192 | Self::#variant_ident 193 | }; 194 | if ix.has_ix_args() { 195 | let ix_args_ident = ix.ix_args_ident(); 196 | variant_expr.extend(quote! { 197 | (#ix_args_ident::deserialize(&mut reader)?) 198 | }) 199 | } 200 | quote! { 201 | #discm_ident => Ok(#variant_expr) 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /solores/src/idl_format/shank/mod.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | use toml::{map::Map, Value}; 3 | 4 | use crate::write_cargotoml::{ 5 | DependencyValue, FeaturesDependencyValue, OptionalDependencyValue, BORSH_CRATE, BYTEMUCK_CRATE, 6 | NUM_DERIVE_CRATE, NUM_TRAITS_CRATE, SERDE_CRATE, SOLANA_PROGRAM_CRATE, THISERROR_CRATE, 7 | }; 8 | 9 | use super::{IdlCodegenModule, IdlFormat}; 10 | 11 | use self::{ 12 | accounts::AccountsCodegenModule, 13 | errors::{ErrorEnumVariant, ErrorsCodegenModule}, 14 | instructions::{IxCodegenModule, NamedInstruction}, 15 | typedefs::{NamedType, TypedefsCodegenModule}, 16 | }; 17 | 18 | pub mod accounts; 19 | pub mod errors; 20 | pub mod instructions; 21 | pub mod typedefs; 22 | 23 | #[derive(Deserialize)] 24 | pub struct ShankIdl { 25 | pub name: String, 26 | pub version: String, 27 | pub metadata: Metadata, 28 | pub accounts: Option>, 29 | pub types: Option>, 30 | pub instructions: Option>, 31 | pub errors: Option>, 32 | } 33 | 34 | #[derive(Deserialize)] 35 | pub struct Metadata { 36 | pub address: String, 37 | pub origin: String, 38 | } 39 | 40 | impl IdlFormat for ShankIdl { 41 | fn program_name(&self) -> &str { 42 | &self.name 43 | } 44 | 45 | fn program_version(&self) -> &str { 46 | &self.version 47 | } 48 | 49 | fn program_address(&self) -> Option<&str> { 50 | Some(&self.metadata.address) 51 | } 52 | 53 | fn is_correct_idl_format(&self) -> bool { 54 | self.metadata.origin == "shank" 55 | } 56 | 57 | fn modules<'me>(&'me self, args: &'me crate::Args) -> Vec> { 58 | let mut res: Vec> = Vec::new(); 59 | if let Some(v) = &self.accounts { 60 | res.push(Box::new(AccountsCodegenModule { 61 | cli_args: args, 62 | named_types: v, 63 | })); 64 | } 65 | if let Some(v) = &self.r#types { 66 | res.push(Box::new(TypedefsCodegenModule { 67 | cli_args: args, 68 | named_types: v, 69 | })); 70 | } 71 | if let Some(v) = &self.instructions { 72 | res.push(Box::new(IxCodegenModule { 73 | program_name: self.program_name(), 74 | instructions: v, 75 | })); 76 | } 77 | if let Some(v) = &self.errors { 78 | res.push(Box::new(ErrorsCodegenModule { 79 | program_name: self.program_name(), 80 | variants: v, 81 | })); 82 | } 83 | res 84 | } 85 | 86 | fn dependencies(&self, args: &crate::Args) -> Map { 87 | let mut map = Map::new(); 88 | map.insert(BORSH_CRATE.into(), DependencyValue(&args.borsh_vers).into()); 89 | if !args.zero_copy.is_empty() { 90 | map.insert( 91 | BYTEMUCK_CRATE.into(), 92 | FeaturesDependencyValue { 93 | dependency: DependencyValue(&args.bytemuck_vers), 94 | features: vec!["derive".into()], 95 | } 96 | .into(), 97 | ); 98 | } 99 | map.insert( 100 | SOLANA_PROGRAM_CRATE.into(), 101 | DependencyValue(&args.solana_program_vers).into(), 102 | ); 103 | map.insert( 104 | SERDE_CRATE.into(), 105 | OptionalDependencyValue(DependencyValue(&args.serde_vers)).into(), 106 | ); 107 | if self.errors.is_some() { 108 | map.insert( 109 | THISERROR_CRATE.into(), 110 | DependencyValue(&args.thiserror_vers).into(), 111 | ); 112 | map.insert( 113 | NUM_DERIVE_CRATE.into(), 114 | DependencyValue(&args.num_derive_vers).into(), 115 | ); 116 | map.insert( 117 | NUM_TRAITS_CRATE.into(), 118 | DependencyValue(&args.num_traits_vers).into(), 119 | ); 120 | } 121 | map 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /solores/src/idl_format/shank/typedefs/mod.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | 4 | use crate::idl_format::IdlCodegenModule; 5 | 6 | mod typedef; 7 | pub use typedef::*; 8 | 9 | pub struct TypedefsCodegenModule<'a> { 10 | pub cli_args: &'a crate::Args, 11 | pub named_types: &'a [NamedType], 12 | } 13 | 14 | impl IdlCodegenModule for TypedefsCodegenModule<'_> { 15 | fn name(&self) -> &str { 16 | "typedefs" 17 | } 18 | 19 | fn gen_head(&self) -> TokenStream { 20 | let mut res = quote! { 21 | use borsh::{BorshDeserialize, BorshSerialize}; 22 | }; 23 | for a in self.named_types { 24 | if self.cli_args.zero_copy.iter().any(|e| e == &a.name) { 25 | res.extend(quote! { 26 | use bytemuck::{Pod, Zeroable}; 27 | }); 28 | break; 29 | } 30 | } 31 | for t in self.named_types { 32 | if t.r#type.has_pubkey_field() { 33 | res.extend(quote! { 34 | use solana_program::pubkey::Pubkey; 35 | }); 36 | break; 37 | } 38 | } 39 | res 40 | } 41 | 42 | fn gen_body(&self) -> TokenStream { 43 | self.named_types 44 | .iter() 45 | .map(|e| e.to_token_stream(self.cli_args)) 46 | .collect() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /solores/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | use std::{ 4 | env, 5 | fs::{self, File, OpenOptions}, 6 | io::Seek, 7 | path::PathBuf, 8 | }; 9 | 10 | use clap::{command, Parser}; 11 | use idl_format::{bincode::BincodeIdl, IdlFormat}; 12 | 13 | use crate::idl_format::{anchor::AnchorIdl, shank::ShankIdl}; 14 | 15 | // Just make all mods pub to allow ppl to use the lib 16 | 17 | pub mod idl_format; 18 | pub mod utils; 19 | pub mod write_cargotoml; 20 | pub mod write_gitignore; 21 | pub mod write_src; 22 | 23 | use write_cargotoml::write_cargotoml; 24 | use write_gitignore::write_gitignore; 25 | use write_src::*; 26 | 27 | const DEFAULT_OUTPUT_CRATE_NAME_MSG: &str = "_interface"; 28 | const DEFAULT_PROGRAM_ID_MSG: &str = "program ID in IDL else system program ID if absent"; 29 | const RUST_LOG_ENV_VAR: &str = "RUST_LOG"; 30 | 31 | #[derive(Parser, Debug)] 32 | #[command(author, version, about, long_about = None)] 33 | pub struct Args { 34 | pub idl_path: PathBuf, 35 | 36 | #[arg( 37 | long, 38 | short, 39 | help = "directory to output generated crate to", 40 | default_value = "./" 41 | )] 42 | pub output_dir: PathBuf, 43 | 44 | #[arg( 45 | long, 46 | help = "output crate name", 47 | default_value = DEFAULT_OUTPUT_CRATE_NAME_MSG, 48 | )] 49 | pub output_crate_name: String, 50 | 51 | #[arg(long, short, help = "program ID / address / pubkey", default_value = DEFAULT_PROGRAM_ID_MSG)] 52 | pub program_id: Option, 53 | 54 | #[arg( 55 | long, 56 | short, 57 | help = "typedefs and accounts to derive bytemuck::Pod for. Does not currently check validity of derivation." 58 | )] 59 | pub zero_copy: Vec, 60 | 61 | #[arg( 62 | long, 63 | short, 64 | help = "solana-program dependency version for generated crate", 65 | default_value = "^2.0" 66 | )] 67 | pub solana_program_vers: String, 68 | 69 | #[arg( 70 | long, 71 | short, 72 | help = "borsh dependency version for generated crate", 73 | default_value = "^1.5" 74 | )] 75 | pub borsh_vers: String, 76 | 77 | #[arg( 78 | long, 79 | help = "thiserror dependency version for generated crate", 80 | default_value = "^1.0" 81 | )] 82 | pub thiserror_vers: String, 83 | 84 | #[arg( 85 | long, 86 | help = "num-derive dependency version for generated crate", 87 | default_value = "0.4.2" 88 | )] 89 | pub num_derive_vers: String, 90 | 91 | #[arg( 92 | long, 93 | help = "num-traits dependency version for generated crate", 94 | default_value = "^0.2" 95 | )] 96 | pub num_traits_vers: String, 97 | 98 | #[arg( 99 | long, 100 | help = "serde dependency version for generated crate", 101 | default_value = "^1.0" 102 | )] 103 | pub serde_vers: String, 104 | 105 | #[arg( 106 | long, 107 | help = "bytemuck dependency version for generated crate", 108 | default_value = "^1.16" 109 | )] 110 | pub bytemuck_vers: String, 111 | } 112 | 113 | /// The CLI entrypoint 114 | pub fn main() { 115 | if env::var(RUST_LOG_ENV_VAR).is_err() { 116 | env::set_var(RUST_LOG_ENV_VAR, "info") 117 | } 118 | env_logger::init(); 119 | log_panics::init(); 120 | 121 | let mut args = Args::parse(); 122 | 123 | let mut file = OpenOptions::new().read(true).open(&args.idl_path).unwrap(); 124 | 125 | let idl = load_idl(&mut file); 126 | 127 | if args.output_crate_name == DEFAULT_OUTPUT_CRATE_NAME_MSG { 128 | args.output_crate_name = format!("{}_interface", idl.program_name()); 129 | } 130 | 131 | args.program_id = args.program_id.and_then(|s| { 132 | if s == DEFAULT_PROGRAM_ID_MSG { 133 | None 134 | } else { 135 | Some(s) 136 | } 137 | }); 138 | 139 | args.output_dir.push(&args.output_crate_name); 140 | fs::create_dir_all(args.output_dir.join("src/")).unwrap(); 141 | 142 | // TODO: multithread, 1 thread per generated file 143 | write_gitignore(&args).unwrap(); 144 | write_cargotoml(&args, idl.as_ref()).unwrap(); 145 | write_lib(&args, idl.as_ref()).unwrap(); 146 | 147 | log::info!( 148 | "{} crate written to {}", 149 | args.output_crate_name, 150 | args.output_dir.to_string_lossy() 151 | ); 152 | } 153 | 154 | pub fn load_idl(file: &mut File) -> Box { 155 | if let Ok(shank_idl) = serde_json::from_reader::<&File, ShankIdl>(file) { 156 | if shank_idl.is_correct_idl_format() { 157 | log::info!("Successfully loaded shank IDL"); 158 | return Box::new(shank_idl); 159 | } 160 | } 161 | file.rewind().unwrap(); 162 | if let Ok(bincode_idl) = serde_json::from_reader::<&File, BincodeIdl>(file) { 163 | if bincode_idl.is_correct_idl_format() { 164 | log::info!("Successfully loaded bincode IDL"); 165 | return Box::new(bincode_idl); 166 | } 167 | } 168 | file.rewind().unwrap(); 169 | // Assume anchor if unidentified 170 | match serde_json::from_reader::<&File, AnchorIdl>(file) { 171 | Ok(anchor_idl) => { 172 | log::info!("Successfully loaded anchor IDL"); 173 | Box::new(anchor_idl) 174 | } 175 | Err(e) => { 176 | panic!("Could not determine IDL format: {:?}", e); 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /solores/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | solores::main(); 3 | } 4 | -------------------------------------------------------------------------------- /solores/src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashSet, 3 | fmt, 4 | fs::{File, OpenOptions}, 5 | hash::Hash, 6 | marker::PhantomData, 7 | path::Path, 8 | str::FromStr, 9 | }; 10 | 11 | use heck::ToPascalCase; 12 | use serde::{ 13 | de::{self, MapAccess, Visitor}, 14 | Deserialize, Deserializer, 15 | }; 16 | use void::Void; 17 | 18 | pub const PUBKEY_TOKEN: &str = "Pubkey"; 19 | 20 | pub fn primitive_or_pubkey_to_token(s: &str) -> String { 21 | match s { 22 | "publicKey" => PUBKEY_TOKEN.to_owned(), 23 | "string" => s.to_pascal_case(), 24 | "bytes" => { 25 | #[cfg(feature = "bytes_to_u8")] 26 | { 27 | "u8".to_owned() 28 | } 29 | #[cfg(not(feature = "bytes_to_u8"))] 30 | { 31 | "bytes".to_owned() 32 | } 33 | } 34 | _ => s.to_owned(), 35 | } 36 | } 37 | 38 | pub fn open_file_create_overwrite>(path: P) -> std::io::Result { 39 | OpenOptions::new() 40 | .create(true) 41 | .write(true) 42 | .truncate(true) 43 | .open(path) 44 | } 45 | 46 | /// Copied from https://serde.rs/string-or-struct.html 47 | pub fn string_or_struct<'de, T, D>(deserializer: D) -> Result 48 | where 49 | T: Deserialize<'de> + FromStr, 50 | D: Deserializer<'de>, 51 | { 52 | // This is a Visitor that forwards string types to T's `FromStr` impl and 53 | // forwards map types to T's `Deserialize` impl. The `PhantomData` is to 54 | // keep the compiler from complaining about T being an unused generic type 55 | // parameter. We need T in order to know the Value type for the Visitor 56 | // impl. 57 | struct StringOrStruct(PhantomData T>); 58 | 59 | impl<'de, T> Visitor<'de> for StringOrStruct 60 | where 61 | T: Deserialize<'de> + FromStr, 62 | { 63 | type Value = T; 64 | 65 | fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { 66 | formatter.write_str("string or map") 67 | } 68 | 69 | fn visit_str(self, value: &str) -> Result 70 | where 71 | E: de::Error, 72 | { 73 | Ok(FromStr::from_str(value).unwrap()) 74 | } 75 | 76 | fn visit_map(self, map: M) -> Result 77 | where 78 | M: MapAccess<'de>, 79 | { 80 | // `MapAccessDeserializer` is a wrapper that turns a `MapAccess` 81 | // into a `Deserializer`, allowing it to be used as the input to T's 82 | // `Deserialize` implementation. T then deserializes itself using 83 | // the entries from the map visitor. 84 | Deserialize::deserialize(de::value::MapAccessDeserializer::new(map)) 85 | } 86 | } 87 | 88 | deserializer.deserialize_any(StringOrStruct(PhantomData)) 89 | } 90 | 91 | pub struct UniqueByReportDupsResult<'a, T> { 92 | pub unique: Vec<&'a T>, 93 | pub duplicates: Vec<&'a T>, 94 | } 95 | 96 | /// Preserves order of vals 97 | pub fn unique_by_report_dups<'a, I, T, V, F>(vals: I, mut f: F) -> UniqueByReportDupsResult<'a, T> 98 | where 99 | I: Iterator, 100 | V: Eq + Hash, 101 | F: FnMut(&T) -> V, 102 | { 103 | let mut hashes = HashSet::new(); 104 | let mut unique = Vec::new(); 105 | let mut duplicates = Vec::new(); 106 | for val in vals { 107 | let hash = f(val); 108 | if hashes.contains(&hash) { 109 | duplicates.push(val); 110 | } else { 111 | hashes.insert(hash); 112 | unique.push(val); 113 | } 114 | } 115 | UniqueByReportDupsResult { unique, duplicates } 116 | } 117 | 118 | pub fn conditional_pascal_case(s: &str) -> String { 119 | // Only apply PascalCase if the string does not start with an uppercase letter. 120 | if s.chars().next().map_or(false, |c| c.is_uppercase()) { 121 | s.to_string() 122 | } else { 123 | s.to_pascal_case() 124 | } 125 | } 126 | 127 | #[cfg(test)] 128 | mod tests { 129 | use super::*; 130 | 131 | #[test] 132 | #[cfg(feature = "bytes_to_u8")] 133 | fn test_bytes_to_u8_feature_enabled() { 134 | let result = primitive_or_pubkey_to_token("bytes"); 135 | assert_eq!(result, "u8"); 136 | 137 | let result = primitive_or_pubkey_to_token("publicKey"); 138 | assert_eq!(result, PUBKEY_TOKEN.to_owned()); 139 | 140 | let result = primitive_or_pubkey_to_token("string"); 141 | assert_eq!(result, "String"); 142 | } 143 | 144 | #[test] 145 | #[cfg(not(feature = "bytes_to_u8"))] 146 | fn test_bytes_to_u8_feature_disabled() { 147 | let result = primitive_or_pubkey_to_token("bytes"); 148 | assert_eq!(result, "bytes"); 149 | 150 | let result = primitive_or_pubkey_to_token("publicKey"); 151 | assert_eq!(result, PUBKEY_TOKEN.to_owned()); 152 | 153 | let result = primitive_or_pubkey_to_token("string"); 154 | assert_eq!(result, "String"); 155 | } 156 | 157 | #[test] 158 | fn test_already_uppercase() { 159 | let input = "I80F48"; 160 | let expected = "I80F48"; 161 | assert_eq!(conditional_pascal_case(input), expected); 162 | } 163 | 164 | #[test] 165 | fn test_lowercase_single_word() { 166 | let input = "pool"; 167 | let expected = "Pool"; 168 | assert_eq!(conditional_pascal_case(input), expected); 169 | } 170 | 171 | #[test] 172 | fn test_mixed_case_string() { 173 | let input = "exampleString"; 174 | let expected = "ExampleString"; 175 | assert_eq!(conditional_pascal_case(input), expected); 176 | } 177 | 178 | #[test] 179 | fn test_empty_string() { 180 | let input = ""; 181 | let expected = ""; 182 | assert_eq!(conditional_pascal_case(input), expected); 183 | } 184 | 185 | #[test] 186 | fn test_already_pascal_case() { 187 | let input = "PascalCase"; 188 | let expected = "PascalCase"; 189 | assert_eq!(conditional_pascal_case(input), expected); 190 | } 191 | 192 | #[test] 193 | fn test_multiple_words() { 194 | let input = "multiple words"; 195 | let expected = "MultipleWords"; 196 | assert_eq!(conditional_pascal_case(input), expected); 197 | } 198 | 199 | #[test] 200 | fn test_numeric_start() { 201 | let input = "123abc"; 202 | let expected = "123abc"; 203 | assert_eq!(conditional_pascal_case(input), expected); 204 | } 205 | 206 | #[test] 207 | fn test_uppercase_first_letter() { 208 | let input = "Uppercase"; 209 | let expected = "Uppercase"; 210 | assert_eq!(conditional_pascal_case(input), expected); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /solores/src/write_cargotoml.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use serde::Serialize; 4 | use toml::{map::Map, Value}; 5 | 6 | use crate::{idl_format::IdlFormat, utils::open_file_create_overwrite, Args}; 7 | 8 | pub const BORSH_CRATE: &str = "borsh"; 9 | pub const BYTEMUCK_CRATE: &str = "bytemuck"; 10 | pub const SERDE_CRATE: &str = "serde"; 11 | pub const SOLANA_PROGRAM_CRATE: &str = "solana-program"; 12 | pub const THISERROR_CRATE: &str = "thiserror"; 13 | pub const NUM_DERIVE_CRATE: &str = "num-derive"; 14 | pub const NUM_TRAITS_CRATE: &str = "num-traits"; 15 | 16 | pub fn write_cargotoml(args: &Args, idl: &dyn IdlFormat) -> std::io::Result<()> { 17 | let cargo_toml = CargoToml::from_args_and_idl(args, idl); 18 | let cargo_toml_str = toml::to_string(&cargo_toml).unwrap(); 19 | 20 | let path = args.output_dir.join("Cargo.toml"); 21 | let mut file = open_file_create_overwrite(path)?; 22 | file.write_all(cargo_toml_str.as_bytes())?; 23 | file.flush() 24 | } 25 | 26 | #[derive(Serialize)] 27 | pub struct CargoToml<'a> { 28 | pub package: Package<'a>, 29 | pub dependencies: Map, 30 | } 31 | 32 | impl<'a> CargoToml<'a> { 33 | pub fn from_args_and_idl(args: &'a Args, idl: &'a dyn IdlFormat) -> Self { 34 | Self { 35 | package: Package { 36 | name: &args.output_crate_name, 37 | version: idl.program_version(), 38 | edition: "2021", 39 | }, 40 | dependencies: idl.dependencies(args), 41 | } 42 | } 43 | } 44 | 45 | #[derive(Serialize)] 46 | pub struct Package<'a> { 47 | pub name: &'a str, 48 | pub version: &'a str, 49 | pub edition: &'a str, 50 | } 51 | 52 | /// Contained str value is the version string arg. 53 | /// e.g. "^1.16", "workspace = true" 54 | pub struct DependencyValue<'a>(pub &'a str); 55 | 56 | impl From> for Map { 57 | fn from(value: DependencyValue) -> Self { 58 | match toml::from_str::>(value.0) { 59 | Ok(m) => m, // "workspace = true" 60 | Err(_) => { 61 | let mut map = Map::new(); 62 | map.insert("version".into(), value.0.into()); 63 | map 64 | } 65 | } 66 | } 67 | } 68 | 69 | impl From> for Value { 70 | fn from(value: DependencyValue) -> Self { 71 | Value::Table(value.into()) 72 | } 73 | } 74 | 75 | pub struct OptionalDependencyValue(pub T); 76 | 77 | impl>> From> for Map { 78 | fn from(value: OptionalDependencyValue) -> Self { 79 | let mut map = value.0.into(); 80 | map.insert("optional".into(), true.into()); 81 | map 82 | } 83 | } 84 | 85 | impl>> From> for Value { 86 | fn from(value: OptionalDependencyValue) -> Self { 87 | Value::Table(value.into()) 88 | } 89 | } 90 | 91 | /// Contained str value is the version string arg. 92 | /// e.g. "^1.16", "workspace = true" 93 | pub struct FeaturesDependencyValue { 94 | pub dependency: T, 95 | pub features: Vec, 96 | } 97 | 98 | impl>> From> for Map { 99 | fn from( 100 | FeaturesDependencyValue { 101 | dependency, 102 | features, 103 | }: FeaturesDependencyValue, 104 | ) -> Self { 105 | let mut map = dependency.into(); 106 | map.insert("features".into(), features.into()); 107 | map 108 | } 109 | } 110 | 111 | impl>> From> for Value { 112 | fn from(value: FeaturesDependencyValue) -> Self { 113 | Value::Table(value.into()) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /solores/src/write_gitignore.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use crate::{utils::open_file_create_overwrite, Args}; 4 | 5 | pub fn write_gitignore(args: &Args) -> std::io::Result<()> { 6 | let path = args.output_dir.join(".gitignore"); 7 | let mut file = open_file_create_overwrite(path)?; 8 | file.write_all(b"/target\nCargo.lock")?; 9 | file.flush() 10 | } 11 | -------------------------------------------------------------------------------- /solores/src/write_src.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Ident, Span, TokenStream, TokenTree}; 2 | use quote::quote; 3 | use std::{io::Write, path::Path}; 4 | 5 | use crate::{idl_format::IdlFormat, utils::open_file_create_overwrite, Args}; 6 | 7 | const DEFAULT_PROGRAM_ID_STR: &str = "TH1S1SNoTAVAL1DPUBKEYDoNoTUSE11111111111111"; 8 | 9 | const MAX_BASE58_LEN: usize = 44; 10 | const PUBKEY_BYTES_SIZE: usize = 32; 11 | 12 | /// Copied from solana_program::Pubkey::from_str() 13 | /// so that we dont have to have solana_program as a dep 14 | fn is_valid_pubkey(s: &str) -> bool { 15 | if s.len() > MAX_BASE58_LEN { 16 | return false; 17 | } 18 | let pubkey_vec = match bs58::decode(s).into_vec() { 19 | Ok(v) => v, 20 | Err(_) => return false, 21 | }; 22 | if pubkey_vec.len() != PUBKEY_BYTES_SIZE { 23 | return false; 24 | } 25 | true 26 | } 27 | 28 | pub fn write_lib(args: &Args, idl: &dyn IdlFormat) -> std::io::Result<()> { 29 | let user_provided_id_opt = 30 | args.program_id 31 | .as_ref() 32 | .and_then(|s| if is_valid_pubkey(s) { Some(s) } else { None }); 33 | let id = user_provided_id_opt 34 | .map(|string| string.as_ref()) 35 | .unwrap_or_else(|| { 36 | idl.program_address().unwrap_or_else(|| { 37 | log::warn!( 38 | "program address not in IDL, setting to default: {}", 39 | DEFAULT_PROGRAM_ID_STR 40 | ); 41 | DEFAULT_PROGRAM_ID_STR 42 | }) 43 | }); 44 | 45 | let mut contents = quote! { 46 | solana_program::declare_id!(#id); 47 | }; 48 | 49 | for module in idl.modules(args) { 50 | let module_name = module.name(); 51 | let module_ident = Ident::new(module.name(), Span::call_site()); 52 | contents.extend(quote! { 53 | pub mod #module_ident; 54 | pub use #module_ident::*; 55 | }); 56 | let mut module_contents = module.gen_head(); 57 | module_contents.extend(module.gen_body()); 58 | write_src_file(args, &format!("src/{module_name}.rs"), module_contents)?; 59 | } 60 | 61 | write_src_file(args, "src/lib.rs", contents) 62 | } 63 | 64 | fn write_src_file>( 65 | args: &Args, 66 | src_file_path: P, 67 | mut contents: TokenStream, 68 | ) -> std::io::Result<()> { 69 | let sanitized_contents = sanitize_tokens(contents); 70 | 71 | let unpretty = syn::parse2(sanitized_contents).unwrap(); 72 | let formatted = prettyplease::unparse(&unpretty); 73 | 74 | let path = args.output_dir.join(src_file_path); 75 | let mut file = open_file_create_overwrite(path)?; 76 | file.write_all(formatted.as_bytes())?; 77 | file.flush() 78 | } 79 | 80 | 81 | fn sanitize_tokens(input: TokenStream) -> TokenStream { 82 | input.into_iter().map(sanitize_token).collect() 83 | } 84 | 85 | fn sanitize_token(token: TokenTree) -> TokenTree { 86 | match token { 87 | TokenTree::Group(group) => { 88 | let content = sanitize_tokens(group.stream()); 89 | TokenTree::Group(proc_macro2::Group::new(group.delimiter(), content)) 90 | }, 91 | TokenTree::Ident(ident) if ident == "type" => { 92 | let raw_type = quote! { r#type }; 93 | raw_type.into_iter().next().unwrap() 94 | }, 95 | _ => token, 96 | } 97 | } -------------------------------------------------------------------------------- /solores/tests/test_gen_and_check_examples.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "test_gen_examples")] 2 | 3 | use test_utils::{check_example, gen_example}; 4 | 5 | const BASE_WORKSPACE_DEPS_ARGS: [&str; 14] = [ 6 | "--solana-program-vers", 7 | "workspace=true", 8 | "--borsh-vers", 9 | "workspace=true", 10 | "--thiserror-vers", 11 | "workspace=true", 12 | "--num-derive-vers", 13 | "workspace=true", 14 | "--num-traits-vers", 15 | "workspace=true", 16 | "--serde-vers", 17 | "workspace=true", 18 | "--bytemuck-vers", 19 | "workspace=true", 20 | ]; 21 | 22 | #[test] 23 | fn test_token_metadata() -> Result<(), Box> { 24 | const EXAMPLE_PATH: &str = "shank/token_metadata"; 25 | gen_example(EXAMPLE_PATH, &BASE_WORKSPACE_DEPS_ARGS)?; 26 | check_example(EXAMPLE_PATH, "mpl_token_metadata_interface") 27 | } 28 | 29 | #[test] 30 | fn test_phoenix_v1() -> Result<(), Box> { 31 | const EXAMPLE_PATH: &str = "shank/phoenix_v1"; 32 | gen_example( 33 | EXAMPLE_PATH, 34 | &[ 35 | BASE_WORKSPACE_DEPS_ARGS.as_ref(), 36 | &[ 37 | "-z", 38 | "Ticks", 39 | "-z", 40 | "MarketSizeParams", 41 | "-z", 42 | "TokenParams", 43 | "-z", 44 | "Seat", 45 | "-z", 46 | "MarketHeader", 47 | "-z", 48 | "FIFOOrderId", 49 | ], 50 | ] 51 | .concat(), 52 | )?; 53 | check_example(EXAMPLE_PATH, "phoenix_v1_interface") 54 | } 55 | 56 | #[test] 57 | fn test_unstake_it() -> Result<(), Box> { 58 | const EXAMPLE_PATH: &str = "anchor/unstake_it"; 59 | gen_example(EXAMPLE_PATH, &BASE_WORKSPACE_DEPS_ARGS)?; 60 | check_example(EXAMPLE_PATH, "unstake_interface") 61 | } 62 | 63 | #[test] 64 | fn test_marinade() -> Result<(), Box> { 65 | const EXAMPLE_PATH: &str = "anchor/marinade"; 66 | gen_example(EXAMPLE_PATH, &BASE_WORKSPACE_DEPS_ARGS)?; 67 | check_example(EXAMPLE_PATH, "marinade_finance_interface") 68 | } 69 | 70 | #[test] 71 | fn test_anchor_ix_no_privilege() -> Result<(), Box> { 72 | const EXAMPLE_PATH: &str = "anchor/ix_no_privilege"; 73 | gen_example(EXAMPLE_PATH, &BASE_WORKSPACE_DEPS_ARGS)?; 74 | check_example(EXAMPLE_PATH, "anchor_ix_no_privilege_interface") 75 | } 76 | 77 | #[test] 78 | fn test_anchor_ix_no_args() -> Result<(), Box> { 79 | const EXAMPLE_PATH: &str = "anchor/ix_no_args"; 80 | gen_example(EXAMPLE_PATH, &BASE_WORKSPACE_DEPS_ARGS)?; 81 | check_example(EXAMPLE_PATH, "anchor_ix_no_args_interface") 82 | } 83 | 84 | #[test] 85 | fn test_anchor_ix_no_accounts() -> Result<(), Box> { 86 | const EXAMPLE_PATH: &str = "anchor/ix_no_accounts"; 87 | gen_example(EXAMPLE_PATH, &BASE_WORKSPACE_DEPS_ARGS)?; 88 | check_example(EXAMPLE_PATH, "anchor_ix_no_accounts_interface") 89 | } 90 | 91 | #[test] 92 | fn test_anchor_ix_no_accounts_pubkey_arg() -> Result<(), Box> { 93 | const EXAMPLE_PATH: &str = "anchor/ix_no_accounts_pubkey_arg"; 94 | gen_example(EXAMPLE_PATH, &BASE_WORKSPACE_DEPS_ARGS)?; 95 | check_example(EXAMPLE_PATH, "anchor_ix_no_accounts_pubkey_arg_interface") 96 | } 97 | 98 | #[test] 99 | fn test_anchor_ix_blank() -> Result<(), Box> { 100 | const EXAMPLE_PATH: &str = "anchor/ix_blank"; 101 | gen_example(EXAMPLE_PATH, &BASE_WORKSPACE_DEPS_ARGS)?; 102 | check_example(EXAMPLE_PATH, "anchor_ix_blank_interface") 103 | } 104 | 105 | #[test] 106 | fn test_shank_ix_no_privilege() -> Result<(), Box> { 107 | const EXAMPLE_PATH: &str = "shank/ix_no_privilege"; 108 | gen_example(EXAMPLE_PATH, &BASE_WORKSPACE_DEPS_ARGS)?; 109 | check_example(EXAMPLE_PATH, "shank_ix_no_privilege_interface") 110 | } 111 | 112 | #[test] 113 | fn test_shank_ix_no_args() -> Result<(), Box> { 114 | const EXAMPLE_PATH: &str = "shank/ix_no_args"; 115 | gen_example(EXAMPLE_PATH, &BASE_WORKSPACE_DEPS_ARGS)?; 116 | check_example(EXAMPLE_PATH, "shank_ix_no_args_interface") 117 | } 118 | 119 | #[test] 120 | fn test_shank_ix_no_accounts() -> Result<(), Box> { 121 | const EXAMPLE_PATH: &str = "shank/ix_no_accounts"; 122 | gen_example(EXAMPLE_PATH, &BASE_WORKSPACE_DEPS_ARGS)?; 123 | check_example(EXAMPLE_PATH, "shank_ix_no_accounts_interface") 124 | } 125 | 126 | #[test] 127 | fn test_shank_ix_no_accounts_pubkey_arg() -> Result<(), Box> { 128 | const EXAMPLE_PATH: &str = "shank/ix_no_accounts_pubkey_arg"; 129 | gen_example(EXAMPLE_PATH, &BASE_WORKSPACE_DEPS_ARGS)?; 130 | check_example(EXAMPLE_PATH, "shank_ix_no_accounts_pubkey_arg_interface") 131 | } 132 | 133 | #[test] 134 | fn test_shank_ix_blank() -> Result<(), Box> { 135 | const EXAMPLE_PATH: &str = "shank/ix_blank"; 136 | gen_example(EXAMPLE_PATH, &BASE_WORKSPACE_DEPS_ARGS)?; 137 | check_example(EXAMPLE_PATH, "shank_ix_blank_interface") 138 | } 139 | 140 | #[test] 141 | fn test_drift() -> Result<(), Box> { 142 | const EXAMPLE_PATH: &str = "anchor/drift"; 143 | gen_example( 144 | EXAMPLE_PATH, 145 | &[ 146 | BASE_WORKSPACE_DEPS_ARGS.as_ref(), 147 | &[ 148 | "--program-id", 149 | "dRiftyHA39MWEi3m9aunc5MzRF1JYuBsbn6VPcn33UH", 150 | ], 151 | ] 152 | .concat(), 153 | )?; 154 | check_example(EXAMPLE_PATH, "drift_interface") 155 | } 156 | 157 | #[test] 158 | fn test_system_program() -> Result<(), Box> { 159 | const EXAMPLE_PATH: &str = "bincode/system"; 160 | gen_example(EXAMPLE_PATH, &BASE_WORKSPACE_DEPS_ARGS)?; 161 | check_example(EXAMPLE_PATH, "system_program_interface") 162 | } 163 | 164 | #[test] 165 | fn test_stake_program() -> Result<(), Box> { 166 | const EXAMPLE_PATH: &str = "bincode/stake"; 167 | gen_example(EXAMPLE_PATH, &BASE_WORKSPACE_DEPS_ARGS)?; 168 | check_example(EXAMPLE_PATH, "stake_program_interface") 169 | } 170 | -------------------------------------------------------------------------------- /test_utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test_utils" 3 | publish = false 4 | version = "0.0.0" 5 | edition = "2021" 6 | 7 | [lib] 8 | name = "test_utils" 9 | 10 | [dependencies] 11 | assert_cmd = "2.0.7" 12 | -------------------------------------------------------------------------------- /test_utils/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::{path::PathBuf, process::Command}; 2 | 3 | use assert_cmd::prelude::{CommandCargoExt, OutputAssertExt}; 4 | 5 | pub const BIN_NAME: &str = "solores"; 6 | 7 | pub fn example_dir(example_path: &str) -> PathBuf { 8 | let mut p = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 9 | p.extend(&["../", "examples", example_path]); 10 | p 11 | } 12 | 13 | pub fn gen_example(example_path: &str, args: &[&str]) -> Result<(), Box> { 14 | let mut solores_cmd = Command::cargo_bin(BIN_NAME)?; 15 | 16 | let dir = example_dir(example_path); 17 | 18 | let mut idl_path = dir.clone(); 19 | idl_path.push("idl.json"); 20 | 21 | solores_cmd.arg(idl_path).arg("-o").arg(&dir); 22 | for arg in args { 23 | solores_cmd.arg(arg); 24 | } 25 | solores_cmd.assert().success(); 26 | 27 | Ok(()) 28 | } 29 | 30 | /// `cargo check` a generated interface crate 31 | /// to ensure valid rust code 32 | pub fn check_example( 33 | example_path: &str, 34 | gen_package_name: &str, 35 | ) -> Result<(), Box> { 36 | let mut generated_cargo_toml_path = example_dir(example_path); 37 | generated_cargo_toml_path.push(gen_package_name); 38 | generated_cargo_toml_path.push("Cargo.toml"); 39 | let mut cargo_check_cmd = Command::new("cargo"); 40 | cargo_check_cmd 41 | .current_dir(example_dir(example_path)) 42 | .arg("check"); 43 | cargo_check_cmd.assert().success(); 44 | Ok(()) 45 | } 46 | 47 | /// `cargo test` a consumer crate of the generated interface crate. 48 | /// Currently unused, takes too long to run. 49 | pub fn test_consumer( 50 | example_path: &str, 51 | consumer_crate_name: &str, 52 | ) -> Result<(), Box> { 53 | let mut consumer_path = example_dir(example_path); 54 | consumer_path.push(consumer_crate_name); 55 | let mut cargo_check_cmd = Command::new("cargo"); 56 | cargo_check_cmd.current_dir(consumer_path).arg("test"); 57 | cargo_check_cmd.assert().success(); 58 | Ok(()) 59 | } 60 | --------------------------------------------------------------------------------