├── .github └── workflows │ └── main.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── build.zig ├── build.zig.zon ├── install-solana-zig.sh ├── program-test ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── build.zig ├── build.zig.zon ├── install-build-deps.sh ├── pubkey │ └── main.zig ├── test.sh └── tests │ └── pubkey.rs └── src ├── account.zig ├── allocator.zig ├── blake3.zig ├── bpf.zig ├── clock.zig ├── context.zig ├── hash.zig ├── instruction.zig ├── log.zig ├── public_key.zig ├── rent.zig ├── root.zig └── slot_hashes.zig /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build library and test programs 2 | 3 | on: [pull_request, push] 4 | 5 | env: 6 | SOLANA_ZIG_VERSION: v1.47.0 7 | SOLANA_ZIG_DIR: solana-zig 8 | 9 | jobs: 10 | library: 11 | name: Build and test library 12 | strategy: 13 | matrix: 14 | os: [ubuntu-latest, macos-latest, windows-latest] 15 | fail-fast: false 16 | runs-on: ${{ matrix.os }} 17 | steps: 18 | - uses: actions/checkout@v4 19 | with: 20 | submodules: recursive 21 | 22 | - uses: actions/cache@v4 23 | with: 24 | path: $SOLANA_ZIG_DIR 25 | key: solana-zig-$SOLANA_ZIG_VERSION 26 | 27 | - name: Download solana-zig compiler 28 | shell: bash 29 | run: ./install-solana-zig.sh $SOLANA_ZIG_DIR 30 | 31 | - name: Test library 32 | shell: bash 33 | run: | 34 | $SOLANA_ZIG_DIR/zig build test --summary all 35 | 36 | programs: 37 | name: Build and test programs 38 | strategy: 39 | matrix: 40 | os: [ubuntu-latest, macos-latest, windows-latest] 41 | fail-fast: false 42 | runs-on: ${{ matrix.os }} 43 | steps: 44 | - uses: actions/checkout@v4 45 | with: 46 | submodules: recursive 47 | 48 | - uses: actions/cache@v4 49 | with: 50 | path: | 51 | ~/.cargo/registry 52 | ~/.cargo/git 53 | $SOLANA_ZIG_DIR 54 | key: solana-zig-${{ hashFiles('./program-test/Cargo.lock') }}-$SOLANA_ZIG_VERSION 55 | 56 | - name: Download solana-zig compiler 57 | shell: bash 58 | run: ./install-solana-zig.sh $SOLANA_ZIG_DIR 59 | 60 | - name: Install Rust 61 | uses: dtolnay/rust-toolchain@master 62 | with: 63 | toolchain: 1.84.1 64 | 65 | # took the workaround from https://github.com/sfackler/rust-openssl/issues/2149 66 | - name: Set Perl environment variables 67 | if: runner.os == 'Windows' 68 | run: | 69 | echo "PERL=$((where.exe perl)[0])" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 70 | echo "OPENSSL_SRC_PERL=$((where.exe perl)[0])" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 71 | 72 | - name: Install build deps 73 | shell: bash 74 | run: ./program-test/install-build-deps.sh 75 | 76 | - name: Build and test program 77 | shell: bash 78 | run: ./program-test/test.sh 79 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | .direnv/ 3 | .envrc 4 | 5 | solana-zig/ 6 | zig-cache/ 7 | zig-out/ 8 | .zig-cache/ 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/zig-clap"] 2 | path = lib/zig-clap 3 | url = https://github.com/Hejsil/zig-clap 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # solana-program-sdk-zig 2 | 3 | Write Solana on-chain programs in Zig! 4 | 5 | If you want a more complete program example, please see the 6 | [`solana-helloworld-zig` repo](https://github.com/joncinque/solana-helloworld-zig), 7 | which also provides tests and a CLI. 8 | 9 | ## Other Zig Packages for Solana Program development 10 | 11 | Here are some other packages to help with developing Solana programs with Zig: 12 | 13 | * [Base-58](https://github.com/joncinque/base58-zig) 14 | * [Bincode](https://github.com/joncinque/bincode-zig) 15 | * [Borsh](https://github.com/joncinque/borsh-zig) 16 | * [Solana Program Library](https://github.com/joncinque/solana-program-library-zig) 17 | * [Metaplex Token-Metadata](https://github.com/joncinque/mpl-token-metadata-zig) 18 | 19 | ## Prerequisites 20 | 21 | Requires a Solana-compatible Zig compiler, which can be built with 22 | [solana-zig-bootstrap](https://github.com/joncinque/solana-zig-bootstrap). 23 | 24 | It's also possible to download an appropriate compiler for your system from the 25 | [GitHub Releases](https://github.com/joncinque/solana-zig-bootstrap/releases). 26 | 27 | You can run the convenience script in this repo to download the compiler to 28 | `solana-zig`: 29 | 30 | ``` 31 | ./install-solana-zig.sh 32 | ./solana-zig/zig build test 33 | ``` 34 | 35 | ## How to use 36 | 37 | 1. Add this package to your project: 38 | 39 | ```console 40 | zig fetch --save https://github.com/joncinque/solana-program-sdk-zig/archive/refs/tags/v0.16.0.tar.gz 41 | ``` 42 | 43 | 2. (Optional) if you want to generate a keypair during building, you'll also 44 | need to install base58 and clap: 45 | 46 | ```console 47 | zig fetch --save https://github.com/joncinque/base58-zig/archive/refs/tags/v0.14.0.tar.gz 48 | zig fetch --save https://github.com/Hejsil/zig-clap/archive/refs/tags/0.10.0.tar.gz 49 | ``` 50 | 51 | 3. In your build.zig, add the modules that you want one by one, or use the 52 | helpers in `build.zig`: 53 | 54 | ```zig 55 | const std = @import("std"); 56 | const solana = @import("solana_program_sdk"); 57 | const base58 = @import("base58"); 58 | 59 | pub fn build(b: *std.build.Builder) !void { 60 | // Choose the on-chain target (bpf, sbf v1, sbf v2, etc) 61 | // Many targets exist in the package, including `bpf_target`, 62 | // `sbf_target`, and `sbfv2_target`. 63 | // See `build.zig` for more info. 64 | const target = b.resolveTargetQuery(solana.sbf_target); 65 | // Choose the optimization. `.ReleaseFast` gives optimized CU usage 66 | const optimize = .ReleaseFast; 67 | // Define your program as a shared library 68 | const program = b.addSharedLibrary(.{ 69 | .name = "program_name", 70 | // Give the root of your program, where the entrypoint is defined 71 | .root_source_file = b.path("src/main.zig"), 72 | .optimize = optimize, 73 | .target = target, 74 | }); 75 | // Use the `buildProgram` helper to create the solana-sdk module, and link 76 | // the program properly. 77 | const solana_mod = solana.buildProgram(b, program, target, optimize); 78 | 79 | // Install the program artifact 80 | b.installArtifact(program); 81 | 82 | // Optional: to generate a keypair in `zig-out/lib`, be sure to run this too: 83 | base58.generateProgramKeypair(b, program); 84 | 85 | // Optional, but if you define unit tests in your program files, you can run 86 | // them with `zig build test` with this step included 87 | const test_step = b.step("test", "Run unit tests"); 88 | const lib_unit_tests = b.addTest(.{ 89 | .root_source_file = b.path("src/main.zig"), 90 | }); 91 | lib_unit_tests.root_module.addImport("solana_program_sdk", solana_mod); 92 | const run_unit_tests = b.addRunArtifact(lib_unit_tests); 93 | test_step.dependOn(&run_unit_tests.step); 94 | } 95 | ``` 96 | 97 | 4. Setup `src/main.zig`: 98 | 99 | ```zig 100 | const solana = @import("solana_program_sdk"); 101 | 102 | export fn entrypoint(_: [*]u8) callconv(.C) u64 { 103 | solana.print("Hello world!", .{}); 104 | return 0; 105 | } 106 | ``` 107 | 108 | 5. Download the solana-zig compiler using the script in this repository: 109 | 110 | ```console 111 | $ ./install-solana-zig.sh 112 | ``` 113 | 114 | 6. Build and deploy your program on Solana devnet: 115 | 116 | ```console 117 | $ ./solana-zig/zig build --summary all 118 | Program ID: FHGeakPPYgDWomQT6Embr4mVW5DSoygX6TaxQXdgwDYU 119 | 120 | $ solana airdrop -ud 1 121 | Requesting airdrop of 1 SOL 122 | 123 | Signature: 52rgcLosCjRySoQq5MQLpoKg4JacCdidPNXPWbJhTE1LJR2uzFgp93Q7Dq1hQrcyc6nwrNrieoN54GpyNe8H4j3T 124 | 125 | 882.4039166 SOL 126 | 127 | $ solana program deploy -ud zig-out/lib/program_name.so 128 | Program Id: FHGeakPPYgDWomQT6Embr4mVW5DSoygX6TaxQXdgwDYU 129 | ``` 130 | 131 | And that's it! 132 | 133 | ### Targets available 134 | 135 | The helpers in build.zig contain various Solana targets. Here are their analogues 136 | to the Rust build tools: 137 | 138 | * `sbf_target` -> `cargo build-sbf` 139 | * `sbfv2_target` -> `cargo build-sbf --arch sbfv2` 140 | * **Deprecated** `bpf_target` -> `cargo build-bpf` 141 | 142 | ## Unit tests 143 | 144 | The unit tests require the solana-zig compiler as mentioned in the prerequisites. 145 | 146 | You can run all unit tests for the library with: 147 | 148 | ```console 149 | ./solana-zig/zig build test --summary all 150 | ``` 151 | 152 | ## Integration tests 153 | 154 | There are also integration tests that build programs and run against the Agave 155 | runtime using the 156 | [`solana-program-test` crate](https://crates.io/crates/solana-program-test). 157 | 158 | You can run these tests using the `test.sh` script: 159 | 160 | ```console 161 | cd program-test/ 162 | ./test.sh 163 | ``` 164 | 165 | These tests require a Rust compiler along with the solana-zig compiler, as 166 | mentioned in the prerequisites. Be sure to run `./install-solana-zig.sh` first. 167 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn build(b: *std.Build) void { 4 | const target = b.standardTargetOptions(.{}); 5 | const optimize = b.standardOptimizeOption(.{}); 6 | 7 | // Export self as a module 8 | const solana_mod = b.addModule("solana_program_sdk", .{ .root_source_file = b.path("src/root.zig") }); 9 | 10 | const lib = b.addStaticLibrary(.{ 11 | .name = "solana_program_sdk", 12 | .root_source_file = b.path("src/root.zig"), 13 | .target = target, 14 | .optimize = optimize, 15 | }); 16 | 17 | const base58_dep = b.dependency("base58", .{ 18 | .target = target, 19 | .optimize = optimize, 20 | }); 21 | const base58_mod = base58_dep.module("base58"); 22 | lib.root_module.addImport("base58", base58_mod); 23 | solana_mod.addImport("base58", base58_mod); 24 | 25 | b.installArtifact(lib); 26 | 27 | const lib_unit_tests = b.addTest(.{ 28 | .root_source_file = b.path("src/root.zig"), 29 | .target = target, 30 | .optimize = optimize, 31 | }); 32 | 33 | lib_unit_tests.root_module.addImport("base58", base58_mod); 34 | 35 | const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); 36 | 37 | const test_step = b.step("test", "Run unit tests"); 38 | test_step.dependOn(&run_lib_unit_tests.step); 39 | } 40 | 41 | // General helper function to do all the tricky build steps, by adding the 42 | // solana-sdk module, adding the BPF link script 43 | pub fn buildProgram(b: *std.Build, program: *std.Build.Step.Compile, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode) *std.Build.Module { 44 | const solana_dep = b.dependency("solana_program_sdk", .{ 45 | .target = target, 46 | .optimize = optimize, 47 | }); 48 | const solana_mod = solana_dep.module("solana_program_sdk"); 49 | program.root_module.addImport("solana_program_sdk", solana_mod); 50 | linkSolanaProgram(b, program); 51 | return solana_mod; 52 | } 53 | 54 | pub const sbf_target: std.Target.Query = .{ 55 | .cpu_arch = .sbf, 56 | .os_tag = .solana, 57 | }; 58 | 59 | pub const sbfv2_target: std.Target.Query = .{ 60 | .cpu_arch = .sbf, 61 | .cpu_model = .{ 62 | .explicit = &std.Target.sbf.cpu.sbfv2, 63 | }, 64 | .os_tag = .solana, 65 | .cpu_features_add = std.Target.sbf.cpu.sbfv2.features, 66 | }; 67 | 68 | pub const bpf_target: std.Target.Query = .{ 69 | .cpu_arch = .bpfel, 70 | .os_tag = .freestanding, 71 | .cpu_features_add = std.Target.bpf.featureSet(&.{.solana}), 72 | }; 73 | 74 | pub fn linkSolanaProgram(b: *std.Build, lib: *std.Build.Step.Compile) void { 75 | const write_file_step = b.addWriteFiles(); 76 | const linker_script = write_file_step.add("bpf.ld", 77 | \\PHDRS 78 | \\{ 79 | \\text PT_LOAD ; 80 | \\rodata PT_LOAD ; 81 | \\data PT_LOAD ; 82 | \\dynamic PT_DYNAMIC ; 83 | \\} 84 | \\ 85 | \\SECTIONS 86 | \\{ 87 | \\. = SIZEOF_HEADERS; 88 | \\.text : { *(.text*) } :text 89 | \\.rodata : { *(.rodata*) } :rodata 90 | \\.data.rel.ro : { *(.data.rel.ro*) } :rodata 91 | \\.dynamic : { *(.dynamic) } :dynamic 92 | \\.dynsym : { *(.dynsym) } :data 93 | \\.dynstr : { *(.dynstr) } :data 94 | \\.rel.dyn : { *(.rel.dyn) } :data 95 | \\/DISCARD/ : { 96 | \\*(.eh_frame*) 97 | \\*(.gnu.hash*) 98 | \\*(.hash*) 99 | \\} 100 | \\} 101 | ); 102 | 103 | lib.step.dependOn(&write_file_step.step); 104 | 105 | lib.setLinkerScript(linker_script); 106 | lib.stack_size = 4096; 107 | lib.link_z_notext = true; 108 | lib.root_module.pic = true; 109 | lib.root_module.strip = true; 110 | lib.entry = .{ .symbol_name = "entrypoint" }; 111 | } 112 | -------------------------------------------------------------------------------- /build.zig.zon: -------------------------------------------------------------------------------- 1 | .{ 2 | .fingerprint = 0xdc47aff950fd68c0, 3 | .name = .solana_program_sdk, 4 | .version = "0.16.0", 5 | .minimum_zig_version = "0.14.0", 6 | 7 | // This field is optional. 8 | // Each dependency must either provide a `url` and `hash`, or a `path`. 9 | // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. 10 | // Once all dependencies are fetched, `zig build` no longer requires 11 | // internet connectivity. 12 | .dependencies = .{ 13 | .base58 = .{ 14 | .url = "https://github.com/joncinque/base58-zig/archive/refs/tags/v0.14.0.tar.gz", 15 | .hash = "base58-0.14.0-6-CZm81qAAD4JCRHgewcNh8FS0pmnk7-OmwUkosaFKvg", 16 | }, 17 | }, 18 | .paths = .{ 19 | "build.zig", 20 | "build.zig.zon", 21 | "src", 22 | "LICENSE", 23 | "README.md", 24 | }, 25 | } 26 | -------------------------------------------------------------------------------- /install-solana-zig.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [[ -n $SOLANA_ZIG_VERSION ]]; then 4 | solana_zig_version="$SOLANA_ZIG_VERSION" 5 | else 6 | solana_zig_version="v1.47.0" 7 | fi 8 | solana_zig_release_url="https://github.com/joncinque/solana-zig-bootstrap/releases/download/solana-$solana_zig_version" 9 | 10 | output_dir="$1" 11 | if [[ -z $output_dir ]]; then 12 | output_dir="solana-zig" 13 | fi 14 | output_dir="$(mkdir -p "$output_dir"; cd "$output_dir"; pwd)" 15 | cd $output_dir 16 | 17 | arch=$(uname -m) 18 | if [[ "$arch" == "arm64" ]]; then 19 | arch="aarch64" 20 | fi 21 | case $(uname -s | cut -c1-7) in 22 | "Linux") 23 | os="linux" 24 | abi="musl" 25 | ;; 26 | "Darwin") 27 | os="macos" 28 | abi="none" 29 | ;; 30 | "Windows" | "MINGW64") 31 | os="windows" 32 | abi="gnu" 33 | ;; 34 | *) 35 | echo "install-solana-zig.sh: Unknown OS $(uname -s)" >&2 36 | exit 1 37 | ;; 38 | esac 39 | 40 | solana_zig_tar=zig-$arch-$os-$abi.tar.bz2 41 | url="$solana_zig_release_url/$solana_zig_tar" 42 | echo "Downloading $url" 43 | curl --proto '=https' --tlsv1.2 -SfOL "$url" 44 | echo "Unpacking $solana_zig_tar" 45 | tar -xjf $solana_zig_tar 46 | rm $solana_zig_tar 47 | 48 | solana_zig_dir="zig-$arch-$os-$abi-baseline" 49 | mv "$solana_zig_dir"/* . 50 | rmdir $solana_zig_dir 51 | echo "solana-zig compiler available at $output_dir" 52 | -------------------------------------------------------------------------------- /program-test/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /program-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jon C "] 3 | description = "Solana-SDK Zig Program Test" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | name = "solana-sdk-zig-program-test" 7 | repository = "https://github.com/joncinque/solana-sdk-zig" 8 | version = "0.0.1" 9 | 10 | [dev-dependencies] 11 | num_enum = "0.7" 12 | solana-program = "2.2.1" 13 | solana-program-test = "2.2.6" 14 | solana-sdk = "2.2.2" 15 | test-case = "3" 16 | -------------------------------------------------------------------------------- /program-test/build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const solana = @import("solana_program_sdk"); 3 | const base58 = @import("base58"); 4 | 5 | pub fn build(b: *std.Build) !void { 6 | const optimize = .ReleaseSmall; 7 | const target = b.resolveTargetQuery(solana.sbf_target); 8 | const program = b.addSharedLibrary(.{ 9 | .name = "pubkey", 10 | .root_source_file = b.path("pubkey/main.zig"), 11 | .optimize = optimize, 12 | .target = target, 13 | }); 14 | 15 | // Adding required dependencies, link the program properly, and get a 16 | // prepared modules 17 | _ = solana.buildProgram(b, program, target, optimize); 18 | b.installArtifact(program); 19 | base58.generateProgramKeypair(b, program); 20 | } 21 | -------------------------------------------------------------------------------- /program-test/build.zig.zon: -------------------------------------------------------------------------------- 1 | .{ 2 | .fingerprint = 0x8d2e243edb223d6c, 3 | .name = .solana_program_sdk_test, 4 | .version = "0.13.0", 5 | .minimum_zig_version = "0.14.0", 6 | .dependencies = .{ 7 | .base58 = .{ 8 | .url = "https://github.com/joncinque/base58-zig/archive/refs/tags/v0.14.0.tar.gz", 9 | .hash = "base58-0.14.0-6-CZm81qAAD4JCRHgewcNh8FS0pmnk7-OmwUkosaFKvg", 10 | }, 11 | .clap = .{ 12 | .url = "https://github.com/Hejsil/zig-clap/archive/refs/tags/0.10.0.tar.gz", 13 | .hash = "clap-0.10.0-oBajB434AQBDh-Ei3YtoKIRxZacVPF1iSwp3IX_ZB8f0", 14 | }, 15 | .solana_program_sdk = .{ 16 | .path = "..", 17 | }, 18 | }, 19 | 20 | .paths = .{ 21 | // For example... 22 | "build.zig", 23 | "build.zig.zon", 24 | "pubkey", 25 | }, 26 | } 27 | -------------------------------------------------------------------------------- /program-test/install-build-deps.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | case $(uname -s | cut -c1-7) in 4 | "Windows" | "MINGW64") 5 | choco install openssl --version 3.4.1 --install-arguments="'/DIR=C:\OpenSSL'" -y 6 | export OPENSSL_LIB_DIR='C:\OpenSSL\lib\VC\x64\MT' 7 | export OPENSSL_INCLUDE_DIR='C:\OpenSSL\include' 8 | choco install protoc 9 | export PROTOC='C:\ProgramData\chocolatey\lib\protoc\tools\bin\protoc.exe' 10 | ;; 11 | "Darwin") 12 | brew install protobuf 13 | ;; 14 | "Linux") 15 | sudo apt update 16 | sudo apt install protobuf-compiler -y 17 | ;; 18 | *) 19 | echo "Unknown Operating System" 20 | exit 1 21 | ;; 22 | esac 23 | -------------------------------------------------------------------------------- /program-test/pubkey/main.zig: -------------------------------------------------------------------------------- 1 | const sol = @import("solana_program_sdk"); 2 | 3 | export fn entrypoint(input: [*]u8) u64 { 4 | const context = sol.Context.load(input) catch return 1; 5 | sol.print("Hello zig program {s}", .{context.program_id}); 6 | return 0; 7 | } 8 | -------------------------------------------------------------------------------- /program-test/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ZIG="$1" 4 | ROOT_DIR="$(cd "$(dirname "$0")"/..; pwd)" 5 | if [[ -z "$ZIG" ]]; then 6 | ZIG="$ROOT_DIR/solana-zig/zig" 7 | fi 8 | 9 | set -e 10 | cd $ROOT_DIR/program-test 11 | $ZIG build --summary all --verbose 12 | SBF_OUT_DIR="$ROOT_DIR/program-test/zig-out/lib" cargo test --manifest-path "$ROOT_DIR/program-test/Cargo.toml" 13 | -------------------------------------------------------------------------------- /program-test/tests/pubkey.rs: -------------------------------------------------------------------------------- 1 | use { 2 | solana_program::instruction::Instruction, 3 | solana_program_test::{tokio, ProgramTest}, 4 | solana_sdk::{signature::Signer, transaction::Transaction}, 5 | }; 6 | 7 | mod program { 8 | solana_program::declare_id!("Zigc1Hc97L8Pebma74jDzYiyoUvdxxcj7Gxppg9VRxK"); 9 | } 10 | 11 | fn program_test() -> ProgramTest { 12 | ProgramTest::new("pubkey", program::id(), None) 13 | } 14 | 15 | #[tokio::test] 16 | async fn call() { 17 | let pt = program_test(); 18 | let mut context = pt.start_with_context().await; 19 | let blockhash = context.banks_client.get_latest_blockhash().await.unwrap(); 20 | let transaction = Transaction::new_signed_with_payer( 21 | &[Instruction { 22 | program_id: program::id(), 23 | accounts: vec![], 24 | data: vec![], 25 | }], 26 | Some(&context.payer.pubkey()), 27 | &[&context.payer], 28 | blockhash, 29 | ); 30 | context 31 | .banks_client 32 | .process_transaction(transaction) 33 | .await 34 | .unwrap(); 35 | } 36 | -------------------------------------------------------------------------------- /src/account.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const PublicKey = @import("public_key.zig").PublicKey; 3 | 4 | pub const ACCOUNT_DATA_PADDING = 10 * 1024; 5 | 6 | pub const Account = struct { 7 | pub const DATA_HEADER = 88; 8 | /// A Solana account sliced from what is provided as inputs to the BPF virtual machine. 9 | pub const Data = packed struct { 10 | duplicate_index: u8, 11 | is_signer: u8, 12 | is_writable: u8, 13 | is_executable: u8, 14 | original_data_len: u32, 15 | id: PublicKey, 16 | owner_id: PublicKey, 17 | lamports: u64, 18 | data_len: u64, 19 | 20 | comptime { 21 | std.debug.assert(@offsetOf(Account.Data, "duplicate_index") == 0); 22 | std.debug.assert(@offsetOf(Account.Data, "is_signer") == 0 + 1); 23 | std.debug.assert(@offsetOf(Account.Data, "is_writable") == 0 + 1 + 1); 24 | std.debug.assert(@offsetOf(Account.Data, "is_executable") == 0 + 1 + 1 + 1); 25 | std.debug.assert(@offsetOf(Account.Data, "original_data_len") == 0 + 1 + 1 + 1 + 1); 26 | std.debug.assert(@offsetOf(Account.Data, "id") == 0 + 1 + 1 + 1 + 1 + 4); 27 | std.debug.assert(@offsetOf(Account.Data, "owner_id") == 0 + 1 + 1 + 1 + 1 + 4 + 32); 28 | std.debug.assert(@offsetOf(Account.Data, "lamports") == 0 + 1 + 1 + 1 + 1 + 4 + 32 + 32); 29 | std.debug.assert(@offsetOf(Account.Data, "data_len") == 0 + 1 + 1 + 1 + 1 + 4 + 32 + 32 + 8); 30 | std.debug.assert(@bitSizeOf(Account.Data) == DATA_HEADER * 8); 31 | } 32 | }; 33 | 34 | /// Metadata representing a Solana acconut. 35 | pub const Param = extern struct { 36 | id: *const PublicKey, 37 | is_writable: bool, 38 | is_signer: bool, 39 | }; 40 | 41 | pub const Info = extern struct { 42 | id: *const PublicKey, 43 | lamports: *u64, 44 | data_len: u64, 45 | data: [*]u8, 46 | owner_id: *const PublicKey, 47 | rent_epoch: u64, 48 | is_signer: bool, 49 | is_writable: bool, 50 | is_executable: bool, 51 | }; 52 | 53 | ptr: *Account.Data, 54 | 55 | pub fn fromDataPtr(ptr: *Account.Data) Account { 56 | return Account { .ptr = ptr }; 57 | } 58 | 59 | pub fn id(self: Account) PublicKey { 60 | return self.ptr.id; 61 | } 62 | 63 | pub fn lamports(self: Account) *u64 { 64 | return &self.ptr.lamports; 65 | } 66 | 67 | pub fn ownerId(self: Account) PublicKey { 68 | return self.ptr.owner_id; 69 | } 70 | 71 | pub fn assign(self: Account, new_owner_id: PublicKey) void { 72 | self.ptr.owner_id = new_owner_id; 73 | } 74 | 75 | pub fn data(self: Account) []u8 { 76 | const data_ptr = @as([*]u8, @ptrFromInt(@intFromPtr(self.ptr))) + DATA_HEADER; 77 | return data_ptr[0..self.ptr.data_len]; 78 | } 79 | 80 | pub fn isWritable(self: Account) bool { 81 | return self.ptr.is_writable == 1; 82 | } 83 | 84 | pub fn isExecutable(self: Account) bool { 85 | return self.ptr.is_executable == 1; 86 | } 87 | 88 | pub fn isSigner(self: Account) bool { 89 | return self.ptr.is_signer == 1; 90 | } 91 | 92 | pub fn dataLen(self: Account) u64 { 93 | return self.ptr.data_len; 94 | } 95 | 96 | pub fn realloc(self: Account, new_data_len: u64) error.InvalidRealloc!void { 97 | const diff = @subWithOverflow(new_data_len, self.original_data_len); 98 | if (diff[1] == 0 and diff[0] > ACCOUNT_DATA_PADDING) { 99 | return error.InvalidRealloc; 100 | } 101 | self.reallocUnchecked(new_data_len); 102 | } 103 | 104 | pub fn reallocUnchecked(self: Account, new_data_len: u64) void { 105 | self.ptr.data_len = new_data_len; 106 | } 107 | 108 | pub fn info(self: Account) Account.Info { 109 | const data_ptr = @as([*]u8, @ptrFromInt(@intFromPtr(self.ptr))) + DATA_HEADER; 110 | const rent_epoch = @as(*u64, @ptrFromInt(std.mem.alignForward(u64, @intFromPtr(self.ptr) + self.ptr.data_len + ACCOUNT_DATA_PADDING, @alignOf(u64)))); 111 | 112 | return .{ 113 | .id = &self.ptr.id, 114 | .lamports = &self.ptr.lamports, 115 | .data_len = self.ptr.data_len, 116 | .data = data_ptr, 117 | .owner_id = &self.ptr.owner_id, 118 | .rent_epoch = rent_epoch.*, 119 | .is_signer = self.ptr.is_signer, 120 | .is_writable = self.ptr.is_writable, 121 | .is_executable = self.ptr.is_executable, 122 | }; 123 | } 124 | }; 125 | -------------------------------------------------------------------------------- /src/allocator.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const assert = std.debug.assert; 3 | const Alignment = std.mem.Alignment; 4 | 5 | const heap_start = @as([*]u8, @ptrFromInt(0x300000000)); 6 | const heap_length = 32 * 1024; 7 | 8 | pub const reverse_allocator: std.mem.Allocator = @constCast(&ReverseFixedBufferAllocator.comptimeInit(heap_start[0..heap_length])).allocator(); 9 | pub const allocator: std.mem.Allocator = @constCast(&FixedBufferAllocator.comptimeInit(heap_start[0..heap_length])).allocator(); 10 | 11 | // Just like std.heap.FixedBufferAllocator, with a few key differences: 12 | // * The current end index used by allocations is stored in the first `usize` 13 | // bytes of the provided buffer, allowing it to be used in `comptime` contexts 14 | pub const FixedBufferAllocator = struct { 15 | buffer: []u8, 16 | 17 | fn end_index(self: *FixedBufferAllocator) *usize { 18 | return @as(*usize, @ptrCast(@alignCast(self.buffer[0..@sizeOf(usize)]))); 19 | } 20 | 21 | pub fn comptimeInit(buffer: []u8) FixedBufferAllocator { 22 | return FixedBufferAllocator{ 23 | .buffer = buffer, 24 | }; 25 | } 26 | 27 | pub fn init(buffer: []u8) FixedBufferAllocator { 28 | var fba = FixedBufferAllocator{ 29 | .buffer = buffer, 30 | }; 31 | fba.end_index().* = @sizeOf(usize); 32 | return fba; 33 | } 34 | 35 | /// *WARNING* using this at the same time as the interface returned by `threadSafeAllocator` is not thread safe 36 | pub fn allocator(self: *FixedBufferAllocator) std.mem.Allocator { 37 | return .{ 38 | .ptr = self, 39 | .vtable = &.{ 40 | .alloc = alloc, 41 | .resize = resize, 42 | .free = free, 43 | .remap = remap, 44 | }, 45 | }; 46 | } 47 | 48 | fn ownsSlice(self: *FixedBufferAllocator, slice: []u8) bool { 49 | return sliceContainsSlice(self.buffer, slice); 50 | } 51 | 52 | /// NOTE: this will not work in all cases, if the last allocation had an adjusted_index 53 | /// then we won't be able to determine what the last allocation was. This is because 54 | /// the alignForward operation done in alloc is not reversible. 55 | pub fn isLastAllocation(self: *FixedBufferAllocator, buf: []u8) bool { 56 | return buf.ptr + buf.len == self.buffer.ptr + self.end_index().*; 57 | } 58 | 59 | fn alloc(ctx: *anyopaque, n: usize, alignment: Alignment, ra: usize) ?[*]u8 { 60 | const self: *FixedBufferAllocator = @ptrCast(@alignCast(ctx)); 61 | _ = ra; 62 | 63 | if (self.end_index().* == 0) { 64 | self.end_index().* = @sizeOf(usize); 65 | } 66 | 67 | const ptr_align = alignment.toByteUnits(); 68 | const current_end_index = self.end_index().*; 69 | const adjust_off = std.mem.alignPointerOffset(self.buffer.ptr + current_end_index, ptr_align) orelse return null; 70 | const adjusted_index = current_end_index + adjust_off; 71 | const new_end_index = adjusted_index + n; 72 | if (new_end_index > self.buffer.len) return null; 73 | self.end_index().* = new_end_index; 74 | return self.buffer.ptr + adjusted_index; 75 | } 76 | 77 | fn resize( 78 | ctx: *anyopaque, 79 | buf: []u8, 80 | alignment: Alignment, 81 | new_size: usize, 82 | return_address: usize, 83 | ) bool { 84 | const self: *FixedBufferAllocator = @ptrCast(@alignCast(ctx)); 85 | _ = alignment; 86 | _ = return_address; 87 | assert(@inComptime() or self.ownsSlice(buf)); 88 | 89 | if (!self.isLastAllocation(buf)) { 90 | if (new_size > buf.len) return false; 91 | return true; 92 | } 93 | 94 | if (new_size <= buf.len) { 95 | const sub = buf.len - new_size; 96 | self.end_index().* -= sub; 97 | return true; 98 | } 99 | 100 | const add = new_size - buf.len; 101 | if (add + self.end_index().* > self.buffer.len) return false; 102 | 103 | self.end_index().* += add; 104 | return true; 105 | } 106 | 107 | fn free( 108 | ctx: *anyopaque, 109 | buf: []u8, 110 | alignment: Alignment, 111 | return_address: usize, 112 | ) void { 113 | const self: *FixedBufferAllocator = @ptrCast(@alignCast(ctx)); 114 | _ = alignment; 115 | _ = return_address; 116 | assert(@inComptime() or self.ownsSlice(buf)); 117 | 118 | if (self.isLastAllocation(buf)) { 119 | self.end_index().* -= buf.len; 120 | } 121 | } 122 | 123 | fn remap( 124 | context: *anyopaque, 125 | memory: []u8, 126 | alignment: Alignment, 127 | new_len: usize, 128 | return_address: usize, 129 | ) ?[*]u8 { 130 | return if (resize(context, memory, alignment, new_len, return_address)) memory.ptr else null; 131 | } 132 | 133 | pub fn reset(self: *FixedBufferAllocator) void { 134 | self.end_index().* = @sizeOf(usize); 135 | } 136 | }; 137 | 138 | fn sliceContainsSlice(container: []u8, slice: []u8) bool { 139 | return @intFromPtr(slice.ptr) >= @intFromPtr(container.ptr) and 140 | (@intFromPtr(slice.ptr) + slice.len) <= (@intFromPtr(container.ptr) + container.len); 141 | } 142 | 143 | // Just like std.heap.FixedBufferAllocator, with a few key differences: 144 | // * Allocations start at the high address and go down 145 | // * The current end index used by allocations is stored in the final `usize` 146 | // bytes of the provided buffer, allowing it to be used in `comptime` contexts 147 | const ReverseFixedBufferAllocator = struct { 148 | buffer: []u8, 149 | 150 | fn isLastAllocation(self: *ReverseFixedBufferAllocator, buf: []u8) bool { 151 | return buf.ptr == self.buffer.ptr + self.end_index().*; 152 | } 153 | 154 | fn end_index(self: *ReverseFixedBufferAllocator) *usize { 155 | const cutoff = self.buffer.len - @sizeOf(usize); 156 | return @as(*usize, @ptrCast(@alignCast(self.buffer[cutoff..]))); 157 | } 158 | 159 | pub fn comptimeInit(buffer: []u8) ReverseFixedBufferAllocator { 160 | return ReverseFixedBufferAllocator{ 161 | .buffer = buffer, 162 | }; 163 | } 164 | 165 | pub fn init(buffer: []u8) ReverseFixedBufferAllocator { 166 | const cutoff = buffer.len - @sizeOf(usize); 167 | const end = @as(*usize, @ptrCast(@alignCast(buffer[cutoff..]))); 168 | end.* = cutoff; 169 | return ReverseFixedBufferAllocator{ 170 | .buffer = buffer, 171 | }; 172 | } 173 | 174 | pub fn allocator(self: *ReverseFixedBufferAllocator) std.mem.Allocator { 175 | return .{ 176 | .ptr = self, 177 | .vtable = &.{ 178 | .alloc = ReverseFixedBufferAllocator.alloc, 179 | .free = ReverseFixedBufferAllocator.free, 180 | // For an allocator that grows from high addresses to low, it 181 | // isn't possible to resize without rewriting all the memory at 182 | // the new lower address, so we just omit it here 183 | .resize = std.mem.Allocator.noResize, 184 | .remap = std.mem.Allocator.noRemap, 185 | }, 186 | }; 187 | } 188 | 189 | fn alloc(ctx: *anyopaque, n: usize, alignment: Alignment, ra: usize) ?[*]u8 { 190 | _ = ra; 191 | 192 | const self: *ReverseFixedBufferAllocator = @ptrCast(@alignCast(ctx)); 193 | if (self.end_index().* == 0) { 194 | const cutoff = self.buffer.len - @sizeOf(usize); 195 | const end = @as(*usize, @ptrCast(@alignCast(self.buffer[cutoff..]))); 196 | end.* = cutoff; 197 | } 198 | const ptr_align = alignment.toByteUnits(); 199 | const buffer_address = @intFromPtr(self.buffer.ptr); 200 | var new_end_address = buffer_address + self.end_index().* - n; 201 | new_end_address &= ~(ptr_align - 1); 202 | if (new_end_address - buffer_address < @sizeOf([*]u8)) { 203 | return null; 204 | } 205 | const new_end_index = new_end_address - buffer_address; 206 | self.end_index().* = new_end_index; 207 | 208 | return @ptrCast(self.buffer[new_end_index .. new_end_index + n]); 209 | } 210 | 211 | fn free( 212 | ctx: *anyopaque, 213 | buf: []u8, 214 | alignment: Alignment, 215 | return_address: usize, 216 | ) void { 217 | var self: *ReverseFixedBufferAllocator = @ptrCast(@alignCast(ctx)); 218 | _ = alignment; 219 | _ = return_address; 220 | 221 | if (self.isLastAllocation(buf)) { 222 | self.end_index().* += buf.len; 223 | } 224 | } 225 | }; 226 | 227 | const TEST_SIZE: usize = 800000; 228 | test "ReverseFixedBufferAllocator" { 229 | var test_fixed_buffer = [_]u64{0} ** TEST_SIZE; 230 | var bump_allocator = ReverseFixedBufferAllocator.init(@ptrCast(@alignCast(&test_fixed_buffer))); 231 | 232 | try std.heap.testAllocator(bump_allocator.allocator()); 233 | try std.heap.testAllocatorAligned(bump_allocator.allocator()); 234 | try std.heap.testAllocatorLargeAlignment(bump_allocator.allocator()); 235 | try std.heap.testAllocatorAlignedShrink(bump_allocator.allocator()); 236 | } 237 | 238 | test "FixedBufferAllocator" { 239 | var test_fixed_buffer = [_]u64{0} ** TEST_SIZE; 240 | var bump_allocator = FixedBufferAllocator.init(@ptrCast(@alignCast(&test_fixed_buffer))); 241 | 242 | try std.heap.testAllocator(bump_allocator.allocator()); 243 | try std.heap.testAllocatorAligned(bump_allocator.allocator()); 244 | try std.heap.testAllocatorLargeAlignment(bump_allocator.allocator()); 245 | try std.heap.testAllocatorAlignedShrink(bump_allocator.allocator()); 246 | } 247 | -------------------------------------------------------------------------------- /src/blake3.zig: -------------------------------------------------------------------------------- 1 | const bpf = @import("bpf.zig"); 2 | const log = @import("log.zig"); 3 | const Hash = @import("hash.zig").Hash; 4 | 5 | /// Return a Blake3 hash for the given data. 6 | pub fn hashv(vals: []const []const u8) !Hash { 7 | var hash: Hash = undefined; 8 | if (bpf.is_bpf_program) { 9 | const Syscall = struct { 10 | extern fn sol_blake3( 11 | vals_ptr: [*]const []const u8, 12 | vals_len: u64, 13 | hash_ptr: *Hash, 14 | ) callconv(.C) u64; 15 | }; 16 | const result = Syscall.sol_blake3(vals.ptr, vals.len, &hash); 17 | if (result != 0) { 18 | log.print("failed to get blake3 hash: error code {}", .{result}); 19 | return error.Unexpected; 20 | } 21 | } else { 22 | log.log("cannot calculate blake3 hash in non-bpf context"); 23 | return error.Unexpected; 24 | } 25 | return hash; 26 | } 27 | -------------------------------------------------------------------------------- /src/bpf.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const builtin = @import("builtin"); 3 | const PublicKey = @import("public_key.zig").PublicKey; 4 | 5 | pub const bpf_loader_deprecated_program_id = PublicKey.comptimeFromBase58("BPFLoader1111111111111111111111111111111111"); 6 | pub const bpf_loader_program_id = PublicKey.comptimeFromBase58("BPFLoader2111111111111111111111111111111111"); 7 | pub const bpf_upgradeable_loader_program_id = PublicKey.comptimeFromBase58("BPFLoaderUpgradeab1e11111111111111111111111"); 8 | 9 | pub const UpgradeableLoaderState = union(enum(u32)) { 10 | pub const ProgramData = struct { 11 | slot: u64, 12 | upgrade_authority_id: ?PublicKey, 13 | }; 14 | 15 | uninitialized: void, 16 | buffer: struct { 17 | authority_id: ?PublicKey, 18 | }, 19 | program: struct { 20 | program_data_id: PublicKey, 21 | }, 22 | program_data: ProgramData, 23 | }; 24 | 25 | pub fn getUpgradeableLoaderProgramDataId(program_id: PublicKey) !PublicKey { 26 | const pda = try PublicKey.findProgramAddress(.{program_id}, bpf_upgradeable_loader_program_id); 27 | return pda.address; 28 | } 29 | 30 | pub const is_bpf_program = !builtin.is_test and 31 | ((builtin.os.tag == .freestanding and 32 | builtin.cpu.arch == .bpfel and 33 | std.Target.bpf.featureSetHas(builtin.cpu.features, .solana)) or 34 | builtin.cpu.arch == .sbf); 35 | -------------------------------------------------------------------------------- /src/clock.zig: -------------------------------------------------------------------------------- 1 | const bpf = @import("bpf.zig"); 2 | const log = @import("log.zig"); 3 | const PublicKey = @import("public_key.zig").PublicKey; 4 | 5 | pub const Clock = extern struct { 6 | pub const id = PublicKey.comptimeFromBase58("SysvarC1ock11111111111111111111111111111111"); 7 | 8 | /// The current network/bank slot 9 | slot: u64, 10 | /// The timestamp of the first slot in this Epoch 11 | epoch_start_timestamp: i64, 12 | /// The bank epoch 13 | epoch: u64, 14 | /// The future epoch for which the leader schedule has 15 | /// most recently been calculated 16 | leader_schedule_epoch: u64, 17 | /// Originally computed from genesis creation time and network time 18 | /// in slots (drifty); corrected using validator timestamp oracle as of 19 | /// timestamp_correction and timestamp_bounding features 20 | /// An approximate measure of real-world time, expressed as Unix time 21 | /// (i.e. seconds since the Unix epoch) 22 | unix_timestamp: i64, 23 | 24 | pub fn get() !Clock { 25 | var clock: Clock = undefined; 26 | if (bpf.is_bpf_program) { 27 | const Syscall = struct { 28 | extern fn sol_get_clock_sysvar(ptr: *Clock) callconv(.C) u64; 29 | }; 30 | const result = Syscall.sol_get_clock_sysvar(&clock); 31 | if (result != 0) { 32 | log.print("failed to get clock sysvar: error code {}", .{result}); 33 | return error.Unexpected; 34 | } 35 | } 36 | return clock; 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /src/context.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const Account = @import("account.zig").Account; 4 | const ACCOUNT_DATA_PADDING = @import("account.zig").ACCOUNT_DATA_PADDING; 5 | const allocator = @import("allocator.zig").allocator; 6 | const PublicKey = @import("public_key.zig").PublicKey; 7 | 8 | pub const Context = struct { 9 | num_accounts: u64, 10 | accounts: [64]Account, 11 | data: []const u8, 12 | program_id: *align(1) PublicKey, 13 | 14 | pub fn load(input: [*]u8) !Context { 15 | var ptr: [*]u8 = input; 16 | 17 | const num_accounts: *u64 = @ptrCast(@alignCast(ptr)); 18 | ptr += @sizeOf(u64); 19 | 20 | var i: usize = 0; 21 | var accounts: [64]Account = undefined; 22 | while (i < num_accounts.*) { 23 | const data: *Account.Data = @ptrCast(@alignCast(ptr)); 24 | if (data.duplicate_index != std.math.maxInt(u8)) { 25 | ptr += @sizeOf(u64); 26 | accounts[i] = accounts[data.duplicate_index]; 27 | } else { 28 | accounts[i] = Account.fromDataPtr(data); 29 | ptr += Account.DATA_HEADER + data.data_len + ACCOUNT_DATA_PADDING + @sizeOf(u64); 30 | ptr = @ptrFromInt(std.mem.alignForward(u64, @intFromPtr(ptr), @alignOf(u64))); 31 | } 32 | i += 1; 33 | } 34 | 35 | const data_len: *u64 = @ptrCast(@alignCast(ptr)); 36 | ptr += @sizeOf(u64); 37 | 38 | const data = ptr[0..data_len.*]; 39 | ptr += data_len.*; 40 | 41 | const program_id = @as(*align(1) PublicKey, @ptrCast(ptr)); 42 | 43 | return Context{ 44 | .num_accounts = num_accounts.*, 45 | .accounts = accounts, 46 | .data = data, 47 | .program_id = program_id, 48 | }; 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /src/hash.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const base58 = @import("base58"); 3 | 4 | pub const Hash = struct { 5 | pub const length: usize = 32; 6 | bytes: [Hash.length]u8, 7 | 8 | pub fn format(self: Hash, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { 9 | _ = fmt; 10 | _ = options; 11 | var buffer: [base58.bitcoin.getEncodedLengthUpperBound(Hash.length)]u8 = undefined; 12 | try writer.print("{s}", .{base58.bitcoin.encode(&buffer, &self.bytes)}); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /src/instruction.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Account = @import("account.zig").Account; 3 | const PublicKey = @import("public_key.zig").PublicKey; 4 | const bpf = @import("bpf.zig"); 5 | 6 | pub const Instruction = extern struct { 7 | program_id: *const PublicKey, 8 | accounts: [*]const Account.Param, 9 | accounts_len: usize, 10 | data: [*]const u8, 11 | data_len: usize, 12 | 13 | extern fn sol_invoke_signed_c( 14 | instruction: *const Instruction, 15 | account_infos: ?[*]const Account.Info, 16 | account_infos_len: usize, 17 | signer_seeds: ?[*]const []const []const u8, 18 | signer_seeds_len: usize, 19 | ) callconv(.C) u64; 20 | 21 | pub fn from(params: struct { 22 | program_id: *const PublicKey, 23 | accounts: []const Account.Param, 24 | data: []const u8, 25 | }) Instruction { 26 | return .{ 27 | .program_id = params.program_id, 28 | .accounts = params.accounts.ptr, 29 | .accounts_len = params.accounts.len, 30 | .data = params.data.ptr, 31 | .data_len = params.data.len, 32 | }; 33 | } 34 | 35 | pub fn invoke(self: *const Instruction, accounts: []const Account.Info) !void { 36 | if (bpf.is_bpf_program) { 37 | return switch (sol_invoke_signed_c(self, accounts.ptr, accounts.len, null, 0)) { 38 | 0 => {}, 39 | else => error.CrossProgramInvocationFailed, 40 | }; 41 | } 42 | return error.CrossProgramInvocationFailed; 43 | } 44 | 45 | pub fn invokeSigned(self: *const Instruction, accounts: []const Account.Info, signer_seeds: []const []const []const u8) !void { 46 | if (bpf.is_bpf_program) { 47 | return switch (sol_invoke_signed_c(self, accounts.ptr, accounts.len, signer_seeds.ptr, signer_seeds.len)) { 48 | 0 => {}, 49 | else => error.CrossProgramInvocationFailed, 50 | }; 51 | } 52 | return error.CrossProgramInvocationFailed; 53 | } 54 | }; 55 | 56 | /// Helper for no-alloc CPIs. By providing a discriminant and data type, the 57 | /// dynamic type can be constructed in-place and used for instruction data: 58 | /// 59 | /// const Discriminant = enum(u32) { 60 | /// one, 61 | /// }; 62 | /// const Data = packed struct { 63 | /// field: u64 64 | /// }; 65 | /// const data = InstructionData(Discriminant, Data) { 66 | /// .discriminant = Discriminant.one, 67 | /// .data = .{ .field = 1 } 68 | /// }; 69 | /// const instruction = Instruction.from(.{ 70 | /// .program_id = ..., 71 | /// .accounts = &[_]Account.Param{...}, 72 | /// .data = data.asBytes(), 73 | /// }); 74 | pub fn InstructionData(comptime Discriminant: type, comptime Data: type) type { 75 | comptime { 76 | if (@bitSizeOf(Discriminant) % 8 != 0) { 77 | @panic("Discriminant bit size is not divisible by 8"); 78 | } 79 | if (@bitSizeOf(Data) % 8 != 0) { 80 | @panic("Data bit size is not divisible by 8"); 81 | } 82 | } 83 | return packed struct { 84 | discriminant: Discriminant, 85 | data: Data, 86 | const Self = @This(); 87 | fn asBytes(self: *const Self) []const u8 { 88 | return std.mem.asBytes(self)[0..((@bitSizeOf(Discriminant) + @bitSizeOf(Data)) / 8)]; 89 | } 90 | }; 91 | } 92 | 93 | test "instruction: data transmute" { 94 | const Discriminant = enum(u32) { 95 | zero, 96 | one, 97 | two, 98 | three, 99 | }; 100 | 101 | const Data = packed struct { 102 | a: u8, 103 | b: u16, 104 | c: u64, 105 | }; 106 | 107 | const instruction = InstructionData(Discriminant, Data){ .discriminant = Discriminant.three, .data = Data{ .a = 1, .b = 2, .c = 3 } }; 108 | try std.testing.expectEqualSlices(u8, instruction.asBytes(), &[_]u8{ 3, 0, 0, 0, 1, 2, 0, 3, 0, 0, 0, 0, 0, 0, 0 }); 109 | } 110 | -------------------------------------------------------------------------------- /src/log.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const bpf = @import("bpf.zig"); 3 | 4 | pub inline fn log(message: []const u8) void { 5 | if (bpf.is_bpf_program) { 6 | const Syscall = struct { 7 | extern fn sol_log_(ptr: [*]const u8, len: u64) callconv(.C) void; 8 | }; 9 | Syscall.sol_log_(message.ptr, message.len); 10 | } else { 11 | std.debug.print("{s}\n", .{message}); 12 | } 13 | } 14 | 15 | pub fn print(comptime format: []const u8, args: anytype) void { 16 | if (!bpf.is_bpf_program) { 17 | return std.debug.print(format ++ "\n", args); 18 | } 19 | 20 | if (args.len == 0) { 21 | return log(format); 22 | } 23 | 24 | var buffer: [1024]u8 = undefined; 25 | const message = std.fmt.bufPrint(&buffer, format, args) catch return; 26 | return log(message); 27 | } 28 | 29 | pub inline fn logComputeUnits() void { 30 | if (bpf.is_bpf_program) { 31 | const Syscall = struct { 32 | extern fn sol_log_compute_units_() callconv(.C) void; 33 | }; 34 | Syscall.sol_log_compute_units_(); 35 | } else { 36 | std.debug.print("Compute units not available\n"); 37 | } 38 | } 39 | 40 | pub inline fn logData(data: []const []const u8) void { 41 | if (bpf.is_bpf_program) { 42 | const Syscall = struct { 43 | extern fn sol_log_data(ptr: [*]const []const u8, len: u64) callconv(.C) void; 44 | }; 45 | Syscall.sol_log_data(data.ptr, data.len); 46 | } else { 47 | // TODO base64 encoding 48 | std.debug.print("{s}\n", .{data}); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/public_key.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const base58 = @import("base58"); 3 | const builtin = @import("builtin"); 4 | 5 | const bpf = @import("bpf.zig"); 6 | const log = @import("log.zig"); 7 | 8 | const mem = std.mem; 9 | const testing = std.testing; 10 | 11 | pub const ProgramDerivedAddress = struct { 12 | address: PublicKey, 13 | bump_seed: [1]u8, 14 | }; 15 | 16 | pub const PublicKey = packed struct { 17 | pub const length: usize = 32; 18 | pub const base58_length: usize = 44; 19 | 20 | pub const max_num_seeds: usize = 16; 21 | pub const max_seed_length: usize = 32; 22 | 23 | bytes: u256, 24 | 25 | pub fn from(bytes: [PublicKey.length]u8) PublicKey { 26 | return .{ .bytes = mem.bytesToValue(u256, &bytes) }; 27 | } 28 | 29 | pub fn comptimeFromBase58(comptime encoded: []const u8) PublicKey { 30 | return PublicKey.from(base58.bitcoin.comptimeDecode(encoded)); 31 | } 32 | 33 | pub fn comptimeCreateProgramAddress(comptime seeds: anytype, comptime program_id: PublicKey) PublicKey { 34 | comptime { 35 | return PublicKey.createProgramAddress(seeds, program_id) catch |err| { 36 | @compileError("Failed to create program address: " ++ @errorName(err)); 37 | }; 38 | } 39 | } 40 | 41 | pub fn comptimeFindProgramAddress(comptime seeds: anytype, comptime program_id: PublicKey) ProgramDerivedAddress { 42 | comptime { 43 | return PublicKey.findProgramAddress(seeds, program_id) catch |err| { 44 | @compileError("Failed to find program address: " ++ @errorName(err)); 45 | }; 46 | } 47 | } 48 | 49 | pub fn equals(self: PublicKey, other: PublicKey) bool { 50 | return self.bytes == other.bytes; 51 | } 52 | 53 | pub fn isPointOnCurve(self: PublicKey) bool { 54 | const Y = std.crypto.ecc.Curve25519.Fe.fromBytes(mem.toBytes(self.bytes)); 55 | const Z = std.crypto.ecc.Curve25519.Fe.one; 56 | const YY = Y.sq(); 57 | const u = YY.sub(Z); 58 | const v = YY.mul(std.crypto.ecc.Curve25519.Fe.edwards25519d).add(Z); 59 | if (sqrtRatioM1(u, v) != 1) { 60 | return false; 61 | } 62 | return true; 63 | } 64 | 65 | fn sqrtRatioM1(u: std.crypto.ecc.Curve25519.Fe, v: std.crypto.ecc.Curve25519.Fe) u32 { 66 | const v3 = v.sq().mul(v); // v^3 67 | const x = v3.sq().mul(u).mul(v).pow2523().mul(v3).mul(u); // uv^3(uv^7)^((q-5)/8) 68 | const vxx = x.sq().mul(v); // vx^2 69 | const m_root_check = vxx.sub(u); // vx^2-u 70 | const p_root_check = vxx.add(u); // vx^2+u 71 | const has_m_root = m_root_check.isZero(); 72 | const has_p_root = p_root_check.isZero(); 73 | return @intFromBool(has_m_root) | @intFromBool(has_p_root); 74 | } 75 | 76 | pub fn createProgramAddress(seeds: anytype, program_id: PublicKey) !PublicKey { 77 | if (seeds.len > PublicKey.max_num_seeds) { 78 | return error.MaxSeedLengthExceeded; 79 | } 80 | 81 | comptime var seeds_index = 0; 82 | inline while (seeds_index < seeds.len) : (seeds_index += 1) { 83 | if (@as([]const u8, seeds[seeds_index]).len > PublicKey.max_seed_length) { 84 | return error.MaxSeedLengthExceeded; 85 | } 86 | } 87 | 88 | var address: PublicKey = undefined; 89 | 90 | if (bpf.is_bpf_program) { 91 | const Syscall = struct { 92 | extern fn sol_create_program_address( 93 | seeds_ptr: [*]const []const u8, 94 | seeds_len: u64, 95 | program_id_ptr: *const PublicKey, 96 | address_ptr: *PublicKey, 97 | ) callconv(.C) u64; 98 | }; 99 | 100 | var seeds_array: [seeds.len][]const u8 = undefined; 101 | inline for (seeds, 0..) |seed, i| seeds_array[i] = seed; 102 | 103 | const result = Syscall.sol_create_program_address( 104 | &seeds_array, 105 | seeds.len, 106 | &program_id, 107 | &address, 108 | ); 109 | if (result != 0) { 110 | log.print("failed to create program address with seeds {any} and program id {}: error code {}", .{ 111 | seeds, 112 | program_id, 113 | result, 114 | }); 115 | return error.Unexpected; 116 | } 117 | 118 | return address; 119 | } 120 | 121 | @setEvalBranchQuota(100_000_000); 122 | 123 | var hasher = std.crypto.hash.sha2.Sha256.init(.{}); 124 | comptime var i = 0; 125 | inline while (i < seeds.len) : (i += 1) { 126 | hasher.update(seeds[i]); 127 | } 128 | hasher.update(mem.asBytes(&program_id.bytes)); 129 | hasher.update("ProgramDerivedAddress"); 130 | hasher.final(mem.asBytes(&address.bytes)); 131 | 132 | if (address.isPointOnCurve()) { 133 | return error.InvalidSeeds; 134 | } 135 | 136 | return address; 137 | } 138 | 139 | pub fn findProgramAddress(seeds: anytype, program_id: PublicKey) !ProgramDerivedAddress { 140 | var pda: ProgramDerivedAddress = undefined; 141 | 142 | if (comptime bpf.is_bpf_program) { 143 | const Syscall = struct { 144 | extern fn sol_try_find_program_address( 145 | seeds_ptr: [*]const []const u8, 146 | seeds_len: u64, 147 | program_id_ptr: *const PublicKey, 148 | address_ptr: *PublicKey, 149 | bump_seed_ptr: *u8, 150 | ) callconv(.C) u64; 151 | }; 152 | 153 | var seeds_array: [seeds.len][]const u8 = undefined; 154 | 155 | comptime var seeds_index = 0; 156 | inline while (seeds_index < seeds.len) : (seeds_index += 1) { 157 | const Seed = @TypeOf(seeds[seeds_index]); 158 | if (comptime Seed == PublicKey) { 159 | seeds_array[seeds_index] = &seeds[seeds_index].bytes; 160 | } else { 161 | seeds_array[seeds_index] = seeds[seeds_index]; 162 | } 163 | } 164 | 165 | const result = Syscall.sol_try_find_program_address( 166 | &seeds_array, 167 | seeds.len, 168 | &program_id, 169 | &pda.address, 170 | &pda.bump_seed[0], 171 | ); 172 | if (result != 0) { 173 | log.print("failed to find program address given seeds {any} and program id {}: error code {}", .{ 174 | seeds, 175 | program_id, 176 | result, 177 | }); 178 | return error.Unexpected; 179 | } 180 | 181 | return pda; 182 | } 183 | 184 | var seeds_with_bump: [seeds.len + 1][]const u8 = undefined; 185 | 186 | comptime var seeds_index = 0; 187 | inline while (seeds_index < seeds.len) : (seeds_index += 1) { 188 | const Seed = @TypeOf(seeds[seeds_index]); 189 | if (comptime Seed == PublicKey) { 190 | seeds_with_bump[seeds_index] = &seeds[seeds_index].bytes; 191 | } else { 192 | seeds_with_bump[seeds_index] = seeds[seeds_index]; 193 | } 194 | } 195 | 196 | pda.bump_seed[0] = 255; 197 | seeds_with_bump[seeds.len] = &pda.bump_seed; 198 | 199 | while (pda.bump_seed[0] >= 0) : (pda.bump_seed[0] -= 1) { 200 | pda = ProgramDerivedAddress{ 201 | .address = PublicKey.createProgramAddress(&seeds_with_bump, program_id) catch { 202 | if (pda.bump_seed[0] == 0) { 203 | return error.NoViableBumpSeed; 204 | } 205 | continue; 206 | }, 207 | .bump_seed = pda.bump_seed, 208 | }; 209 | 210 | break; 211 | } 212 | 213 | return pda; 214 | } 215 | 216 | pub fn jsonStringify(self: PublicKey, options: anytype, writer: anytype) !void { 217 | _ = options; 218 | try writer.print("\"{}\"", .{self}); 219 | } 220 | 221 | pub fn format(self: PublicKey, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void { 222 | _ = fmt; 223 | _ = options; 224 | var buffer: [base58.bitcoin.getEncodedLengthUpperBound(PublicKey.length)]u8 = undefined; 225 | try writer.print("{s}", .{base58.bitcoin.encode(&buffer, mem.asBytes(&self.bytes))}); 226 | } 227 | }; 228 | 229 | test "public_key: comptime create program address" { 230 | const id = comptime PublicKey.comptimeFromBase58("11111111111111111111111111111111"); 231 | const address = comptime PublicKey.comptimeCreateProgramAddress(.{ "hello", &.{255} }, id); 232 | try testing.expectFmt("2PjSSVURwJV4o9wz1BDVwwddvcUCuF1NKFpcQBF9emYJ", "{}", .{address}); 233 | } 234 | 235 | test "public_key: comptime find program address" { 236 | const id = comptime PublicKey.comptimeFromBase58("11111111111111111111111111111111"); 237 | const pda = comptime PublicKey.comptimeFindProgramAddress(.{"hello"}, id); 238 | try testing.expectFmt("2PjSSVURwJV4o9wz1BDVwwddvcUCuF1NKFpcQBF9emYJ", "{}", .{pda.address}); 239 | try comptime testing.expectEqual(@as(u8, 255), pda.bump_seed[0]); 240 | } 241 | -------------------------------------------------------------------------------- /src/rent.zig: -------------------------------------------------------------------------------- 1 | const bpf = @import("bpf.zig"); 2 | const log = @import("log.zig"); 3 | const PublicKey = @import("public_key.zig").PublicKey; 4 | 5 | pub const Rent = struct { 6 | pub const id = PublicKey.comptimeFromBase58("SysvarRent111111111111111111111111111111111"); 7 | 8 | /// Default rental rate in lamports/byte-year based on: 9 | /// - 10^9 lamports per SOL 10 | /// - $1 per SOL 11 | /// - $0.01 per megabyte day 12 | /// - $3.65 per megabyte year 13 | pub const default_lamports_per_byte_year: u64 = 1_000_000_000 / 100 * 365 / (1024 * 1024); 14 | 15 | /// Default amount of time (in years) the balance has to include rent for. 16 | pub const default_exemption_threshold: f64 = 2.0; 17 | 18 | /// Default percentage of rent to burn (valid values are 0 to 100). 19 | pub const default_burn_percent: u8 = 50; 20 | 21 | /// Account storage overhead for calculation of base rent. 22 | pub const account_storage_overhead: u64 = 128; 23 | 24 | pub const Data = packed struct { 25 | lamports_per_byte_year: u64 = Rent.default_lamports_per_byte_year, 26 | exemption_threshold: f64 = Rent.default_exemption_threshold, 27 | burn_percent: u8 = Rent.default_burn_percent, 28 | 29 | pub fn getAmountBurned(self: Rent.Data, rent_collected: u64) struct { burned: u64, remaining: u64 } { 30 | const burned = (rent_collected * @as(u64, self.burn_percent)) / 100; 31 | return .{ .burned = burned, .remaining = rent_collected - burned }; 32 | } 33 | 34 | pub fn getAmountDue(self: Rent.Data, balance: u64, data_len: usize, years_elapsed: f64) ?u64 { 35 | if (self.isExempt(balance, data_len)) return null; 36 | const total_data_len: u64 = Rent.account_storage_overhead + data_len; 37 | return @intFromFloat(@as(f64, @floatFromInt(total_data_len * self.lamports_per_byte_year)) * years_elapsed); 38 | } 39 | 40 | pub fn isExempt(self: Rent.Data, balance: u64, data_len: usize) bool { 41 | return balance >= self.getMinimumBalance(data_len); 42 | } 43 | 44 | pub fn getMinimumBalance(self: Rent.Data, data_len: usize) u64 { 45 | const total_data_len: u64 = Rent.account_storage_overhead + data_len; 46 | return total_data_len * self.lamports_per_byte_year * @as(u64, @intFromFloat(self.exemption_threshold)); 47 | } 48 | }; 49 | 50 | pub fn get() !Rent.Data { 51 | var rent: Rent.Data = undefined; 52 | if (bpf.is_bpf_program) { 53 | const Syscall = struct { 54 | extern fn sol_get_rent_sysvar(ptr: *Rent.Data) callconv(.C) u64; 55 | }; 56 | const result = Syscall.sol_get_rent_sysvar(&rent); 57 | if (result != 0) { 58 | log.print("failed to get rent sysvar: error code {}", .{result}); 59 | return error.Unexpected; 60 | } 61 | } 62 | return rent; 63 | } 64 | }; 65 | -------------------------------------------------------------------------------- /src/root.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | const sol = @This(); 4 | 5 | pub usingnamespace @import("public_key.zig"); 6 | pub usingnamespace @import("account.zig"); 7 | pub usingnamespace @import("instruction.zig"); 8 | pub usingnamespace @import("allocator.zig"); 9 | pub usingnamespace @import("context.zig"); 10 | pub usingnamespace @import("clock.zig"); 11 | pub usingnamespace @import("rent.zig"); 12 | pub usingnamespace @import("log.zig"); 13 | pub usingnamespace @import("hash.zig"); 14 | 15 | pub const blake3 = @import("blake3.zig"); 16 | //pub const system_program = @import("system_program.zig"); 17 | pub const slot_hashes = @import("slot_hashes.zig"); 18 | 19 | pub const bpf = @import("bpf.zig"); 20 | 21 | pub const native_loader_id = sol.PublicKey.comptimeFromBase58("NativeLoader1111111111111111111111111111111"); 22 | pub const incinerator_id = sol.PublicKey.comptimeFromBase58("1nc1nerator11111111111111111111111111111111"); 23 | 24 | pub const sysvar_id = sol.PublicKey.comptimeFromBase58("Sysvar1111111111111111111111111111111111111"); 25 | pub const instructions_id = sol.PublicKey.comptimeFromBase58("Sysvar1nstructions1111111111111111111111111"); 26 | 27 | pub const ed25519_program_id = sol.PublicKey.comptimeFromBase58("Ed25519SigVerify111111111111111111111111111"); 28 | pub const secp256k1_program_id = sol.PublicKey.comptimeFromBase58("KeccakSecp256k11111111111111111111111111111"); 29 | 30 | pub const lamports_per_sol = 1_000_000_000; 31 | 32 | test { 33 | std.testing.refAllDecls(@This()); 34 | } 35 | -------------------------------------------------------------------------------- /src/slot_hashes.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const PublicKey = @import("public_key.zig").PublicKey; 3 | 4 | pub const SlotHashes = struct { 5 | ptr: [*]SlotHash, 6 | len: u64, 7 | 8 | pub const id = PublicKey.comptimeFromBase58("SysvarS1otHashes111111111111111111111111111"); 9 | 10 | /// About 2.5 minutes to get your vote in. 11 | pub const max_entries = 512; 12 | }; 13 | 14 | pub const SlotHash = struct { 15 | slot: u64, 16 | hash: [32]u8, 17 | 18 | pub fn from(data: []const u8) []const SlotHash { 19 | const len = std.mem.readIntSliceLittle(u64, data[0..@sizeOf(u64)]); 20 | return @as([*]const SlotHash, @ptrCast(@alignCast(data.ptr + @sizeOf(u64))))[0..len]; 21 | } 22 | }; 23 | --------------------------------------------------------------------------------