Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ inputs:
clippy:
description: Install Clippy if `true`. Defaults to `false`.
required: false
purge:
description: Purge unused directories if `true`. Defaults to `false`.
required: false
rustfmt:
description: Install Rustfmt if `true`. Defaults to `false`.
required: false
Expand All @@ -32,6 +35,44 @@ runs:
node-version: 20
cache: 'pnpm'

- name: Purge unused ubuntu runner directories
if: ${{ inputs.purge == 'true' }}
shell: bash
run: |
# If there are still disk space issues, try to add more packages from
# https://github.com/jlumbroso/free-disk-space
sudo rm -rf /usr/share/dotnet
sudo rm -rf /usr/share/swift
sudo rm -rf /usr/share/mysql
sudo rm -rf /usr/share/az_*
sudo rm -rf /usr/share/postgresql-common
sudo rm -rf /opt/ghc
sudo rm -rf /usr/local/.ghcup
sudo rm -rf /opt/az
sudo rm -rf /opt/pipx
sudo rm -rf /opt/microsoft
sudo rm -rf /opt/google
sudo rm -rf /opt/hostedtoolcache
sudo rm -rf /usr/local/lib/android
sudo rm -rf /usr/local/lib/heroku
sudo rm -rf /imagegeneration
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
sudo docker image prune --all --force
sudo docker system prune -af
# Clear additional caches that might be large
sudo rm -rf /tmp/*
sudo rm -rf /var/tmp/*
# This packages aren't that big, ~500MB total
#sudo apt-get remove -y '^php.*' --fix-missing
#sudo apt-get remove -y '^dotnet-.*' --fix-missing
#sudo apt-get remove -y '^mongodb-.*' --fix-missing
#sudo apt-get remove -y '^mysql-.*' --fix-missing
sudo apt-get remove -y '^aspnetcore-.*' azure-cli google-chrome-stable firefox powershell mono-devel libgl1-mesa-dri google-cloud-cli --fix-missing
sudo apt-get autoremove -y
sudo apt-get clean
#sudo swapoff -a
#sudo rm -f /mnt/swapfile

- name: Install Dependencies
run: pnpm install --frozen-lockfile
shell: bash
Expand Down
20 changes: 20 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,25 @@ jobs:
- name: Lint
run: pnpm p-token:lint

format_and_lint_interface:
name: Format & Lint Interface
runs-on: ubuntu-latest
steps:
- name: Git Checkout
uses: actions/checkout@v4

- name: Setup Environment
uses: ./.github/actions/setup
with:
clippy: true
rustfmt: true

- name: Format
run: pnpm interface:format

- name: Lint
run: pnpm interface:lint

audit_rust:
name: Audit Rust
runs-on: ubuntu-latest
Expand Down Expand Up @@ -294,6 +313,7 @@ jobs:
with:
cargo-cache-key: cargo-test-ptoken
solana: true
purge: true

- name: Restore Program Builds
uses: actions/cache/restore@v4
Expand Down
41 changes: 41 additions & 0 deletions interface/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
[package]
name = "spl-token-interface"
version = "1.0.0"
description = "Solana Program Library Token Interface"
documentation = "https://docs.rs/spl-token-interface"
readme = "README.md"
authors = { workspace = true }
repository = { workspace = true }
license = { workspace = true }
edition = { workspace = true }

[dependencies]
arrayref = "0.3.9"
bytemuck = "1.20.0"
num-derive = "0.4"
num_enum = "0.7.4"
num-traits = "0.2"
solana-instruction = "2.3.0"
solana-program-error = "2.2.2"
solana-program-option = "2.2.1"
solana-program-pack = "2.2.1"
solana-pubkey = { version = "2.4.0", features = ["bytemuck"] }
solana-sdk-ids = "2.2.1"
thiserror = "2.0"

[dev-dependencies]
proptest = "1.5"
strum = "0.24"
strum_macros = "0.24"

[lib]
crate-type = ["lib"]

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

[lints]
workspace = true

[package.metadata.solana]
program-id = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
14 changes: 14 additions & 0 deletions interface/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Token Interface

A token program interface on the Solana blockchain, usable for fungible and
non-fungible tokens.

This crate provides an interface that third parties can utilize to create and
use their tokens.

Full documentation is available at [https://www.solana-program.com/docs/token](https://www.solana-program.com/docs/token)

## Audit

The audit repository [README](https://github.com/solana-labs/solana-program-library#audits)
contains information about program audits.
166 changes: 166 additions & 0 deletions interface/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
//! Error types

use {
num_derive::FromPrimitive,
solana_program_error::{ProgramError, ToStr},
thiserror::Error,
};

/// Errors that may be returned by the Token program.
#[cfg_attr(test, derive(strum_macros::FromRepr, strum_macros::EnumIter))]
#[derive(Clone, Debug, Eq, Error, FromPrimitive, PartialEq)]
pub enum TokenError {
// 0
/// Lamport balance below rent-exempt threshold.
#[error("Lamport balance below rent-exempt threshold")]
NotRentExempt,
/// Insufficient funds for the operation requested.
#[error("Insufficient funds")]
InsufficientFunds,
/// Invalid Mint.
#[error("Invalid Mint")]
InvalidMint,
/// Account not associated with this Mint.
#[error("Account not associated with this Mint")]
MintMismatch,
/// Owner does not match.
#[error("Owner does not match")]
OwnerMismatch,

// 5
/// This token's supply is fixed and new tokens cannot be minted.
#[error("Fixed supply")]
FixedSupply,
/// The account cannot be initialized because it is already being used.
#[error("Already in use")]
AlreadyInUse,
/// Invalid number of provided signers.
#[error("Invalid number of provided signers")]
InvalidNumberOfProvidedSigners,
/// Invalid number of required signers.
#[error("Invalid number of required signers")]
InvalidNumberOfRequiredSigners,
/// State is uninitialized.
#[error("State is uninitialized")]
UninitializedState,

// 10
/// Instruction does not support native tokens
#[error("Instruction does not support native tokens")]
NativeNotSupported,
/// Non-native account can only be closed if its balance is zero
#[error("Non-native account can only be closed if its balance is zero")]
NonNativeHasBalance,
/// Invalid instruction
#[error("Invalid instruction")]
InvalidInstruction,
/// State is invalid for requested operation.
#[error("State is invalid for requested operation")]
InvalidState,
/// Operation overflowed
#[error("Operation overflowed")]
Overflow,

// 15
/// Account does not support specified authority type.
#[error("Account does not support specified authority type")]
AuthorityTypeNotSupported,
/// This token mint cannot freeze accounts.
#[error("This token mint cannot freeze accounts")]
MintCannotFreeze,
/// Account is frozen; all account operations will fail
#[error("Account is frozen")]
AccountFrozen,
/// Mint decimals mismatch between the client and mint
#[error("The provided decimals value different from the Mint decimals")]
MintDecimalsMismatch,
/// Instruction does not support non-native tokens
#[error("Instruction does not support non-native tokens")]
NonNativeNotSupported,
}
impl From<TokenError> for ProgramError {
fn from(e: TokenError) -> Self {
ProgramError::Custom(e as u32)
}
}

impl TryFrom<u32> for TokenError {
type Error = ProgramError;
fn try_from(error: u32) -> Result<Self, Self::Error> {
match error {
0 => Ok(TokenError::NotRentExempt),
1 => Ok(TokenError::InsufficientFunds),
2 => Ok(TokenError::InvalidMint),
3 => Ok(TokenError::MintMismatch),
4 => Ok(TokenError::OwnerMismatch),
5 => Ok(TokenError::FixedSupply),
6 => Ok(TokenError::AlreadyInUse),
7 => Ok(TokenError::InvalidNumberOfProvidedSigners),
8 => Ok(TokenError::InvalidNumberOfRequiredSigners),
9 => Ok(TokenError::UninitializedState),
10 => Ok(TokenError::NativeNotSupported),
11 => Ok(TokenError::NonNativeHasBalance),
12 => Ok(TokenError::InvalidInstruction),
13 => Ok(TokenError::InvalidState),
14 => Ok(TokenError::Overflow),
15 => Ok(TokenError::AuthorityTypeNotSupported),
16 => Ok(TokenError::MintCannotFreeze),
17 => Ok(TokenError::AccountFrozen),
18 => Ok(TokenError::MintDecimalsMismatch),
19 => Ok(TokenError::NonNativeNotSupported),
_ => Err(ProgramError::InvalidArgument),
}
}
}

impl ToStr for TokenError {
fn to_str<E>(&self) -> &'static str {
match self {
TokenError::NotRentExempt => "Error: Lamport balance below rent-exempt threshold",
TokenError::InsufficientFunds => "Error: insufficient funds",
TokenError::InvalidMint => "Error: Invalid Mint",
TokenError::MintMismatch => "Error: Account not associated with this Mint",
TokenError::OwnerMismatch => "Error: owner does not match",
TokenError::FixedSupply => "Error: the total supply of this token is fixed",
TokenError::AlreadyInUse => "Error: account or token already in use",
TokenError::InvalidNumberOfProvidedSigners => {
"Error: Invalid number of provided signers"
}
TokenError::InvalidNumberOfRequiredSigners => {
"Error: Invalid number of required signers"
}
TokenError::UninitializedState => "Error: State is uninitialized",
TokenError::NativeNotSupported => "Error: Instruction does not support native tokens",
TokenError::NonNativeHasBalance => {
"Error: Non-native account can only be closed if its balance is zero"
}
TokenError::InvalidInstruction => "Error: Invalid instruction",
TokenError::InvalidState => "Error: Invalid account state for operation",
TokenError::Overflow => "Error: Operation overflowed",
TokenError::AuthorityTypeNotSupported => {
"Error: Account does not support specified authority type"
}
TokenError::MintCannotFreeze => "Error: This token mint cannot freeze accounts",
TokenError::AccountFrozen => "Error: Account is frozen",
TokenError::MintDecimalsMismatch => "Error: decimals different from the Mint decimals",
TokenError::NonNativeNotSupported => {
"Error: Instruction does not support non-native tokens"
}
}
}
}

#[cfg(test)]
mod test {
use {super::*, strum::IntoEnumIterator};
#[test]
fn test_parse_error_from_primitive_exhaustive() {
for variant in TokenError::iter() {
let variant_u32 = variant as u32;
assert_eq!(
TokenError::from_repr(variant_u32 as usize).unwrap(),
TokenError::try_from(variant_u32).unwrap()
);
}
}
}
Loading
Loading