Skip to content
This repository was archived by the owner on Feb 13, 2025. It is now read-only.

Commit 080b907

Browse files
authored
processor: Split processor (#2)
* Use instruction args * Refactor processor * Update compute table * Remove bytemuck * Move state to interface crate * Update lib comment * Update CU values
1 parent a82f2ac commit 080b907

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1104
-1143
lines changed

Cargo.lock

Lines changed: 0 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@
1212
<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>
1313
</p>
1414

15-
> [!WARNING]
16-
> The program is not yet fully-optimized. There are still opportunities to improve the compute units consumption.
17-
1815
## Overview
1916

2017
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.
@@ -23,27 +20,27 @@ This repository contains a **proof-of-concept** of a reimplementation of the SPL
2320

2421
| Instruction | Completed | CU (`p-token`) | CU (`spl-token`) |
2522
|----------------------------|-----------|----------------|------------------|
26-
| `InitializeMint` || 389 | 2967 |
27-
| `InitializeAccount` || 409 | 4527 |
28-
| `InitializeMultisig` || 458 | 2973 |
29-
| `Transfer` || 194 | 4645 |
30-
| `Approve` || 151 | 2904 |
31-
| `Revoke` || 93 | 2677 |
32-
| `SetAuthority` || 171 | 3167 |
33-
| `MintTo` || 196 | 4538 |
34-
| `Burn` || 184 | 4753 |
35-
| `CloseAccount` || 163 | 2916 |
36-
| `FreezeAccount` || 131 | 4265 |
37-
| `ThawAccount` || 132 | 4267 |
38-
| `TransferChecked` || 207 | 6201 |
39-
| `ApproveChecked` || 166 | 4459 |
40-
| `MintToChecked` || 180 | 4546 |
41-
| `BurnChecked` || 166 | 4755 |
42-
| `InitializeAccount2` || 394 | 4388 |
23+
| `InitializeMint` || 343 | 2967 |
24+
| `InitializeAccount` || 416 | 4527 |
25+
| `InitializeMultisig` || 499 | 2973 |
26+
| `Transfer` || 140 | 4645 |
27+
| `Approve` || 133 | 2904 |
28+
| `Revoke` || 106 | 2677 |
29+
| `SetAuthority` || 142 | 3167 |
30+
| `MintTo` || 143 | 4538 |
31+
| `Burn` || 175 | 4753 |
32+
| `CloseAccount` || 147 | 2916 |
33+
| `FreezeAccount` || 141 | 4265 |
34+
| `ThawAccount` || 142 | 4267 |
35+
| `TransferChecked` || 211 | 6201 |
36+
| `ApproveChecked` || 169 | 4459 |
37+
| `MintToChecked` || 178 | 4546 |
38+
| `BurnChecked` || 181 | 4755 |
39+
| `InitializeAccount2` || 399 | 4388 |
4340
| `SyncNative` || | |
44-
| `InitializeAccount3` || 523 | 4240 |
45-
| `InitializeMultisig2` || 563 | 2826 |
46-
| `InitializeMint2` || 500 | 2827 |
41+
| `InitializeAccount3` || 508 | 4240 |
42+
| `InitializeMultisig2` || 579 | 2826 |
43+
| `InitializeMint2` || 477 | 2827 |
4744
| `GetAccountDataSize` || | |
4845
| `InitializeImmutableOwner` || | |
4946
| `AmountToUiAmount` || | |

interface/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,5 @@ repository = { workspace = true }
88
publish = false
99

1010
[dependencies]
11-
bytemuck = { workspace = true }
1211
pinocchio = { workspace = true }
1312
pinocchio-pubkey = { workspace = true }

interface/src/instruction.rs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
//! Instruction types
1+
//! Instruction types.
22
3-
use pinocchio::pubkey::Pubkey;
3+
use pinocchio::{program_error::ProgramError, pubkey::Pubkey};
44

5-
use crate::state::PodCOption;
5+
use crate::error::TokenError;
66

77
/// Instructions supported by the token program.
88
#[repr(C)]
@@ -27,7 +27,7 @@ pub enum TokenInstruction<'a> {
2727
/// The authority/multisignature to mint tokens.
2828
mint_authority: Pubkey,
2929
/// The freeze authority/multisignature of the mint.
30-
freeze_authority: PodCOption<Pubkey>,
30+
freeze_authority: Option<Pubkey>,
3131
},
3232

3333
/// Initializes a new account to hold tokens. If this account is associated
@@ -147,7 +147,7 @@ pub enum TokenInstruction<'a> {
147147
/// The type of authority to update.
148148
authority_type: AuthorityType,
149149
/// The new authority
150-
new_authority: PodCOption<Pubkey>,
150+
new_authority: Option<Pubkey>,
151151
},
152152

153153
/// Mints new tokens to an account. The native mint does not support
@@ -416,7 +416,7 @@ pub enum TokenInstruction<'a> {
416416
/// The authority/multisignature to mint tokens.
417417
mint_authority: Pubkey,
418418
/// The freeze authority/multisignature of the mint.
419-
freeze_authority: PodCOption<Pubkey>,
419+
freeze_authority: Option<Pubkey>,
420420
},
421421

