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

Commit 7e0e82a

Browse files
committed
Add remaining instructions
1 parent 115c988 commit 7e0e82a

File tree

8 files changed

+198
-8
lines changed

8 files changed

+198
-8
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,14 @@ This repository contains a **proof-of-concept** of a reimplementation of the SPL
4040
| `MintToChecked` || 218 | 4546 |
4141
| `BurnChecked` || 201 | 4755 |
4242
| `InitializeAccount2` || 431 | 4388 |
43-
| `SyncNative` | | | |
43+
| `SyncNative` | | | |
4444
| `InitializeAccount3` || 566 | 4240 |
4545
| `InitializeMultisig2` || 601 | 2826 |
4646
| `InitializeMint2` || 529 | 2827 |
47-
| `GetAccountDataSize` | | | |
48-
| `InitializeImmutableOwner` | | | |
49-
| `AmountToUiAmount` | | | |
50-
| `UiAmountToAmount` | | | |
47+
| `GetAccountDataSize` | | | |
48+
| `InitializeImmutableOwner` | | | |
49+
| `AmountToUiAmount` | | | |
50+
| `UiAmountToAmount` | | | |
5151

5252
> Tests were run using Solana `v2.1.0`.
5353

program/src/entrypoint.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
1+
use core::str;
2+
13
use pinocchio::{
24
account_info::AccountInfo, entrypoint, program_error::ProgramError, pubkey::Pubkey,
35
ProgramResult,
46
};
57

