Browse Source

Add remaining instructions

febo 10 months ago
parent
commit
144bcfa27c

+ 42 - 0
p-token/src/entrypoint.rs

@@ -1,18 +1,23 @@
+use core::str;
+
 use pinocchio::{
     account_info::AccountInfo, entrypoint, program_error::ProgramError, pubkey::Pubkey,
     ProgramResult,
 };
 
 use crate::processor::{
+    amount_to_ui_amount::process_amount_to_ui_amount,
     approve::process_approve,
     approve_checked::{process_approve_checked, ApproveChecked},
     burn::process_burn,
     burn_checked::{process_burn_checked, BurnChecked},
     close_account::process_close_account,
     freeze_account::process_freeze_account,
+    get_account_data_size::process_get_account_data_size,
     initialize_account::process_initialize_account,
     initialize_account2::process_initialize_account2,
     initialize_account3::process_initialize_account3,
+    initialize_immutable_owner::process_initialize_immutable_owner,
     initialize_mint::{process_initialize_mint, InitializeMint},
     initialize_mint2::process_initialize_mint2,
     initialize_multisig::process_initialize_multisig,
@@ -25,6 +30,7 @@ use crate::processor::{
     thaw_account::process_thaw_account,
     transfer::process_transfer,
     transfer_checked::{process_transfer_checked, TransferChecked},
+    ui_amount_to_amount::process_ui_amount_to_amount,
 };
 
 entrypoint!(process_instruction);
@@ -229,6 +235,42 @@ pub fn process_instruction(
 
             process_initialize_mint2(accounts, &instruction)
         }
+        // 21 - GetAccountDataSize
+        Some((&21, _)) => {
+            #[cfg(feature = "logging")]
+            pinocchio::msg!("Instruction: GetAccountDataSize");
+
+            process_get_account_data_size(program_id, accounts)
+        }
+        // 22 - InitializeImmutableOwner
+        Some((&22, _)) => {
+            #[cfg(feature = "logging")]
+            pinocchio::msg!("Instruction: InitializeImmutableOwner");
+
+            process_initialize_immutable_owner(accounts)
+        }
+        // 23 - AmountToUiAmount
+        Some((&23, data)) => {
+            #[cfg(feature = "logging")]
+            pinocchio::msg!("Instruction: AmountToUiAmount");
+
+            let amount = u64::from_le_bytes(
+                data.try_into()
+                    .map_err(|_error| ProgramError::InvalidInstructionData)?,
+            );
+
+            process_amount_to_ui_amount(program_id, accounts, amount)
+        }
+        // 24 - UiAmountToAmount
+        Some((&24, data)) => {
+            #[cfg(feature = "logging")]
+            pinocchio::msg!("Instruction: UiAmountToAmount");
+
+            let ui_amount =
+                str::from_utf8(data).map_err(|_error| ProgramError::InvalidInstructionData)?;
+
+            process_ui_amount_to_amount(program_id, accounts, ui_amount)
+        }
         _ => Err(ProgramError::InvalidInstructionData),
     }
 }

+ 25 - 0
p-token/src/processor/amount_to_ui_amount.rs

@@ -0,0 +1,25 @@
+use pinocchio::{
+    account_info::AccountInfo, program::set_return_data, program_error::ProgramError,
+    pubkey::Pubkey, ProgramResult,
+};
+use token_interface::{error::TokenError, state::mint::Mint};
+
+use super::{amount_to_ui_amount_string_trimmed, check_account_owner};
+
+pub fn process_amount_to_ui_amount(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    amount: u64,
+) -> ProgramResult {
+    let mint_info = accounts.first().ok_or(ProgramError::NotEnoughAccountKeys)?;
+    check_account_owner(program_id, mint_info)?;
+
+    let mint =
+        bytemuck::try_from_bytes_mut::<Mint>(unsafe { mint_info.borrow_mut_data_unchecked() })
+            .map_err(|_error| TokenError::InvalidMint)?;
+
+    let ui_amount = amount_to_ui_amount_string_trimmed(amount, mint.decimals);
+    set_return_data(&ui_amount.into_bytes());
+
+    Ok(())
+}

+ 25 - 0
p-token/src/processor/get_account_data_size.rs

@@ -0,0 +1,25 @@
+use pinocchio::{
+    account_info::AccountInfo, program::set_return_data, program_error::ProgramError,
+    pubkey::Pubkey, ProgramResult,
+};
+use token_interface::state::{account::Account, mint::Mint};
+
+use super::check_account_owner;
+
+pub fn process_get_account_data_size(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+) -> ProgramResult {
+    let [mint_info, _remaning @ ..] = accounts else {
+        return Err(ProgramError::NotEnoughAccountKeys);
+    };
+
+    check_account_owner(program_id, mint_info)?;
+
+    let _ = bytemuck::try_from_bytes::<Mint>(unsafe { mint_info.borrow_data_unchecked() })
+        .map_err(|_error| ProgramError::InvalidAccountData)?;
+
+    set_return_data(&Account::LEN.to_le_bytes());
+
+    Ok(())
+}

+ 17 - 0
p-token/src/processor/initialize_immutable_owner.rs