422422
/// Gets the required size of an account for the given mint as a
@@ -482,7 +482,7 @@ pub enum TokenInstruction<'a> {
482482
// token/js/src/instructions/types.ts to maintain @solana/spl-token compatibility
483483
}
484484

485-
/// Specifies the authority type for SetAuthority instructions
485+
/// Specifies the authority type for `SetAuthority` instructions
486486
#[repr(u8)]
487487
#[derive(Clone, Debug, PartialEq)]
488488
pub enum AuthorityType {
@@ -506,13 +506,13 @@ impl AuthorityType {
506506
}
507507
}
508508

509-
pub fn from(index: u8) -> Self {
509+
pub fn from(index: u8) -> Result<Self, ProgramError> {
510510
match index {
511-
0 => AuthorityType::MintTokens,
512-
1 => AuthorityType::FreezeAccount,
513-
2 => AuthorityType::AccountOwner,
514-
3 => AuthorityType::CloseAccount,
515-
_ => panic!("invalid authority type: {index}"),
511+
0 => Ok(AuthorityType::MintTokens),
512+
1 => Ok(AuthorityType::FreezeAccount),
513+
2 => Ok(AuthorityType::AccountOwner),
514+
3 => Ok(AuthorityType::CloseAccount),
515+
_ => Err(TokenError::InvalidInstruction.into()),
516516
}
517517
}
518518
}

interface/src/state/account.rs

Lines changed: 154 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
use bytemuck::{Pod, Zeroable};
2-
use pinocchio::pubkey::Pubkey;
1+
use pinocchio::{
2+
account_info::{AccountInfo, Ref},
3+
program_error::ProgramError,
4+
pubkey::Pubkey,
5+
};
36

4-
use super::{PodCOption, PodU64};
7+
use crate::program::ID;
58