68
use crate::processor::{
9+
amount_to_ui_amount::process_amount_to_ui_amount,
710
approve::process_approve,
811
approve_checked::{process_approve_checked, ApproveChecked},
912
burn::process_burn,
1013
burn_checked::{process_burn_checked, BurnChecked},
1114
close_account::process_close_account,
1215
freeze_account::process_freeze_account,
16+
get_account_data_size::process_get_account_data_size,
1317
initialize_account::process_initialize_account,
1418
initialize_account2::process_initialize_account2,
1519
initialize_account3::process_initialize_account3,
20+
initialize_immutable_owner::process_initialize_immutable_owner,
1621
initialize_mint::{process_initialize_mint, InitializeMint},
1722
initialize_mint2::process_initialize_mint2,
1823
initialize_multisig::process_initialize_multisig,
@@ -25,6 +30,7 @@ use crate::processor::{
2530
thaw_account::process_thaw_account,
2631
transfer::process_transfer,
2732
transfer_checked::{process_transfer_checked, TransferChecked},
33+
ui_amount_to_amount::process_ui_amount_to_amount,
2834
};
2935

3036
entrypoint!(process_instruction);
@@ -229,6 +235,42 @@ pub fn process_instruction(
229235

230236
process_initialize_mint2(accounts, &instruction)
231237
}
238+
// 21 - GetAccountDataSize
239+
Some((&21, _)) => {
240+
#[cfg(feature = "logging")]
241+
pinocchio::msg!("Instruction: GetAccountDataSize");
242+
243+
process_get_account_data_size(program_id, accounts)
244+
}
245+
// 22 - InitializeImmutableOwner
246+
Some((&22, _)) => {
247+
#[cfg(feature = "logging")]
248+
pinocchio::msg!("Instruction: InitializeImmutableOwner");
249+
250+
process_initialize_immutable_owner(accounts)
251+
}
252+
// 23 - AmountToUiAmount
253+
Some((&23, data)) => {
254+
#[cfg(feature = "logging")]
255+
pinocchio::msg!("Instruction: AmountToUiAmount");
256+
257+
let amount = u64::from_le_bytes(
258+
data.try_into()
259+
.map_err(|_error| ProgramError::InvalidInstructionData)?,
260+
);
261+
262+
process_amount_to_ui_amount(program_id, accounts, amount)
263+
}
264+
// 24 - UiAmountToAmount
265+
Some((&24, data)) => {
266+
#[cfg(feature = "logging")]
267+
pinocchio::msg!("Instruction: UiAmountToAmount");
268+
269+
let ui_amount =
270+
str::from_utf8(data).map_err(|_error| ProgramError::InvalidInstructionData)?;
271+
272+
process_ui_amount_to_amount(program_id, accounts, ui_amount)
273+
}
232274
_ => Err(ProgramError::InvalidInstructionData),
233275
}
234276
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
use pinocchio::{
2+
account_info::AccountInfo, program::set_return_data, program_error::ProgramError,
3+
pubkey::Pubkey, ProgramResult,
4+
};
5+
use token_interface::{error::TokenError, state::mint::Mint};
6+
7+
use super::{amount_to_ui_amount_string_trimmed, check_account_owner};
8+
9+
pub fn process_amount_to_ui_amount(
10+
program_id: &Pubkey,
11+
accounts: &[AccountInfo],
12+
amount: u64,
13+
) -> ProgramResult {
14+
let mint_info = accounts.first().ok_or(ProgramError::NotEnoughAccountKeys)?;
15+
check_account_owner(program_id, mint_info)?;
16+
17+
let mint =
18+
bytemuck::try_from_bytes_mut::<Mint>(unsafe { mint_info.borrow_mut_data_unchecked() })
19+
.map_err(|_error| TokenError::InvalidMint)?;
20+
21+
let ui_amount = amount_to_ui_amount_string_trimmed(amount, mint.decimals);
22+
set_return_data(&ui_amount.into_bytes());
23+
24+
Ok(())
25+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
use pinocchio::{
2+
account_info::AccountInfo, program::set_return_data, program_error::ProgramError,
3+
pubkey::Pubkey, ProgramResult,
4+
};
5+
use token_interface::state::{account::Account, mint::Mint};
6+
7+
use super::check_account_owner;
8+
9+
pub fn process_get_account_data_size(
10+
program_id: &Pubkey,
11+
accounts: &[AccountInfo],
12+
) -> ProgramResult {
13+
let [mint_info, _remaning @ ..] = accounts else {
14+
return Err(ProgramError::NotEnoughAccountKeys);
15+
};
16+
17+
check_account_owner(program_id, mint_info)?;
18+
19+
let _ = bytemuck::try_from_bytes::<Mint>(unsafe { mint_info.borrow_data_unchecked() })
20+
.map_err(|_error| ProgramError::InvalidAccountData)?;
21+
22+
set_return_data(&Account::LEN.to_le_bytes());
23+
24+
Ok(())
25+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use pinocchio::{account_info::AccountInfo, msg, program_error::ProgramError, ProgramResult};
2+
use token_interface::{error::TokenError, state::account::Account};
3+
4+
pub fn process_initialize_immutable_owner(accounts: &[AccountInfo]) -> ProgramResult {
5+
let token_account_info = accounts.first().ok_or(ProgramError::NotEnoughAccountKeys)?;
6+
7+
let account = bytemuck::try_from_bytes_mut::<Account>(unsafe {
8+
token_account_info.borrow_mut_data_unchecked()
9+
})
10+
.map_err(|_error| ProgramError::InvalidAccountData)?;
11+
12+
if account.is_initialized() {
13+
return Err(TokenError::AlreadyInUse.into());
14+
}
15+
msg!("Please upgrade to SPL Token 2022 for immutable owner support");
16+
Ok(())
17+
}

program/src/processor/mod.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,18 @@ use token_interface::{
66
state::multisig::{Multisig, MAX_SIGNERS},
77
};
88

9+
pub mod amount_to_ui_amount;
910
pub mod approve;
1011
pub mod approve_checked;
1112
pub mod burn;
1213
pub mod burn_checked;
1314
pub mod close_account;
1415
pub mod freeze_account;
16+
pub mod get_account_data_size;
1517
pub mod initialize_account;
1618
pub mod initialize_account2;
1719
pub mod initialize_account3;
20+
pub mod initialize_immutable_owner;
1821
pub mod initialize_mint;
1922
pub mod initialize_mint2;
2023
pub mod initialize_multisig;
@@ -27,6 +30,7 @@ pub mod sync_native;
2730
pub mod thaw_account;
2831
pub mod transfer;
2932
pub mod transfer_checked;
33+
pub mod ui_amount_to_amount;
3034
// Private processor to toggle the account state. This logic is reused by the
3135
// freeze and thaw account instructions.
3236
mod toggle_account_state;
@@ -92,3 +96,57 @@ pub fn validate_owner(
9296

9397
Ok(())
9498
}
99+
100+
/// Convert a raw amount to its UI representation using the given decimals field
101+
/// Excess zeroes or unneeded decimal point are trimmed.
102+
#[inline(always)]
103+
pub fn amount_to_ui_amount_string_trimmed(amount: u64, decimals: u8) -> String {
104+
let mut s = amount_to_ui_amount_string(amount, decimals);
105+
if decimals > 0 {
106+
let zeros_trimmed = s.trim_end_matches('0');
107+
s = zeros_trimmed.trim_end_matches('.').to_string();
108+
}
109+
s
110+
}
111+
112+
/// Convert a raw amount to its UI representation (using the decimals field
113+
/// defined in its mint)
114+
#[inline(always)]
115+
pub fn amount_to_ui_amount_string(amount: u64, decimals: u8) -> String {
116+
let decimals = decimals as usize;
117+
if decimals > 0 {
118+
// Left-pad zeros to decimals + 1, so we at least have an integer zero
119+
let mut s = format!("{:01$}", amount, decimals + 1);
120+
// Add the decimal point (Sorry, "," locales!)
121+
s.insert(s.len() - decimals, '.');
122+
s
123+
} else {
124+
amount.to_string()
125+
}
126+
}
127+
128+
/// Try to convert a UI representation of a token amount to its raw amount using
129+
/// the given decimals field
130+
pub fn try_ui_amount_into_amount(ui_amount: String, decimals: u8) -> Result<u64, ProgramError> {
131+
let decimals = decimals as usize;
132+
let mut parts = ui_amount.split('.');
133+
// splitting a string, even an empty one, will always yield an iterator of at
134+
// least length == 1
135+
let mut amount_str = parts.next().unwrap().to_string();
136+
let after_decimal = parts.next().unwrap_or("");
137+
let after_decimal = after_decimal.trim_end_matches('0');
138+
if (amount_str.is_empty() && after_decimal.is_empty())
139+
|| parts.next().is_some()
140+
|| after_decimal.len() > decimals
141+
{
142+
return Err(ProgramError::InvalidArgument);
143+
}
144+
145+
amount_str.push_str(after_decimal);
146+
for _ in 0..decimals.saturating_sub(after_decimal.len()) {
147+
amount_str.push('0');
148+
}
149+
amount_str
150+
.parse::<u64>()
151+
.map_err(|_| ProgramError::InvalidArgument)
152+
}

program/src/processor/sync_native.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ use token_interface::{error::TokenError, state::account::Account};
66
use super::check_account_owner;
77

88
pub fn process_sync_native(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
9-
let [native_account_info, _remaning @ ..] = accounts else {
10-
return Err(ProgramError::NotEnoughAccountKeys);
11-
};
9+
let native_account_info = accounts.first().ok_or(ProgramError::NotEnoughAccountKeys)?;
1210

1311
check_account_owner(program_id, native_account_info)?;
1412

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
use pinocchio::{
2+
account_info::AccountInfo, program::set_return_data, program_error::ProgramError,
3+
pubkey::Pubkey, ProgramResult,
4+
};
5+
use token_interface::{error::TokenError, state::mint::Mint};
6+
7+
use super::{check_account_owner, try_ui_amount_into_amount};
8+
9+
pub fn process_ui_amount_to_amount(
10+
program_id: &Pubkey,
11+
accounts: &[AccountInfo],
12+
ui_amount: &str,
13+
) -> ProgramResult {
14+
let mint_info = accounts.first().ok_or(ProgramError::NotEnoughAccountKeys)?;
15+
check_account_owner(program_id, mint_info)?;
16+
17+
let mint =
18+
bytemuck::try_from_bytes_mut::<Mint>(unsafe { mint_info.borrow_mut_data_unchecked() })
19+
.map_err(|_error| TokenError::InvalidMint)?;
20+
21+
let amount = try_ui_amount_into_amount(ui_amount.to_string(), mint.decimals)?;
22+
set_return_data(&amount.to_le_bytes());
23+
24+
Ok(())
25+
}

0 commit comments

Comments
 (0)