@@ -0,0 +1,17 @@
+use pinocchio::{account_info::AccountInfo, msg, program_error::ProgramError, ProgramResult};
+use token_interface::{error::TokenError, state::account::Account};
+
+pub fn process_initialize_immutable_owner(accounts: &[AccountInfo]) -> ProgramResult {
+    let token_account_info = accounts.first().ok_or(ProgramError::NotEnoughAccountKeys)?;
+
+    let account = bytemuck::try_from_bytes_mut::<Account>(unsafe {
+        token_account_info.borrow_mut_data_unchecked()
+    })
+    .map_err(|_error| ProgramError::InvalidAccountData)?;
+
+    if account.is_initialized() {
+        return Err(TokenError::AlreadyInUse.into());
+    }
+    msg!("Please upgrade to SPL Token 2022 for immutable owner support");
+    Ok(())
+}

+ 58 - 0
p-token/src/processor/mod.rs

@@ -6,15 +6,18 @@ use token_interface::{
     state::multisig::{Multisig, MAX_SIGNERS},
 };
 
+pub mod amount_to_ui_amount;
 pub mod approve;
 pub mod approve_checked;
 pub mod burn;
 pub mod burn_checked;
 pub mod close_account;
 pub mod freeze_account;
+pub mod get_account_data_size;
 pub mod initialize_account;
 pub mod initialize_account2;
 pub mod initialize_account3;
+pub mod initialize_immutable_owner;
 pub mod initialize_mint;
 pub mod initialize_mint2;
 pub mod initialize_multisig;
@@ -27,6 +30,7 @@ pub mod sync_native;
 pub mod thaw_account;
 pub mod transfer;
 pub mod transfer_checked;
+pub mod ui_amount_to_amount;
 // Private processor to toggle the account state. This logic is reused by the
 // freeze and thaw account instructions.
 mod toggle_account_state;
@@ -92,3 +96,57 @@ pub fn validate_owner(
 
     Ok(())
 }
+
+/// Convert a raw amount to its UI representation using the given decimals field
+/// Excess zeroes or unneeded decimal point are trimmed.
+#[inline(always)]
+pub fn amount_to_ui_amount_string_trimmed(amount: u64, decimals: u8) -> String {
+    let mut s = amount_to_ui_amount_string(amount, decimals);
+    if decimals > 0 {
+        let zeros_trimmed = s.trim_end_matches('0');
+        s = zeros_trimmed.trim_end_matches('.').to_string();
+    }
+    s
+}
+
+/// Convert a raw amount to its UI representation (using the decimals field
+/// defined in its mint)
+#[inline(always)]
+pub fn amount_to_ui_amount_string(amount: u64, decimals: u8) -> String {
+    let decimals = decimals as usize;
+    if decimals > 0 {
+        // Left-pad zeros to decimals + 1, so we at least have an integer zero
+        let mut s = format!("{:01$}", amount, decimals + 1);
+        // Add the decimal point (Sorry, "," locales!)
+        s.insert(s.len() - decimals, '.');
+        s
+    } else {
+        amount.to_string()
+    }
+}
+
+/// Try to convert a UI representation of a token amount to its raw amount using
+/// the given decimals field
+pub fn try_ui_amount_into_amount(ui_amount: String, decimals: u8) -> Result<u64, ProgramError> {
+    let decimals = decimals as usize;
+    let mut parts = ui_amount.split('.');
+    // splitting a string, even an empty one, will always yield an iterator of at
+    // least length == 1
+    let mut amount_str = parts.next().unwrap().to_string();
+    let after_decimal = parts.next().unwrap_or("");
+    let after_decimal = after_decimal.trim_end_matches('0');
+    if (amount_str.is_empty() && after_decimal.is_empty())
+        || parts.next().is_some()
+        || after_decimal.len() > decimals
+    {
+        return Err(ProgramError::InvalidArgument);
+    }
+
+    amount_str.push_str(after_decimal);
+    for _ in 0..decimals.saturating_sub(after_decimal.len()) {
+        amount_str.push('0');
+    }
+    amount_str
+        .parse::<u64>()
+        .map_err(|_| ProgramError::InvalidArgument)
+}

+ 1 - 3
p-token/src/processor/sync_native.rs

@@ -6,9 +6,7 @@ use token_interface::{error::TokenError, state::account::Account};
 use super::check_account_owner;
 
 pub fn process_sync_native(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
-    let [native_account_info, _remaning @ ..] = accounts else {
-        return Err(ProgramError::NotEnoughAccountKeys);
-    };
+    let native_account_info = accounts.first().ok_or(ProgramError::NotEnoughAccountKeys)?;
 
     check_account_owner(program_id, native_account_info)?;
 

+ 25 - 0
p-token/src/processor/ui_amount_to_amount.rs

@@ -0,0 +1,25 @@
+use pinocchio::{
+    account_info::AccountInfo, program::set_return_data, program_error::ProgramError,
+    pubkey::Pubkey, ProgramResult,
+};
+use token_interface::{error::TokenError, state::mint::Mint};
+
+use super::{check_account_owner, try_ui_amount_into_amount};
+
+pub fn process_ui_amount_to_amount(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    ui_amount: &str,
+) -> ProgramResult {
+    let mint_info = accounts.first().ok_or(ProgramError::NotEnoughAccountKeys)?;
+    check_account_owner(program_id, mint_info)?;
+
+    let mint =
+        bytemuck::try_from_bytes_mut::<Mint>(unsafe { mint_info.borrow_mut_data_unchecked() })
+            .map_err(|_error| TokenError::InvalidMint)?;
+
+    let amount = try_ui_amount_into_amount(ui_amount.to_string(), mint.decimals)?;
+    set_return_data(&amount.to_le_bytes());
+
+    Ok(())
+}