Skip to content

Commit 9668b14

Browse files
committed
token: Add Zig implementation
#### Problem There's only a Rust implementation of SPL Token in Rosetta, but we can write a token program in many other languages. #### Summary of changes Add a simple SPL Token clone in Zig. It does not have multisig support just yet, but it has most of the functionality needed. CU usage is much lower than the Rust version. We might be able to improve it more if we can make pubkey comparisons cheaper. Each one seems to use ~30 CUs currently.
1 parent 2817544 commit 9668b14

File tree

11 files changed

+1377
-5
lines changed

11 files changed

+1377
-5
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
name: Run tests against Zig implementations
2020
strategy:
2121
matrix:
22-
program: [helloworld, transfer-lamports, cpi]
22+
program: [helloworld, transfer-lamports, cpi, token]
2323
fail-fast: false
2424
runs-on: ubuntu-latest
2525
steps:

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,33 +197,39 @@ Token program.
197197
| Language | CU Usage |
198198
| --- | --- |
199199
| Rust | 1115 |
200+
| Zig | 165 |
200201

201202
* Initialize Account
202203

203204
| Language | CU Usage |
204205
| --- | --- |
205206
| Rust | 2071 |
207+
| Zig | 189 |
206208

207209
* Mint To
208210

209211
| Language | CU Usage |
210212
| --- | --- |
211213
| Rust | 2189 |
214+
| Zig | 215 |
212215

213216
* Transfer
214217

215218
| Language | CU Usage |
216219
| --- | --- |
217220
| Rust | 2208 |
221+
| Zig | 205 |
218222

219223
* Burn
220224

221225
| Language | CU Usage |
222226
| --- | --- |
223227
| Rust | 2045 |
228+
| Zig | 175 |
224229

225230
* Close Account
226231

227232
| Language | CU Usage |
228233
| --- | --- |
229234
| Rust | 1483 |
235+
| Zig | 291 |

test-zig.sh

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
#!/usr/bin/env bash
22

33
PROGRAM_NAME="$1"
4-
ZIG="$2"
4+
#ZIG="$2"
5+
PARAMS=("$@")
56
ROOT_DIR="$(cd "$(dirname "$0")"; pwd)"
67
if [[ -z "$ZIG" ]]; then
78
ZIG="$ROOT_DIR/solana-zig/zig"
@@ -11,4 +12,4 @@ set -e
1112
PROGRAM_DIR=$ROOT_DIR/$PROGRAM_NAME
1213
cd $PROGRAM_DIR/zig
1314
$ZIG build --summary all -freference-trace --verbose
14-
SBF_OUT_DIR="$PROGRAM_DIR/zig/zig-out/lib" cargo test --manifest-path "$PROGRAM_DIR/Cargo.toml"
15+
SBF_OUT_DIR="$PROGRAM_DIR/zig/zig-out/lib" cargo test --manifest-path "$PROGRAM_DIR/Cargo.toml" "${PARAMS[@]:2}"