6-
/// Account data.
9+
use super::{account_state::AccountState, COption};
10+
11+
/// Internal representation of a token account data.
712
#[repr(C)]
8-
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
913
pub struct Account {
1014
/// The mint associated with this account
1115
pub mint: Pubkey,
@@ -14,82 +18,179 @@ pub struct Account {
1418
pub owner: Pubkey,
1519

1620
/// The amount of tokens this account holds.
17-
pub amount: PodU64,
21+
amount: [u8; 8],
1822

1923
/// If `delegate` is `Some` then `delegated_amount` represents
20-
/// the amount authorized by the delegate
21-
pub delegate: PodCOption<Pubkey>,
24+
/// the amount authorized by the delegate.
25+
delegate: COption<Pubkey>,
26+
27+
/// The account's state.
28+
pub state: AccountState,
2229

23-
/// The account's state
24-
pub state: u8,
30+
/// Indicates whether this account represents a native token or not.
31+
is_native: [u8; 4],
2532

2633
/// If is_native.is_some, this is a native token, and the value logs the
2734
/// rent-exempt reserve. An Account is required to be rent-exempt, so
2835
/// the value is used by the Processor to ensure that wrapped SOL
2936
/// accounts do not drop below this threshold.
30-
pub is_native: PodCOption<PodU64>,
37+
native_amount: [u8; 8],
3138

32-
/// The amount delegated
33-
pub delegated_amount: PodU64,
39+
/// The amount delegated.
40+
delegated_amount: [u8; 8],
3441

3542
/// Optional authority to close the account.
36-
pub close_authority: PodCOption<Pubkey>,
43+
close_authority: COption<Pubkey>,
3744
}
3845

3946
impl Account {
40-
/// Size of the `Account` account.
41-
pub const LEN: usize = core::mem::size_of::<Self>();
47+
pub const LEN: usize = core::mem::size_of::<Account>();
4248

49+
/// Return a `TokenAccount` from the given account info.
50+
///
51+
/// This method performs owner and length validation on `AccountInfo`, safe borrowing
52+
/// the account data.
4353
#[inline]
44-
pub fn is_initialized(&self) -> bool {
45-
self.state != AccountState::Uninitialized as u8
54+
pub fn from_account_info(account_info: &AccountInfo) -> Result<Ref<Account>, ProgramError> {
55+
if account_info.data_len() != Self::LEN {
56+
return Err(ProgramError::InvalidAccountData);
57+
}
58+
if account_info.owner() != &ID {
59+
return Err(ProgramError::InvalidAccountData);
60+
}
61+
Ok(Ref::map(account_info.try_borrow_data()?, |data| unsafe {
62+
Self::from_bytes(data)
63+
}))
4664
}
4765

66+
/// Return a `TokenAccount` from the given account info.
67+
///
68+
/// This method performs owner and length validation on `AccountInfo`, but does not
69+
/// perform the borrow check.
70+
///
71+
/// # Safety
72+
///
73+
/// The caller must ensure that it is safe to borrow the account data – e.g., there are
74+
/// no mutable borrows of the account data.
4875
#[inline]
49-
pub fn is_frozen(&self) -> bool {
50-
self.state == AccountState::Frozen as u8
76+
pub unsafe fn from_account_info_unchecked(
77+
account_info: &AccountInfo,
78+
) -> Result<&Account, ProgramError> {
79+
if account_info.data_len() != Self::LEN {
80+
return Err(ProgramError::InvalidAccountData);
81+
}
82+
if account_info.owner() != &ID {
83+
return Err(ProgramError::InvalidAccountData);
84+
}
85+
Ok(Self::from_bytes(account_info.borrow_data_unchecked()))
86+
}
87+
88+
/// Return a `TokenAccount` from the given bytes.
89+
///
90+
/// # Safety
91+
///
92+
/// The caller must ensure that `bytes` contains a valid representation of `TokenAccount`.
93+
#[inline(always)]
94+
pub unsafe fn from_bytes(bytes: &[u8]) -> &Self {
95+
&*(bytes.as_ptr() as *const Account)
5196
}
5297

98+
/// Return a mutable `Mint` reference from the given bytes.
99+
///
100+
/// # Safety
101+
///
102+
/// The caller must ensure that `bytes` contains a valid representation of `Mint`.
103+
#[inline(always)]
104+
pub unsafe fn from_bytes_mut(bytes: &mut [u8]) -> &mut Self {
105+
&mut *(bytes.as_mut_ptr() as *mut Account)
106+
}
107+
108+
#[inline]
109+
pub fn set_amount(&mut self, amount: u64) {
110+
self.amount = amount.to_le_bytes();
111+
}
112+
113+
#[inline]
53114
pub fn amount(&self) -> u64 {
54-
self.amount.into()
115+
u64::from_le_bytes(self.amount)
55116
}
56-
}
57117

58-
/// Account state.
59-
#[repr(u8)]
60-
#[derive(Clone, Copy, Debug, Default, PartialEq)]
61-
pub enum AccountState {
62-
/// Account is not yet initialized
63-
#[default]
64-
Uninitialized,
65-
66-
/// Account is initialized; the account owner and/or delegate may perform
67-
/// permitted operations on this account
68-
Initialized,
69-
70-
/// Account has been frozen by the mint freeze authority. Neither the
71-
/// account owner nor the delegate are able to perform operations on
72-
/// this account.
73-
Frozen,
74-
}
118+
#[inline]
119+
pub fn clear_delegate(&mut self) {
120+
self.delegate.0[0] = 0;
121+
}
75122

76-
impl From<u8> for AccountState {
77-
fn from(value: u8) -> Self {
78-
match value {
79-
0 => AccountState::Uninitialized,
80-
1 => AccountState::Initialized,
81-
2 => AccountState::Frozen,
82-
_ => panic!("invalid account state value: {value}"),
123+
#[inline]
124+
pub fn set_delegate(&mut self, delegate: &Pubkey) {
125+
self.delegate.0[0] = 1;
126+
self.delegate.1 = *delegate;
127+
}
128+
129+
#[inline]
130+
pub fn delegate(&self) -> Option<&Pubkey> {
131+
if self.delegate.0[0] == 1 {
132+
Some(&self.delegate.1)
133+
} else {
134+
None
83135
}
84136
}
85-
}
86137

87-
impl From<AccountState> for u8 {
88-
fn from(value: AccountState) -> Self {
89-
match value {
90-
AccountState::Uninitialized => 0,
91-
AccountState::Initialized => 1,
92-
AccountState::Frozen => 2,
138+
#[inline]
139+
pub fn set_native(&mut self, value: bool) {
140+
self.is_native[0] = value as u8;
141+
}
142+
143+
#[inline]
144+
pub fn is_native(&self) -> bool {
145+
self.is_native[0] == 1
146+
}
147+
148+
#[inline]
149+
pub fn native_amount(&self) -> Option<u64> {
150+
if self.is_native() {
151+
Some(u64::from_le_bytes(self.native_amount))
152+
} else {
153+
None
93154
}
94155
}
156+
157+
#[inline]
158+
pub fn set_delegated_amount(&mut self, amount: u64) {
159+
self.delegated_amount = amount.to_le_bytes();
160+
}
161+
162+
#[inline]
163+
pub fn delegated_amount(&self) -> u64 {
164+
u64::from_le_bytes(self.delegated_amount)
165+
}
166+
167+
#[inline]
168+
pub fn clear_close_authority(&mut self) {
169+
self.close_authority.0[0] = 0;
170+
}
171+
172+
#[inline]
173+
pub fn set_close_authority(&mut self, value: &Pubkey) {
174+
self.close_authority.0[0] = 1;
175+
self.close_authority.1 = *value;
176+
}
177+
178+
#[inline]
179+
pub fn close_authority(&self) -> Option<&Pubkey> {
180+
if self.close_authority.0[0] == 1 {
181+
Some(&self.close_authority.1)
182+
} else {
183+
None
184+
}
185+
}
186+
187+
#[inline(always)]
188+
pub fn is_initialized(&self) -> bool {
189+
self.state != AccountState::Uninitialized
190+
}
191+
192+
#[inline(always)]
193+
pub fn is_frozen(&self) -> bool {
194+
self.state == AccountState::Frozen
195+
}
95196
}

0 commit comments

Comments
 (0)