diff --git a/Cargo.lock b/Cargo.lock
index 5e0fd7b..5325d01 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -5206,7 +5206,6 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
name = "token-interface"
version = "0.0.0"
dependencies = [
- "bytemuck",
"pinocchio",
"pinocchio-pubkey",
]
@@ -5216,7 +5215,6 @@ name = "token-program"
version = "0.0.0"
dependencies = [
"assert_matches",
- "bytemuck",
"pinocchio",
"pinocchio-pubkey",
"solana-program-test",
diff --git a/README.md b/README.md
index fab4746..57d38c0 100644
--- a/README.md
+++ b/README.md
@@ -12,9 +12,6 @@
-> [!WARNING]
-> The program is not yet fully-optimized. There are still opportunities to improve the compute units consumption.
-
## Overview
This repository contains a **proof-of-concept** of a reimplementation of the SPL Token program, one of the most used programs on Solana, using [`pinocchio`](https://github.com/febo/pinocchio). The purpose is to have an implementation that optimizes the compute units, while being fully compatible with the original implementation — i.e., support the exact same instruction and account layouts as SPL Token, byte for byte.
@@ -23,27 +20,27 @@ This repository contains a **proof-of-concept** of a reimplementation of the SPL
| Instruction | Completed | CU (`p-token`) | CU (`spl-token`) |
|----------------------------|-----------|----------------|------------------|
-| `InitializeMint` | ✅ | 389 | 2967 |
-| `InitializeAccount` | ✅ | 409 | 4527 |
-| `InitializeMultisig` | ✅ | 458 | 2973 |
-| `Transfer` | ✅ | 194 | 4645 |
-| `Approve` | ✅ | 151 | 2904 |
-| `Revoke` | ✅ | 93 | 2677 |
-| `SetAuthority` | ✅ | 171 | 3167 |
-| `MintTo` | ✅ | 196 | 4538 |
-| `Burn` | ✅ | 184 | 4753 |
-| `CloseAccount` | ✅ | 163 | 2916 |
-| `FreezeAccount` | ✅ | 131 | 4265 |
-| `ThawAccount` | ✅ | 132 | 4267 |
-| `TransferChecked` | ✅ | 207 | 6201 |
-| `ApproveChecked` | ✅ | 166 | 4459 |
-| `MintToChecked` | ✅ | 180 | 4546 |
-| `BurnChecked` | ✅ | 166 | 4755 |
-| `InitializeAccount2` | ✅ | 394 | 4388 |
+| `InitializeMint` | ✅ | 343 | 2967 |
+| `InitializeAccount` | ✅ | 416 | 4527 |
+| `InitializeMultisig` | ✅ | 499 | 2973 |
+| `Transfer` | ✅ | 140 | 4645 |
+| `Approve` | ✅ | 133 | 2904 |
+| `Revoke` | ✅ | 106 | 2677 |
+| `SetAuthority` | ✅ | 142 | 3167 |
+| `MintTo` | ✅ | 143 | 4538 |
+| `Burn` | ✅ | 175 | 4753 |
+| `CloseAccount` | ✅ | 147 | 2916 |
+| `FreezeAccount` | ✅ | 141 | 4265 |
+| `ThawAccount` | ✅ | 142 | 4267 |
+| `TransferChecked` | ✅ | 211 | 6201 |
+| `ApproveChecked` | ✅ | 169 | 4459 |
+| `MintToChecked` | ✅ | 178 | 4546 |
+| `BurnChecked` | ✅ | 181 | 4755 |
+| `InitializeAccount2` | ✅ | 399 | 4388 |
| `SyncNative` | ✅ | | |
-| `InitializeAccount3` | ✅ | 523 | 4240 |
-| `InitializeMultisig2` | ✅ | 563 | 2826 |
-| `InitializeMint2` | ✅ | 500 | 2827 |
+| `InitializeAccount3` | ✅ | 508 | 4240 |
+| `InitializeMultisig2` | ✅ | 579 | 2826 |
+| `InitializeMint2` | ✅ | 477 | 2827 |
| `GetAccountDataSize` | ✅ | | |
| `InitializeImmutableOwner` | ✅ | | |
| `AmountToUiAmount` | ✅ | | |
diff --git a/interface/Cargo.toml b/interface/Cargo.toml
index 628cb2a..fb26855 100644
--- a/interface/Cargo.toml
+++ b/interface/Cargo.toml
@@ -8,6 +8,5 @@ repository = { workspace = true }
publish = false
[dependencies]
-bytemuck = { workspace = true }
pinocchio = { workspace = true }
pinocchio-pubkey = { workspace = true }
diff --git a/interface/src/instruction.rs b/interface/src/instruction.rs
index c78e6bb..a829e9a 100644
--- a/interface/src/instruction.rs
+++ b/interface/src/instruction.rs
@@ -1,8 +1,8 @@
-//! Instruction types
+//! Instruction types.
-use pinocchio::pubkey::Pubkey;
+use pinocchio::{program_error::ProgramError, pubkey::Pubkey};
-use crate::state::PodCOption;
+use crate::error::TokenError;
/// Instructions supported by the token program.
#[repr(C)]
@@ -27,7 +27,7 @@ pub enum TokenInstruction<'a> {
/// The authority/multisignature to mint tokens.
mint_authority: Pubkey,
/// The freeze authority/multisignature of the mint.
- freeze_authority: PodCOption,
+ freeze_authority: Option,
},
/// Initializes a new account to hold tokens. If this account is associated
@@ -147,7 +147,7 @@ pub enum TokenInstruction<'a> {
/// The type of authority to update.
authority_type: AuthorityType,
/// The new authority
- new_authority: PodCOption,
+ new_authority: Option,
},
/// Mints new tokens to an account. The native mint does not support
@@ -416,7 +416,7 @@ pub enum TokenInstruction<'a> {
/// The authority/multisignature to mint tokens.
mint_authority: Pubkey,
/// The freeze authority/multisignature of the mint.
- freeze_authority: PodCOption,
+ freeze_authority: Option,
},
/// Gets the required size of an account for the given mint as a
@@ -482,7 +482,7 @@ pub enum TokenInstruction<'a> {
// token/js/src/instructions/types.ts to maintain @solana/spl-token compatibility
}
-/// Specifies the authority type for SetAuthority instructions
+/// Specifies the authority type for `SetAuthority` instructions
#[repr(u8)]
#[derive(Clone, Debug, PartialEq)]
pub enum AuthorityType {
@@ -506,13 +506,13 @@ impl AuthorityType {
}
}
- pub fn from(index: u8) -> Self {
+ pub fn from(index: u8) -> Result {
match index {
- 0 => AuthorityType::MintTokens,
- 1 => AuthorityType::FreezeAccount,
- 2 => AuthorityType::AccountOwner,
- 3 => AuthorityType::CloseAccount,
- _ => panic!("invalid authority type: {index}"),
+ 0 => Ok(AuthorityType::MintTokens),
+ 1 => Ok(AuthorityType::FreezeAccount),
+ 2 => Ok(AuthorityType::AccountOwner),
+ 3 => Ok(AuthorityType::CloseAccount),
+ _ => Err(TokenError::InvalidInstruction.into()),
}
}
}
diff --git a/interface/src/state/account.rs b/interface/src/state/account.rs
index e88b48d..fbe6377 100644
--- a/interface/src/state/account.rs
+++ b/interface/src/state/account.rs
@@ -1,11 +1,15 @@
-use bytemuck::{Pod, Zeroable};
-use pinocchio::pubkey::Pubkey;
+use pinocchio::{
+ account_info::{AccountInfo, Ref},
+ program_error::ProgramError,
+ pubkey::Pubkey,
+};
-use super::{PodCOption, PodU64};
+use crate::program::ID;
-/// Account data.
+use super::{account_state::AccountState, COption};
+
+/// Internal representation of a token account data.
#[repr(C)]
-#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
pub struct Account {
/// The mint associated with this account
pub mint: Pubkey,
@@ -14,82 +18,179 @@ pub struct Account {
pub owner: Pubkey,
/// The amount of tokens this account holds.
- pub amount: PodU64,
+ amount: [u8; 8],
/// If `delegate` is `Some` then `delegated_amount` represents
- /// the amount authorized by the delegate
- pub delegate: PodCOption,
+ /// the amount authorized by the delegate.
+ delegate: COption,
+
+ /// The account's state.
+ pub state: AccountState,
- /// The account's state
- pub state: u8,
+ /// Indicates whether this account represents a native token or not.
+ is_native: [u8; 4],
/// If is_native.is_some, this is a native token, and the value logs the
/// rent-exempt reserve. An Account is required to be rent-exempt, so
/// the value is used by the Processor to ensure that wrapped SOL
/// accounts do not drop below this threshold.
- pub is_native: PodCOption,
+ native_amount: [u8; 8],
- /// The amount delegated
- pub delegated_amount: PodU64,
+ /// The amount delegated.
+ delegated_amount: [u8; 8],
/// Optional authority to close the account.
- pub close_authority: PodCOption,
+ close_authority: COption,
}
impl Account {
- /// Size of the `Account` account.
- pub const LEN: usize = core::mem::size_of::();
+ pub const LEN: usize = core::mem::size_of::();
+ /// Return a `TokenAccount` from the given account info.
+ ///
+ /// This method performs owner and length validation on `AccountInfo`, safe borrowing
+ /// the account data.
#[inline]
- pub fn is_initialized(&self) -> bool {
- self.state != AccountState::Uninitialized as u8
+ pub fn from_account_info(account_info: &AccountInfo) -> Result[, ProgramError> {
+ if account_info.data_len() != Self::LEN {
+ return Err(ProgramError::InvalidAccountData);
+ }
+ if account_info.owner() != &ID {
+ return Err(ProgramError::InvalidAccountData);
+ }
+ Ok(Ref::map(account_info.try_borrow_data()?, |data| unsafe {
+ Self::from_bytes(data)
+ }))
}
+ /// Return a `TokenAccount` from the given account info.
+ ///
+ /// This method performs owner and length validation on `AccountInfo`, but does not
+ /// perform the borrow check.
+ ///
+ /// # Safety
+ ///
+ /// The caller must ensure that it is safe to borrow the account data – e.g., there are
+ /// no mutable borrows of the account data.
#[inline]
- pub fn is_frozen(&self) -> bool {
- self.state == AccountState::Frozen as u8
+ pub unsafe fn from_account_info_unchecked(
+ account_info: &AccountInfo,
+ ) -> Result<&Account, ProgramError> {
+ if account_info.data_len() != Self::LEN {
+ return Err(ProgramError::InvalidAccountData);
+ }
+ if account_info.owner() != &ID {
+ return Err(ProgramError::InvalidAccountData);
+ }
+ Ok(Self::from_bytes(account_info.borrow_data_unchecked()))
+ }
+
+ /// Return a `TokenAccount` from the given bytes.
+ ///
+ /// # Safety
+ ///
+ /// The caller must ensure that `bytes` contains a valid representation of `TokenAccount`.
+ #[inline(always)]
+ pub unsafe fn from_bytes(bytes: &[u8]) -> &Self {
+ &*(bytes.as_ptr() as *const Account)
}
+ /// Return a mutable `Mint` reference from the given bytes.
+ ///
+ /// # Safety
+ ///
+ /// The caller must ensure that `bytes` contains a valid representation of `Mint`.
+ #[inline(always)]
+ pub unsafe fn from_bytes_mut(bytes: &mut [u8]) -> &mut Self {
+ &mut *(bytes.as_mut_ptr() as *mut Account)
+ }
+
+ #[inline]
+ pub fn set_amount(&mut self, amount: u64) {
+ self.amount = amount.to_le_bytes();
+ }
+
+ #[inline]
pub fn amount(&self) -> u64 {
- self.amount.into()
+ u64::from_le_bytes(self.amount)
}
-}
-/// Account state.
-#[repr(u8)]
-#[derive(Clone, Copy, Debug, Default, PartialEq)]
-pub enum AccountState {
- /// Account is not yet initialized
- #[default]
- Uninitialized,
-
- /// Account is initialized; the account owner and/or delegate may perform
- /// permitted operations on this account
- Initialized,
-
- /// Account has been frozen by the mint freeze authority. Neither the
- /// account owner nor the delegate are able to perform operations on
- /// this account.
- Frozen,
-}
+ #[inline]
+ pub fn clear_delegate(&mut self) {
+ self.delegate.0[0] = 0;
+ }
-impl From for AccountState {
- fn from(value: u8) -> Self {
- match value {
- 0 => AccountState::Uninitialized,
- 1 => AccountState::Initialized,
- 2 => AccountState::Frozen,
- _ => panic!("invalid account state value: {value}"),
+ #[inline]
+ pub fn set_delegate(&mut self, delegate: &Pubkey) {
+ self.delegate.0[0] = 1;
+ self.delegate.1 = *delegate;
+ }
+
+ #[inline]
+ pub fn delegate(&self) -> Option<&Pubkey> {
+ if self.delegate.0[0] == 1 {
+ Some(&self.delegate.1)
+ } else {
+ None
}
}
-}
-impl From for u8 {
- fn from(value: AccountState) -> Self {
- match value {
- AccountState::Uninitialized => 0,
- AccountState::Initialized => 1,
- AccountState::Frozen => 2,
+ #[inline]
+ pub fn set_native(&mut self, value: bool) {
+ self.is_native[0] = value as u8;
+ }
+
+ #[inline]
+ pub fn is_native(&self) -> bool {
+ self.is_native[0] == 1
+ }
+
+ #[inline]
+ pub fn native_amount(&self) -> Option {
+ if self.is_native() {
+ Some(u64::from_le_bytes(self.native_amount))
+ } else {
+ None
}
}
+
+ #[inline]
+ pub fn set_delegated_amount(&mut self, amount: u64) {
+ self.delegated_amount = amount.to_le_bytes();
+ }
+
+ #[inline]
+ pub fn delegated_amount(&self) -> u64 {
+ u64::from_le_bytes(self.delegated_amount)
+ }
+
+ #[inline]
+ pub fn clear_close_authority(&mut self) {
+ self.close_authority.0[0] = 0;
+ }
+
+ #[inline]
+ pub fn set_close_authority(&mut self, value: &Pubkey) {
+ self.close_authority.0[0] = 1;
+ self.close_authority.1 = *value;
+ }
+
+ #[inline]
+ pub fn close_authority(&self) -> Option<&Pubkey> {
+ if self.close_authority.0[0] == 1 {
+ Some(&self.close_authority.1)
+ } else {
+ None
+ }
+ }
+
+ #[inline(always)]
+ pub fn is_initialized(&self) -> bool {
+ self.state != AccountState::Uninitialized
+ }
+
+ #[inline(always)]
+ pub fn is_frozen(&self) -> bool {
+ self.state == AccountState::Frozen
+ }
}
diff --git a/interface/src/state/account_state.rs b/interface/src/state/account_state.rs
new file mode 100644
index 0000000..6747757
--- /dev/null
+++ b/interface/src/state/account_state.rs
@@ -0,0 +1,15 @@
+#[repr(u8)]
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum AccountState {
+ /// Account is not yet initialized
+ Uninitialized,
+
+ /// Account is initialized; the account owner and/or delegate may perform
+ /// permitted operations on this account
+ Initialized,
+
+ /// Account has been frozen by the mint freeze authority. Neither the
+ /// account owner nor the delegate are able to perform operations on
+ /// this account.
+ Frozen,
+}
diff --git a/interface/src/state/mint.rs b/interface/src/state/mint.rs
index 50896c6..1ca3d90 100644
--- a/interface/src/state/mint.rs
+++ b/interface/src/state/mint.rs
@@ -1,32 +1,157 @@
-use bytemuck::{Pod, Zeroable};
-use pinocchio::pubkey::Pubkey;
+use pinocchio::{
+ account_info::{AccountInfo, Ref},
+ program_error::ProgramError,
+ pubkey::Pubkey,
+};
-use super::{PodBool, PodCOption, PodU64};
+use crate::program::ID;
-/// Mint data.
+use super::COption;
+
+/// Internal representation of a mint data.
#[repr(C)]
-#[derive(Clone, Copy, Default, Pod, Zeroable)]
pub struct Mint {
/// Optional authority used to mint new tokens. The mint authority may only
/// be provided during mint creation. If no mint authority is present
/// then the mint has a fixed supply and no further tokens may be
/// minted.
- pub mint_authority: PodCOption,
+ pub mint_authority: COption,
/// Total supply of tokens.
- pub supply: PodU64,
+ supply: [u8; 8],
/// Number of base 10 digits to the right of the decimal place.
pub decimals: u8,
- /// Is `true` if this structure has been initialized
- pub is_initialized: PodBool,
+ /// Is `true` if this structure has been initialized.
+ is_initialized: u8,
+ // Indicates whether the freeze authority is present or not.
+ //freeze_authority_option: [u8; 4],
/// Optional authority to freeze token accounts.
- pub freeze_authority: PodCOption,
+ pub freeze_authority: COption,
}
impl Mint {
- /// Size of the `Mint` account.
- pub const LEN: usize = core::mem::size_of::();
+ /// The length of the `Mint` account data.
+ pub const LEN: usize = core::mem::size_of::();
+
+ /// Return a `Mint` from the given account info.
+ ///
+ /// This method performs owner and length validation on `AccountInfo`, safe borrowing
+ /// the account data.
+ #[inline]
+ pub fn from_account_info(account_info: &AccountInfo) -> Result][, ProgramError> {
+ if account_info.data_len() != Self::LEN {
+ return Err(ProgramError::InvalidAccountData);
+ }
+ if account_info.owner() != &ID {
+ return Err(ProgramError::InvalidAccountOwner);
+ }
+ Ok(Ref::map(account_info.try_borrow_data()?, |data| unsafe {
+ Self::from_bytes(data)
+ }))
+ }
+
+ /// Return a `Mint` from the given account info.
+ ///
+ /// This method performs owner and length validation on `AccountInfo`, but does not
+ /// perform the borrow check.
+ ///
+ /// # Safety
+ ///
+ /// The caller must ensure that it is safe to borrow the account data – e.g., there are
+ /// no mutable borrows of the account data.
+ #[inline]
+ pub unsafe fn from_account_info_unchecked(
+ account_info: &AccountInfo,
+ ) -> Result<&Self, ProgramError> {
+ if account_info.data_len() != Self::LEN {
+ return Err(ProgramError::InvalidAccountData);
+ }
+ if account_info.owner() != &ID {
+ return Err(ProgramError::InvalidAccountOwner);
+ }
+ Ok(Self::from_bytes(account_info.borrow_data_unchecked()))
+ }
+
+ /// Return a `Mint` reference from the given bytes.
+ ///
+ /// # Safety
+ ///
+ /// The caller must ensure that `bytes` contains a valid representation of `Mint`.
+ #[inline]
+ pub unsafe fn from_bytes(bytes: &[u8]) -> &Self {
+ &*(bytes.as_ptr() as *const Mint)
+ }
+
+ /// Return a mutable `Mint` reference from the given bytes.
+ ///
+ /// # Safety
+ ///
+ /// The caller must ensure that `bytes` contains a valid representation of `Mint`.
+ #[inline]
+ pub unsafe fn from_bytes_mut(bytes: &mut [u8]) -> &mut Self {
+ &mut *(bytes.as_mut_ptr() as *mut Mint)
+ }
+
+ #[inline]
+ pub fn set_supply(&mut self, supply: u64) {
+ self.supply = supply.to_le_bytes();
+ }
+
+ #[inline]
+ pub fn supply(&self) -> u64 {
+ u64::from_le_bytes(self.supply)
+ }
+
+ #[inline]
+ pub fn set_initialized(&mut self, value: bool) {
+ self.is_initialized = value as u8;
+ }
+
+ #[inline]
+ pub fn is_initialized(&self) -> bool {
+ self.is_initialized == 1
+ }
+
+ #[inline]
+ pub fn clear_mint_authority(&mut self) {
+ self.mint_authority.0[0] = 0;
+ }
+
+ #[inline]
+ pub fn set_mint_authority(&mut self, mint_authority: &Pubkey) {
+ self.mint_authority.0[0] = 1;
+ self.mint_authority.1 = *mint_authority;
+ }
+
+ #[inline]
+ pub fn mint_authority(&self) -> Option<&Pubkey> {
+ if self.mint_authority.0[0] == 1 {
+ Some(&self.mint_authority.1)
+ } else {
+ None
+ }
+ }
+
+ #[inline]
+ pub fn clear_freeze_authority(&mut self) {
+ self.freeze_authority.0[0] = 0;
+ }
+
+ #[inline]
+ pub fn set_freeze_authority(&mut self, freeze_authority: &Pubkey) {
+ self.freeze_authority.0[0] = 1;
+ self.freeze_authority.1 = *freeze_authority;
+ }
+
+ #[inline]
+ pub fn freeze_authority(&self) -> Option<&Pubkey> {
+ if self.freeze_authority.0[0] == 1 {
+ Some(&self.freeze_authority.1)
+ } else {
+ None
+ }
+ }
}
diff --git a/interface/src/state/mod.rs b/interface/src/state/mod.rs
index a0f8d9b..04c0342 100644
--- a/interface/src/state/mod.rs
+++ b/interface/src/state/mod.rs
@@ -1,162 +1,7 @@
-use std::mem::align_of;
-
-use bytemuck::{Pod, Zeroable};
-
pub mod account;
+pub mod account_state;
pub mod mint;
pub mod multisig;
-#[repr(C)]
-#[derive(Clone, Copy, Debug, Default, PartialEq)]
-pub struct PodCOption {
- /// Indicates if the option is `Some` or `None`.
- tag: [u8; 4],
-
- /// The value of the option.
- value: T,
-}
-
-impl From]