token/tests/assert_instruction_count.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
#![cfg(feature = "test-sbf")]
2-
31
mod action;
42
use {
53
solana_program_test::{processor, tokio, ProgramTest},

token/zig/build.zig

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const std = @import("std");
2+
const solana = @import("solana-program-sdk");
3+
4+
pub fn build(b: *std.Build) !void {
5+
const target = b.resolveTargetQuery(solana.sbf_target);
6+
const optimize = .ReleaseFast;
7+
8+
//const dep_opts = .{ .target = target, .optimize = optimize };
9+
//const solana_lib_dep = b.dependency("solana-program-library", dep_opts);
10+
//const solana_lib_mod = solana_lib_dep.module("solana-program-library");
11+
12+
const program = b.addSharedLibrary(.{
13+
.name = "spl_token",
14+
.root_source_file = b.path("src/main.zig"),
15+
.target = target,
16+
.optimize = optimize,
17+
});
18+
19+
//program.root_module.addImport("solana-program-library", solana_lib_mod);
20+
21+
_ = solana.buildProgram(b, program, target, optimize);
22+
23+
b.installArtifact(program);
24+
}

token/zig/build.zig.zon

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
.{
2+
.name = "solana-program-rosetta-token-zig",
3+
// This is a [Semantic Version](https://semver.org/).
4+
// In a future version of Zig it will be used for package deduplication.
5+
.version = "0.13.0",
6+
7+
// This field is optional.
8+
// This is currently advisory only; Zig does not yet do anything
9+
// with this value.
10+
.minimum_zig_version = "0.13.0",
11+
12+
// This field is optional.
13+
// Each dependency must either provide a `url` and `hash`, or a `path`.
14+
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
15+
// Once all dependencies are fetched, `zig build` no longer requires
16+
// internet connectivity.
17+
.dependencies = .{
18+
.@"solana-program-sdk" = .{
19+
.url = "https://github.com/joncinque/solana-program-sdk-zig/archive/refs/tags/v0.15.0.tar.gz",
20+
.hash = "1220c255d7d80a59251d901da4d2982eb660d099680c1207b14f51078987c655c979",
21+
},
22+
},
23+
24+
// Specifies the set of files and directories that are included in this package.
25+
// Only files and directories listed here are included in the `hash` that
26+
// is computed for this package.
27+
// Paths are relative to the build root. Use the empty string (`""`) to refer to
28+
// the build root itself.
29+
// A directory listed here means that all files within, recursively, are included.
30+
.paths = .{
31+
// For example...
32+
"build.zig",
33+
"build.zig.zon",
34+
"src",
35+
"../../LICENSE",
36+
"../../README.md",
37+
},
38+
}

token/zig/src/error.zig

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
const sol = @import("solana-program-sdk");
2+
3+
pub const TokenError = error{
4+
NotRentExempt,
5+
InsufficientFunds,
6+
InvalidMint,
7+
MintMismatch,
8+
OwnerMismatch,
9+
FixedSupply,
10+
AlreadyInUse,
11+
InvalidNumberOfProvidedSigners,
12+
InvalidNumberOfRequiredSigners,
13+
UninitializedState,
14+
NativeNotSupported,
15+
NonNativeHasBalance,
16+
InvalidInstruction,
17+
InvalidState,
18+
Overflow,
19+
AuthorityTypeNotSupported,
20+
MintCannotFreeze,
21+
AccountFrozen,
22+
MintDecimalsMismatch,
23+
NonNativeNotSupported,
24+
// generic program errors
25+
InvalidArgument,
26+
InvalidInstructionData,
27+
InvalidAccountData,
28+
AccountDataTooSmall,
29+
//InsufficientFunds,
30+
IncorrectProgramId,
31+
MissingRequiredSignature,
32+
AccountAlreadyInitialized,
33+
UninitializedAccount,
34+
NotEnoughAccountKeys,
35+
AccountBorrowFailed,
36+
MaxSeedLengthExceeded,
37+
InvalidSeeds,
38+
BorshIoError,
39+
AccountNotRentExempt,
40+
UnsupportedSysvar,
41+
IllegalOwner,
42+
MaxAccountsDataAllocationsExceeded,
43+
InvalidRealloc,
44+
MaxInstructionTraceLengthExceeded,
45+
BuiltinProgramsMustConsumeComputeUnits,
46+
InvalidAccountOwner,
47+
ArithmeticOverflow,
48+
Immutable,
49+
IncorrectAuthority,
50+
};
51+
52+
pub fn logError(e: TokenError) void {
53+
switch (e) {
54+
TokenError.NotRentExempt => {
55+
sol.log("Error: Lamport balance below rent-exempt threshold");
56+
},
57+
TokenError.InsufficientFunds => {
58+
sol.log("Error: insufficient funds");
59+
},
60+
TokenError.InvalidMint => {
61+
sol.log("Error: Invalid Mint");
62+
},
63+
TokenError.MintMismatch => {
64+
sol.log("Error: Account not associated with this Mint");
65+
},
66+
TokenError.OwnerMismatch => {
67+
sol.log("Error: owner does not match");
68+
},
69+
TokenError.FixedSupply => {
70+
sol.log("Error: the total supply of this token is fixed");
71+
},
72+
TokenError.AlreadyInUse => {
73+
sol.log("Error: account or token already in use");
74+
},
75+
TokenError.InvalidNumberOfProvidedSigners => {
76+
sol.log("Error: Invalid number of provided signers");
77+
},
78+
TokenError.InvalidNumberOfRequiredSigners => {
79+
sol.log("Error: Invalid number of required signers");
80+
},
81+
TokenError.UninitializedState => {
82+
sol.log("Error: State is uninitialized");
83+
},
84+
TokenError.NativeNotSupported => {
85+
sol.log("Error: Instruction does not support native tokens");
86+
},
87+
TokenError.NonNativeHasBalance => {
88+
sol.log("Error: Non-native account can only be closed if its balance is zero");
89+
},
90+
TokenError.InvalidInstruction => {
91+
sol.log("Error: Invalid instruction");
92+
},
93+
TokenError.InvalidState => {
94+
sol.log("Error: Invalid account state for operation");
95+
},
96+
TokenError.Overflow => {
97+
sol.log("Error: Operation overflowed");
98+
},
99+
TokenError.AuthorityTypeNotSupported => {
100+
sol.log("Error: Account does not support specified authority type");
101+
},
102+
TokenError.MintCannotFreeze => {
103+
sol.log("Error: This token mint cannot freeze accounts");
104+
},
105+
TokenError.AccountFrozen => {
106+
sol.log("Error: Account is frozen");
107+
},
108+
TokenError.MintDecimalsMismatch => {
109+
sol.log("Error: decimals different from the Mint decimals");
110+
},
111+
TokenError.NonNativeNotSupported => {
112+
sol.log("Error: Instruction does not support non-native tokens");
113+
},
114+
TokenError.InvalidArgument => {},
115+
TokenError.InvalidInstructionData => {},
116+
TokenError.InvalidAccountData => {},
117+
TokenError.AccountDataTooSmall => {},
118+
TokenError.InsufficientFunds => {},
119+
TokenError.IncorrectProgramId => {},
120+
TokenError.MissingRequiredSignature => {},
121+
TokenError.AccountAlreadyInitialized => {},
122+
TokenError.UninitializedAccount => {},
123+
TokenError.NotEnoughAccountKeys => {},
124+
TokenError.AccountBorrowFailed => {},
125+
TokenError.MaxSeedLengthExceeded => {},
126+
TokenError.InvalidSeeds => {},
127+
TokenError.BorshIoError => {},
128+
TokenError.AccountNotRentExempt => {},
129+
TokenError.UnsupportedSysvar => {},
130+
TokenError.IllegalOwner => {},
131+
TokenError.MaxAccountsDataAllocationsExceeded => {},
132+
TokenError.InvalidRealloc => {},
133+
TokenError.MaxInstructionTraceLengthExceeded => {},
134+
TokenError.BuiltinProgramsMustConsumeComputeUnits => {},
135+
TokenError.InvalidAccountOwner => {},
136+
TokenError.ArithmeticOverflow => {},
137+
TokenError.Immutable => {},
138+
TokenError.IncorrectAuthority => {},
139+
}
140+
}

token/zig/src/id.zig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
const PublicKey = @import("solana-program-sdk").PublicKey;
2+
pub const id = PublicKey.comptimeFromBase58("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA");
3+
pub const native_mint_id = PublicKey.comptimeFromBase58("So11111111111111111111111111111111111111112");
4+
pub const system_program_id = PublicKey.comptimeFromBase58("11111111111111111111111111111111");

0 commit comments

Comments
 (0)