Skip to content

Commit e140027

Browse files
authored
interface: Split out instructions, state, errors from program (#76)
Everything needed by Agave is now split into the spl-token-interface crate. On-chain programs can also use this as an rlib.
1 parent 8b7a81a commit e140027

File tree

19 files changed

+2526
-2393
lines changed

19 files changed

+2526
-2393
lines changed

.github/actions/setup/action.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ inputs:
1313
clippy:
1414
description: Install Clippy if `true`. Defaults to `false`.
1515
required: false
16+
purge:
17+
description: Purge unused directories if `true`. Defaults to `false`.
18+
required: false
1619
rustfmt:
1720
description: Install Rustfmt if `true`. Defaults to `false`.
1821
required: false
@@ -32,6 +35,44 @@ runs:
3235
node-version: 20
3336
cache: 'pnpm'
3437

38+
- name: Purge unused ubuntu runner directories
39+
if: ${{ inputs.purge == 'true' }}
40+
shell: bash
41+
run: |
42+
# If there are still disk space issues, try to add more packages from
43+
# https://github.com/jlumbroso/free-disk-space
44+
sudo rm -rf /usr/share/dotnet
45+
sudo rm -rf /usr/share/swift
46+
sudo rm -rf /usr/share/mysql
47+
sudo rm -rf /usr/share/az_*
48+
sudo rm -rf /usr/share/postgresql-common
49+
sudo rm -rf /opt/ghc
50+
sudo rm -rf /usr/local/.ghcup
51+
sudo rm -rf /opt/az
52+
sudo rm -rf /opt/pipx
53+
sudo rm -rf /opt/microsoft
54+
sudo rm -rf /opt/google
55+
sudo rm -rf /opt/hostedtoolcache
56+
sudo rm -rf /usr/local/lib/android
57+
sudo rm -rf /usr/local/lib/heroku
58+
sudo rm -rf /imagegeneration
59+
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
60+
sudo docker image prune --all --force
61+
sudo docker system prune -af
62+
# Clear additional caches that might be large
63+
sudo rm -rf /tmp/*
64+
sudo rm -rf /var/tmp/*
65+
# This packages aren't that big, ~500MB total
66+
#sudo apt-get remove -y '^php.*' --fix-missing
67+
#sudo apt-get remove -y '^dotnet-.*' --fix-missing
68+
#sudo apt-get remove -y '^mongodb-.*' --fix-missing
69+
#sudo apt-get remove -y '^mysql-.*' --fix-missing
70+
sudo apt-get remove -y '^aspnetcore-.*' azure-cli google-chrome-stable firefox powershell mono-devel libgl1-mesa-dri google-cloud-cli --fix-missing
71+
sudo apt-get autoremove -y
72+
sudo apt-get clean
73+
#sudo swapoff -a
74+
#sudo rm -f /mnt/swapfile
75+
3576
- name: Install Dependencies
3677
run: pnpm install --frozen-lockfile
3778
shell: bash

.github/workflows/main.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,28 @@ jobs:
9999
- name: Lint
100100
run: pnpm p-token:lint
101101

102+
format_and_lint_interface:
103+
name: Format, Lint & Test Interface
104+
runs-on: ubuntu-latest
105+
steps:
106+
- name: Git Checkout
107+
uses: actions/checkout@v4
108+
109+
- name: Setup Environment
110+
uses: ./.github/actions/setup
111+
with:
112+
clippy: true
113+
rustfmt: true
114+
115+
- name: Format
116+
run: pnpm interface:format
117+
118+
- name: Lint
119+
run: pnpm interface:lint
120+
121+
- name: Lint
122+
run: pnpm interface:test
123+
102124
audit_rust:
103125
name: Audit Rust
104126
runs-on: ubuntu-latest
@@ -294,6 +316,7 @@ jobs:
294316
with:
295317
cargo-cache-key: cargo-test-ptoken
296318
solana: true
319+
purge: true
297320

298321
- name: Restore Program Builds
299322
uses: actions/cache/restore@v4

interface/Cargo.toml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
[package]
2+
name = "spl-token-interface"
3+
version = "1.0.0"
4+
description = "Solana Program Library Token Interface"
5+
documentation = "https://docs.rs/spl-token-interface"
6+
readme = "README.md"
7+
authors = { workspace = true }
8+
repository = { workspace = true }
9+
license = { workspace = true }
10+
edition = { workspace = true }
11+
12+
[dependencies]
13+
arrayref = "0.3.9"
14+
bytemuck = "1.20.0"
15+
num-derive = "0.4"
16+
num_enum = "0.7.4"
17+
num-traits = "0.2"
18+
solana-instruction = "2.3.0"
19+
solana-program-error = "2.2.2"
20+
solana-program-option = "2.2.1"
21+
solana-program-pack = "2.2.1"
22+
solana-pubkey = { version = "2.4.0", features = ["bytemuck"] }
23+
solana-sdk-ids = "2.2.1"
24+
thiserror = "2.0"
25+
26+
[dev-dependencies]
27+
proptest = "1.5"
28+
strum = "0.24"
29+
strum_macros = "0.24"
30+
31+
[lib]
32+
crate-type = ["lib"]
33+
34+
[package.metadata.docs.rs]
35+
targets = ["x86_64-unknown-linux-gnu"]
36+
37+
[lints]
38+
workspace = true
39+
40+
[package.metadata.solana]
41+
program-id = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"

interface/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Token Interface
2+
3+
A token program interface on the Solana blockchain, usable for fungible and
4+
non-fungible tokens.
5+
6+
This crate provides an interface that third parties can utilize to create and
7+
use their tokens.
8+
9+
Full documentation is available at [https://www.solana-program.com/docs/token](https://www.solana-program.com/docs/token)
10+
11+
## Audit
12+
13+
The audit repository [README](https://github.com/solana-labs/solana-program-library#audits)
14+
contains information about program audits.

interface/src/error.rs

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
//! Error types
2+
3+
use {
4+
num_derive::FromPrimitive,
5+
solana_program_error::{ProgramError, ToStr},
6+
thiserror::Error,
7+
};
8+
9+
/// Errors that may be returned by the Token program.
10+
#[cfg_attr(test, derive(strum_macros::FromRepr, strum_macros::EnumIter))]
11+
#[derive(Clone, Debug, Eq, Error, FromPrimitive, PartialEq)]
12+
pub enum TokenError {
13+
// 0
14+
/// Lamport balance below rent-exempt threshold.
15+
#[error("Lamport balance below rent-exempt threshold")]
16+
NotRentExempt,
17+
/// Insufficient funds for the operation requested.
18+
#[error("Insufficient funds")]
19+
InsufficientFunds,
20+
/// Invalid Mint.
21+
#[error("Invalid Mint")]
22+
InvalidMint,
23+
/// Account not associated with this Mint.
24+
#[error("Account not associated with this Mint")]
25+
MintMismatch,
26+
/// Owner does not match.
27+
#[error("Owner does not match")]
28+
OwnerMismatch,
29+
30+
// 5
31+
/// This token's supply is fixed and new tokens cannot be minted.
32+
#[error("Fixed supply")]
33+
FixedSupply,
34+
/// The account cannot be initialized because it is already being used.
35+
#[error("Already in use")]
36+
AlreadyInUse,
37+
/// Invalid number of provided signers.
38+
#[error("Invalid number of provided signers")]
39+
InvalidNumberOfProvidedSigners,
40+
/// Invalid number of required signers.
41+
#[error("Invalid number of required signers")]
42+
InvalidNumberOfRequiredSigners,
43+
/// State is uninitialized.
44+
#[error("State is uninitialized")]
45+
UninitializedState,
46+
47+
// 10
48+
/// Instruction does not support native tokens
49+
#[error("Instruction does not support native tokens")]
50+
NativeNotSupported,
51+
/// Non-native account can only be closed if its balance is zero
52+
#[error("Non-native account can only be closed if its balance is zero")]
53+
NonNativeHasBalance,
54+
/// Invalid instruction
55+
#[error("Invalid instruction")]
56+
InvalidInstruction,
57+
/// State is invalid for requested operation.
58+
#[error("State is invalid for requested operation")]
59+
InvalidState,
60+
/// Operation overflowed
61+
#[error("Operation overflowed")]
62+
Overflow,
63+
64+
// 15
65+
/// Account does not support specified authority type.
66+
#[error("Account does not support specified authority type")]
67+
AuthorityTypeNotSupported,
68+
/// This token mint cannot freeze accounts.
69+
#[error("This token mint cannot freeze accounts")]
70+
MintCannotFreeze,
71+
/// Account is frozen; all account operations will fail
72+
#[error("Account is frozen")]
73+
AccountFrozen,
74+
/// Mint decimals mismatch between the client and mint
75+
#[error("The provided decimals value different from the Mint decimals")]
76+
MintDecimalsMismatch,
77+
/// Instruction does not support non-native tokens
78+
#[error("Instruction does not support non-native tokens")]
79+
NonNativeNotSupported,
80+
}
81+
impl From<TokenError> for ProgramError {
82+
fn from(e: TokenError) -> Self {
83+
ProgramError::Custom(e as u32)
84+
}
85+
}
86+
87+
impl TryFrom<u32> for TokenError {
88+
type Error = ProgramError;
89+
fn try_from(error: u32) -> Result<Self, Self::Error> {
90+
match error {
91+
0 => Ok(TokenError::NotRentExempt),
92+
1 => Ok(TokenError::InsufficientFunds),
93+
2 => Ok(TokenError::InvalidMint),
94+
3 => Ok(TokenError::MintMismatch),
95+
4 => Ok(TokenError::OwnerMismatch),
96+
5 => Ok(TokenError::FixedSupply),
97+
6 => Ok(TokenError::AlreadyInUse),
98+
7 => Ok(TokenError::InvalidNumberOfProvidedSigners),
99+
8 => Ok(TokenError::InvalidNumberOfRequiredSigners),
100+
9 => Ok(TokenError::UninitializedState),
101+
10 => Ok(TokenError::NativeNotSupported),
102+
11 => Ok(TokenError::NonNativeHasBalance),
103+
12 => Ok(TokenError::InvalidInstruction),
104+
13 => Ok(TokenError::InvalidState),
105+
14 => Ok(TokenError::Overflow),
106+
15 => Ok(TokenError::AuthorityTypeNotSupported),
107+
16 => Ok(TokenError::MintCannotFreeze),
108+
17 => Ok(TokenError::AccountFrozen),
109+
18 => Ok(TokenError::MintDecimalsMismatch),
110+
19 => Ok(TokenError::NonNativeNotSupported),
111+
_ => Err(ProgramError::InvalidArgument),
112+
}
113+
}
114+
}
115+
116+
impl ToStr for TokenError {
117+
fn to_str<E>(&self) -> &'static str {
118+
match self {
119+
TokenError::NotRentExempt => "Error: Lamport balance below rent-exempt threshold",
120+
TokenError::InsufficientFunds => "Error: insufficient funds",
121+
TokenError::InvalidMint => "Error: Invalid Mint",
122+
TokenError::MintMismatch => "Error: Account not associated with this Mint",
123+
TokenError::OwnerMismatch => "Error: owner does not match",
124+
TokenError::FixedSupply => "Error: the total supply of this token is fixed",
125+
TokenError::AlreadyInUse => "Error: account or token already in use",
126+
TokenError::InvalidNumberOfProvidedSigners => {
127+
"Error: Invalid number of provided signers"
128+
}
129+
TokenError::InvalidNumberOfRequiredSigners => {
130+
"Error: Invalid number of required signers"
131+
}
132+
TokenError::UninitializedState => "Error: State is uninitialized",
133+
TokenError::NativeNotSupported => "Error: Instruction does not support native tokens",
134+
TokenError::NonNativeHasBalance => {
135+
"Error: Non-native account can only be closed if its balance is zero"
136+
}
137+
TokenError::InvalidInstruction => "Error: Invalid instruction",
138+
TokenError::InvalidState => "Error: Invalid account state for operation",
139+
TokenError::Overflow => "Error: Operation overflowed",
140+
TokenError::AuthorityTypeNotSupported => {
141+
"Error: Account does not support specified authority type"
142+
}
143+
TokenError::MintCannotFreeze => "Error: This token mint cannot freeze accounts",
144+
TokenError::AccountFrozen => "Error: Account is frozen",
145+
TokenError::MintDecimalsMismatch => "Error: decimals different from the Mint decimals",
146+
TokenError::NonNativeNotSupported => {
147+
"Error: Instruction does not support non-native tokens"
148+
}
149+
}
150+
}
151+
}
152+
153+
#[cfg(test)]
154+
mod test {
155+
use {super::*, strum::IntoEnumIterator};
156+
#[test]
157+
fn test_parse_error_from_primitive_exhaustive() {
158+
for variant in TokenError::iter() {
159+
let variant_u32 = variant as u32;
160+
assert_eq!(
161+
TokenError::from_repr(variant_u32 as usize).unwrap(),
162+
TokenError::try_from(variant_u32).unwrap()
163+
);
164+
}
165+
}
166+
}

0 commit comments

Comments
 (0)