Skip to content
This repository was archived by the owner on Feb 13, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 0 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

43 changes: 20 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@
<a href="https://github.com/febo/p-token/actions/workflows/main.yml"><img src="https://img.shields.io/github/actions/workflow/status/febo/p-token/main.yml?logo=GitHub" /></a>
</p>

> [!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 &mdash; i.e., support the exact same instruction and account layouts as SPL Token, byte for byte.
Expand All @@ -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` | ✅ | | |
Expand Down
1 change: 0 additions & 1 deletion interface/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,5 @@ repository = { workspace = true }
publish = false

[dependencies]
bytemuck = { workspace = true }
pinocchio = { workspace = true }
pinocchio-pubkey = { workspace = true }
26 changes: 13 additions & 13 deletions interface/src/instruction.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand All @@ -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<Pubkey>,
freeze_authority: Option<Pubkey>,
},

/// Initializes a new account to hold tokens. If this account is associated
Expand Down Expand Up @@ -147,7 +147,7 @@ pub enum TokenInstruction<'a> {
/// The type of authority to update.
authority_type: AuthorityType,
/// The new authority
new_authority: PodCOption<Pubkey>,
new_authority: Option<Pubkey>,
},

/// Mints new tokens to an account. The native mint does not support
Expand Down Expand Up @@ -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<Pubkey>,
freeze_authority: Option<Pubkey>,
},

/// Gets the required size of an account for the given mint as a
Expand Down Expand Up @@ -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 {
Expand All @@ -506,13 +506,13 @@ impl AuthorityType {
}
}

pub fn from(index: u8) -> Self {
pub fn from(index: u8) -> Result<Self, ProgramError> {
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()),
}
}
}
207 changes: 154 additions & 53 deletions interface/src/state/account.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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<Pubkey>,
/// the amount authorized by the delegate.
delegate: COption<Pubkey>,

/// 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<PodU64>,
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<Pubkey>,
close_authority: COption<Pubkey>,
}

impl Account {
/// Size of the `Account` account.
pub const LEN: usize = core::mem::size_of::<Self>();
pub const LEN: usize = core::mem::size_of::<Account>();

/// 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<Ref<Account>, 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<u8> 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<AccountState> 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<u64> {
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
}
}
Loading