diff --git a/Cargo.lock b/Cargo.lock index 1f4a622..4d27b01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2609,15 +2609,13 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pinocchio" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2999d06e0c7b659daa8d41d56d906c26531e551d22e49c044339024d3e195ebc" +version = "0.7.1" +source = "git+https://github.com/febo/pinocchio.git?branch=febo/close-unstable#1aa533f55720e83451df7fb7e04507264e24190f" [[package]] name = "pinocchio-log" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95bd1d0d8a24f2637cb6748232912b6bae67b643b7ca903c68e670e704cd0652" +source = "git+https://github.com/febo/pinocchio.git?branch=febo/close-unstable#1aa533f55720e83451df7fb7e04507264e24190f" dependencies = [ "pinocchio-log-macro", ] @@ -2625,8 +2623,7 @@ dependencies = [ [[package]] name = "pinocchio-log-macro" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "657779675430389ebbf00b8a5b6325e64157795ac61702fadf8756ac8806a50e" +source = "git+https://github.com/febo/pinocchio.git?branch=febo/close-unstable#1aa533f55720e83451df7fb7e04507264e24190f" dependencies = [ "quote", "regex", @@ -2636,8 +2633,7 @@ dependencies = [ [[package]] name = "pinocchio-pubkey" version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "103d69776a0c585bb01b344d641346593adb4496120796df229722a9bab2db68" +source = "git+https://github.com/febo/pinocchio.git?branch=febo/close-unstable#1aa533f55720e83451df7fb7e04507264e24190f" dependencies = [ "five8_const", "pinocchio", @@ -5239,7 +5235,6 @@ dependencies = [ "assert_matches", "pinocchio", "pinocchio-log", - "pinocchio-pubkey", "solana-program-test", "solana-sdk", "spl-token", diff --git a/Cargo.toml b/Cargo.toml index 4decf81..283b708 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,6 @@ license = "Apache-2.0" repository = "https://github.com/febo/token" [workspace.dependencies] -pinocchio = "0.7.0" -pinocchio-log = "0.3.0" -pinocchio-pubkey = "0.2.2" +pinocchio = { version = "0.7", git = "https://github.com/febo/pinocchio.git", branch = "febo/close-unstable" } +pinocchio-log = { version = "0.3", git = "https://github.com/febo/pinocchio.git", branch = "febo/close-unstable" } +pinocchio-pubkey = { version = "0.2", git = "https://github.com/febo/pinocchio.git", branch = "febo/close-unstable" } diff --git a/README.md b/README.md index 85cbde1..284cb24 100644 --- a/README.md +++ b/README.md @@ -34,30 +34,30 @@ This repository contains a **proof-of-concept** of a reimplementation of the SPL | Instruction | Completed | CU (`p-token`) | CU (`spl-token`) | |----------------------------|-----------|----------------|------------------| | `InitializeMint` | ✅ | 100 | 2967 | -| `InitializeAccount` | ✅ | 170 | 4527 | -| `InitializeMultisig` | ✅ | 190 | 2973 | -| `Transfer` | ✅ | 153 | 4645 | -| `Approve` | ✅ | 124 | 2904 | +| `InitializeAccount` | ✅ | 185 | 4527 | +| `InitializeMultisig` | ✅ | 204 | 2973 | +| `Transfer` | ✅ | 155 | 4645 | +| `Approve` | ✅ | 122 | 2904 | | `Revoke` | ✅ | 97 | 2677 | -| `SetAuthority` | ✅ | 126 | 3167 | -| `MintTo` | ✅ | 154 | 4538 | +| `SetAuthority` | ✅ | 127 | 3167 | +| `MintTo` | ✅ | 155 | 4538 | | `Burn` | ✅ | 168 | 4753 | -| `CloseAccount` | ✅ | 147 | 2916 | +| `CloseAccount` | ✅ | 154 | 2916 | | `FreezeAccount` | ✅ | 136 | 4265 | | `ThawAccount` | ✅ | 136 | 4267 | -| `TransferChecked` | ✅ | 206 | 6201 | +| `TransferChecked` | ✅ | 204 | 6201 | | `ApproveChecked` | ✅ | 162 | 4459 | | `MintToChecked` | ✅ | 164 | 4546 | -| `BurnChecked` | ✅ | 170 | 4755 | -| `InitializeAccount2` | ✅ | 150 | 4388 | +| `BurnChecked` | ✅ | 169 | 4755 | +| `InitializeAccount2` | ✅ | 164 | 4388 | | `SyncNative` | ✅ | | | | `InitializeAccount3` | ✅ | 272 | 4240 | | `InitializeMultisig2` | ✅ | 319 | 2826 | | `InitializeMint2` | ✅ | 234 | 2827 | | `GetAccountDataSize` | ✅ | | | | `InitializeImmutableOwner` | ✅ | | | -| `AmountToUiAmount` | ✅ | 483 | 2501 | -| `UiAmountToAmount` | ✅ | 873 | 3161 | +| `AmountToUiAmount` | ✅ | 503 | 2501 | +| `UiAmountToAmount` | ✅ | 875 | 3161 | > Tests were run using Solana `v2.1.0`. diff --git a/program/Cargo.toml b/program/Cargo.toml index 0ef9320..d527968 100644 --- a/program/Cargo.toml +++ b/program/Cargo.toml @@ -20,7 +20,6 @@ test-sbf = [] [dependencies] pinocchio = { workspace = true } pinocchio-log = { workspace = true } -pinocchio-pubkey = { workspace = true } token-interface = { version = "^0", path = "../interface" } [dev-dependencies] diff --git a/program/src/processor/amount_to_ui_amount.rs b/program/src/processor/amount_to_ui_amount.rs index 4ca5e3a..6c13993 100644 --- a/program/src/processor/amount_to_ui_amount.rs +++ b/program/src/processor/amount_to_ui_amount.rs @@ -23,7 +23,8 @@ pub fn process_amount_to_ui_amount( let mint_info = accounts.first().ok_or(ProgramError::NotEnoughAccountKeys)?; check_account_owner(mint_info)?; - // SAFETY: there is a single borrow to the `Mint` account. + // SAFETY: single immutable borrow to `mint_info` account data and + // `load` validates that the mint is initialized. let mint = unsafe { load::(mint_info.borrow_data_unchecked()).map_err(|_| TokenError::InvalidMint)? }; diff --git a/program/src/processor/burn_checked.rs b/program/src/processor/burn_checked.rs index f9ef06c..88a745d 100644 --- a/program/src/processor/burn_checked.rs +++ b/program/src/processor/burn_checked.rs @@ -4,20 +4,20 @@ use super::shared; #[inline(always)] pub fn process_burn_checked(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult { - let (amount, decimals) = instruction_data.split_at(core::mem::size_of::()); - let amount = u64::from_le_bytes( - amount - .try_into() - .map_err(|_error| ProgramError::InvalidInstructionData)?, - ); + // expected u64 (8) + u8 (1) + let (amount, decimals) = if instruction_data.len() == 9 { + let (amount, decimals) = instruction_data.split_at(core::mem::size_of::()); + ( + u64::from_le_bytes( + amount + .try_into() + .map_err(|_error| ProgramError::InvalidInstructionData)?, + ), + decimals.first(), + ) + } else { + return Err(ProgramError::InvalidInstructionData); + }; - shared::burn::process_burn( - accounts, - amount, - Some( - *decimals - .first() - .ok_or(ProgramError::InvalidInstructionData)?, - ), - ) + shared::burn::process_burn(accounts, amount, decimals.copied()) } diff --git a/program/src/processor/close_account.rs b/program/src/processor/close_account.rs index ffd0263..3f90dea 100644 --- a/program/src/processor/close_account.rs +++ b/program/src/processor/close_account.rs @@ -3,14 +3,16 @@ use pinocchio::{ }; use token_interface::{ error::TokenError, - state::{account::Account, load_mut}, + state::{account::Account, load}, }; use super::validate_owner; -/// Incinerator address. -const INCINERATOR_ID: Pubkey = - pinocchio_pubkey::pubkey!("1nc1nerator11111111111111111111111111111111"); +/// Incinerator (`1nc1nerator11111111111111111111111111111111`) address. +const INCINERATOR_ID: Pubkey = [ + 0, 51, 144, 114, 141, 52, 17, 96, 121, 189, 201, 17, 191, 255, 0, 219, 212, 77, 46, 205, 204, + 247, 156, 166, 225, 0, 56, 225, 0, 0, 0, 0, +]; #[inline(always)] pub fn process_close_account(accounts: &[AccountInfo]) -> ProgramResult { @@ -24,26 +26,30 @@ pub fn process_close_account(accounts: &[AccountInfo]) -> ProgramResult { // raw pointer. if source_account_info == destination_account_info { return Err(ProgramError::InvalidAccountData); - } - - let source_account = - unsafe { load_mut::(source_account_info.borrow_mut_data_unchecked())? }; - - if !source_account.is_native() && source_account.amount() != 0 { - return Err(TokenError::NonNativeHasBalance.into()); - } - - let authority = source_account - .close_authority() - .unwrap_or(&source_account.owner); - - if !source_account.is_owned_by_system_program_or_incinerator() { - validate_owner(authority, authority_info, remaining)?; - } else if destination_account_info.key() != &INCINERATOR_ID { - return Err(ProgramError::InvalidAccountData); + } else { + // SAFETY: scoped immutable borrow to `source_account_info` account data and + // `load` validates that the account is initialized. + let source_account = + unsafe { load::(source_account_info.borrow_data_unchecked())? }; + + if !source_account.is_native() && source_account.amount() != 0 { + return Err(TokenError::NonNativeHasBalance.into()); + } + + let authority = source_account + .close_authority() + .unwrap_or(&source_account.owner); + + if !source_account.is_owned_by_system_program_or_incinerator() { + validate_owner(authority, authority_info, remaining)?; + } else if destination_account_info.key() != &INCINERATOR_ID { + return Err(ProgramError::InvalidAccountData); + } } let destination_starting_lamports = destination_account_info.lamports(); + // SAFETY: single mutable borrow to `destination_account_info` lamports and + // there are no "active" borrows of `source_account_info` account data. unsafe { // Moves the lamports to the destination account. *destination_account_info.borrow_mut_lamports_unchecked() = destination_starting_lamports diff --git a/program/src/processor/get_account_data_size.rs b/program/src/processor/get_account_data_size.rs index ff2fa90..7693b64 100644 --- a/program/src/processor/get_account_data_size.rs +++ b/program/src/processor/get_account_data_size.rs @@ -17,6 +17,8 @@ pub fn process_get_account_data_size(accounts: &[AccountInfo]) -> ProgramResult // Make sure the mint is valid. check_account_owner(mint_info)?; + // SAFETY: single immutable borrow to `mint_info` account data and + // `load` validates that the mint is initialized. let _ = unsafe { load::(mint_info.borrow_data_unchecked()).map_err(|_| TokenError::InvalidMint)? }; diff --git a/program/src/processor/initialize_account2.rs b/program/src/processor/initialize_account2.rs index 9200ee0..185c291 100644 --- a/program/src/processor/initialize_account2.rs +++ b/program/src/processor/initialize_account2.rs @@ -1,4 +1,9 @@ -use pinocchio::{account_info::AccountInfo, pubkey::Pubkey, ProgramResult}; +use pinocchio::{ + account_info::AccountInfo, + program_error::ProgramError, + pubkey::{Pubkey, PUBKEY_BYTES}, + ProgramResult, +}; use super::shared; @@ -7,6 +12,13 @@ pub fn process_initialize_account2( accounts: &[AccountInfo], instruction_data: &[u8], ) -> ProgramResult { - let owner = unsafe { &*(instruction_data.as_ptr() as *const Pubkey) }; + // SAFETY: validate `instruction_data` length. + let owner = unsafe { + if instruction_data.len() != PUBKEY_BYTES { + return Err(ProgramError::InvalidInstructionData); + } else { + &*(instruction_data.as_ptr() as *const Pubkey) + } + }; shared::initialize_account::process_initialize_account(accounts, Some(owner), true) } diff --git a/program/src/processor/initialize_account3.rs b/program/src/processor/initialize_account3.rs index 4eaa7a5..54e5501 100644 --- a/program/src/processor/initialize_account3.rs +++ b/program/src/processor/initialize_account3.rs @@ -1,4 +1,9 @@ -use pinocchio::{account_info::AccountInfo, pubkey::Pubkey, ProgramResult}; +use pinocchio::{ + account_info::AccountInfo, + program_error::ProgramError, + pubkey::{Pubkey, PUBKEY_BYTES}, + ProgramResult, +}; use super::shared; @@ -7,6 +12,13 @@ pub fn process_initialize_account3( accounts: &[AccountInfo], instruction_data: &[u8], ) -> ProgramResult { - let owner = unsafe { &*(instruction_data.as_ptr() as *const Pubkey) }; + // SAFETY: validate `instruction_data` length. + let owner = unsafe { + if instruction_data.len() != PUBKEY_BYTES { + return Err(ProgramError::InvalidInstructionData); + } else { + &*(instruction_data.as_ptr() as *const Pubkey) + } + }; shared::initialize_account::process_initialize_account(accounts, Some(owner), false) } diff --git a/program/src/processor/initialize_immutable_owner.rs b/program/src/processor/initialize_immutable_owner.rs index cf69da4..ba98c28 100644 --- a/program/src/processor/initialize_immutable_owner.rs +++ b/program/src/processor/initialize_immutable_owner.rs @@ -8,6 +8,7 @@ use token_interface::{ pub fn process_initialize_immutable_owner(accounts: &[AccountInfo]) -> ProgramResult { let token_account_info = accounts.first().ok_or(ProgramError::NotEnoughAccountKeys)?; + // SAFETY: single immutable borrow to `token_account_info` account data. let account = unsafe { load_unchecked::(token_account_info.borrow_data_unchecked())? }; if account.is_initialized() { diff --git a/program/src/processor/initialize_mint.rs b/program/src/processor/initialize_mint.rs index 8460ce0..ee52eb4 100644 --- a/program/src/processor/initialize_mint.rs +++ b/program/src/processor/initialize_mint.rs @@ -35,6 +35,7 @@ pub fn process_initialize_mint( (mint_info, None) }; + // SAFETY: single mutable borrow to `mint_info` account data. let mint = unsafe { load_mut_unchecked::(mint_info.borrow_mut_data_unchecked())? }; if mint.is_initialized() { @@ -44,7 +45,9 @@ pub fn process_initialize_mint( // Check rent-exempt status of the mint account. let is_exempt = if let Some(rent_sysvar_info) = rent_sysvar_info { - let rent = unsafe { Rent::from_bytes(rent_sysvar_info.borrow_data_unchecked()) }; + // SAFETY: single immutable borrow to `rent_sysvar_info`; account ID and length are + // checked by `from_account_info_unchecked`. + let rent = unsafe { Rent::from_account_info_unchecked(rent_sysvar_info)? }; rent.is_exempt(mint_info.lamports(), size_of::()) } else { Rent::get()?.is_exempt(mint_info.lamports(), size_of::()) @@ -81,7 +84,7 @@ impl InitializeMint<'_> { // - decimals (1 byte) // - mint_authority (32 bytes) // - option + freeze_authority (1 byte + 32 bytes) - if bytes.len() < 34 { + if bytes.len() < 34 || (bytes[33] == 1 && bytes.len() < 66) { return Err(ProgramError::InvalidInstructionData); } @@ -93,16 +96,19 @@ impl InitializeMint<'_> { #[inline] pub fn decimals(&self) -> u8 { + // SAFETY: the `bytes` length was validated in `try_from_bytes`. unsafe { *self.raw } } #[inline] pub fn mint_authority(&self) -> &Pubkey { + // SAFETY: the `bytes` length was validated in `try_from_bytes`. unsafe { &*(self.raw.add(1) as *const Pubkey) } } #[inline] pub fn freeze_authority(&self) -> Option<&Pubkey> { + // SAFETY: the `bytes` length was validated in `try_from_bytes`. unsafe { if *self.raw.add(33) == 0 { Option::None diff --git a/program/src/processor/mint_to_checked.rs b/program/src/processor/mint_to_checked.rs index 6fb3ae9..ebfcdea 100644 --- a/program/src/processor/mint_to_checked.rs +++ b/program/src/processor/mint_to_checked.rs @@ -4,17 +4,20 @@ use super::shared; #[inline(always)] pub fn process_mint_to_checked(accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult { - let (amount, decimals) = instruction_data.split_at(core::mem::size_of::()); + // expected u64 (8) + u8 (1) + let (amount, decimals) = if instruction_data.len() == 9 { + let (amount, decimals) = instruction_data.split_at(core::mem::size_of::()); + ( + u64::from_le_bytes( + amount + .try_into() + .map_err(|_error| ProgramError::InvalidInstructionData)?, + ), + decimals.first(), + ) + } else { + return Err(ProgramError::InvalidInstructionData); + }; - let amount = u64::from_le_bytes( - amount - .try_into() - .map_err(|_error| ProgramError::InvalidInstructionData)?, - ); - - shared::mint_to::process_mint_to( - accounts, - amount, - Some(*decimals.first().ok_or(ProgramError::InvalidAccountData)?), - ) + shared::mint_to::process_mint_to(accounts, amount, decimals.copied()) } diff --git a/program/src/processor/mod.rs b/program/src/processor/mod.rs index 208a48e..0f0592f 100644 --- a/program/src/processor/mod.rs +++ b/program/src/processor/mod.rs @@ -92,7 +92,10 @@ fn check_account_owner(account_info: &AccountInfo) -> ProgramResult { } } -/// Validates owner(s) are present +/// Validates owner(s) are present. +/// +/// Note that `owner_account_info` will be immutable borrowed when it represents +/// a multisig account. #[inline(always)] fn validate_owner( expected_owner: &Pubkey, @@ -106,6 +109,8 @@ fn validate_owner( if owner_account_info.data_len() == Multisig::LEN && owner_account_info.owner() == &TOKEN_PROGRAM_ID { + // SAFETY: the caller guarantees that there are no mutable borrows of `owner_account_info` + // account data and the `load` validates that the account is initialized. let multisig = unsafe { load::(owner_account_info.borrow_data_unchecked())? }; let mut num_signers = 0; diff --git a/program/src/processor/revoke.rs b/program/src/processor/revoke.rs index 2d40b02..6361d99 100644 --- a/program/src/processor/revoke.rs +++ b/program/src/processor/revoke.rs @@ -12,6 +12,8 @@ pub fn process_revoke(accounts: &[AccountInfo], _instruction_data: &[u8]) -> Pro return Err(ProgramError::NotEnoughAccountKeys); }; + // SAFETY: single mutable borrow to `source_account_info` account data and + // `load_mut` validates that the account is initialized. let source_account = unsafe { load_mut::(source_account_info.borrow_mut_data_unchecked())? }; diff --git a/program/src/processor/set_authority.rs b/program/src/processor/set_authority.rs index 2b9fd94..3ad4d12 100644 --- a/program/src/processor/set_authority.rs +++ b/program/src/processor/set_authority.rs @@ -27,6 +27,8 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8]) }; if account_info.data_len() == Account::LEN { + // SAFETY: single mutable borrow to `account_info` account data and + // `load_mut` validates that the account is initialized. let account = unsafe { load_mut::(account_info.borrow_mut_data_unchecked())? }; if account.is_frozen() { @@ -65,6 +67,8 @@ pub fn process_set_authority(accounts: &[AccountInfo], instruction_data: &[u8]) } } } else if account_info.data_len() == Mint::LEN { + // SAFETY: single mutable borrow to `account_info` account data and + // `load_mut` validates that the mint is initialized. let mint = unsafe { load_mut::(account_info.borrow_mut_data_unchecked())? }; match authority_type { @@ -119,7 +123,7 @@ impl SetAuthority<'_> { // The minimum expected size of the instruction data. // - authority_type (1 byte) // - option + new_authority (1 byte + 32 bytes) - if bytes.len() < 2 { + if bytes.len() < 2 || (bytes[1] == 1 && bytes.len() < 34) { return Err(ProgramError::InvalidInstructionData); } @@ -131,11 +135,13 @@ impl SetAuthority<'_> { #[inline(always)] pub fn authority_type(&self) -> Result { + // SAFETY: `bytes` length is validated in `try_from_bytes`. unsafe { AuthorityType::from(*self.raw) } } #[inline(always)] pub fn new_authority(&self) -> Option<&Pubkey> { + // SAFETY: `bytes` length is validated in `try_from_bytes`. unsafe { if *self.raw.add(1) == 0 { Option::None diff --git a/program/src/processor/shared/approve.rs b/program/src/processor/shared/approve.rs index 40fa7af..a9d2812 100644 --- a/program/src/processor/shared/approve.rs +++ b/program/src/processor/shared/approve.rs @@ -45,6 +45,8 @@ pub fn process_approve( // Validates source account. + // SAFETY: single mutable borrow to `source_account_info` account data and + // `load_mut` validates that the account is initialized. let source_account = unsafe { load_mut::(source_account_info.borrow_mut_data_unchecked())? }; @@ -57,6 +59,8 @@ pub fn process_approve( return Err(TokenError::MintMismatch.into()); } + // SAFETY: single immutable borrow of `mint_info` account data and + // `load` validates that the mint is initialized. let mint = unsafe { load::(mint_info.borrow_data_unchecked())? }; if expected_decimals != mint.decimals { diff --git a/program/src/processor/shared/burn.rs b/program/src/processor/shared/burn.rs index 1c26785..06f741a 100644 --- a/program/src/processor/shared/burn.rs +++ b/program/src/processor/shared/burn.rs @@ -16,6 +16,8 @@ pub fn process_burn( return Err(ProgramError::NotEnoughAccountKeys); }; + // SAFETY: single mutable borrow to `source_account_info` account data and + // `load_mut` validates that the account is initialized. let source_account = unsafe { load_mut::(source_account_info.borrow_mut_data_unchecked())? }; @@ -33,6 +35,8 @@ pub fn process_burn( .checked_sub(amount) .ok_or(TokenError::InsufficientFunds)?; + // SAFETY: single mutable borrow to `mint_info` account data and + // `load_mut` validates that the mint is initialized. let mint = unsafe { load_mut::(mint_info.borrow_mut_data_unchecked())? }; if mint_info.key() != &source_account.mint { diff --git a/program/src/processor/shared/initialize_account.rs b/program/src/processor/shared/initialize_account.rs index 1a2c75f..952dd1f 100644 --- a/program/src/processor/shared/initialize_account.rs +++ b/program/src/processor/shared/initialize_account.rs @@ -42,7 +42,9 @@ pub fn process_initialize_account( let minimum_balance = if rent_sysvar_account { let rent_sysvar_info = remaning.first().ok_or(ProgramError::NotEnoughAccountKeys)?; - let rent = unsafe { Rent::from_bytes(rent_sysvar_info.borrow_data_unchecked()) }; + // SAFETY: single immutable borrow to `rent_sysvar_info`; account ID and length are + // checked by `from_account_info_unchecked`. + let rent = unsafe { Rent::from_account_info_unchecked(rent_sysvar_info)? }; rent.minimum_balance(new_account_info_data_len) } else { Rent::get()?.minimum_balance(new_account_info_data_len) @@ -52,6 +54,7 @@ pub fn process_initialize_account( // Initialize the account. + // SAFETY: single mutable borrow of the 'new_account_info' account data. let account = unsafe { load_mut_unchecked::(new_account_info.borrow_mut_data_unchecked())? }; @@ -66,6 +69,8 @@ pub fn process_initialize_account( if !is_native_mint { check_account_owner(mint_info)?; + // SAFETY: single immutable borrow of `mint_info` account data and + // `load` validates that the mint is initialized. let _ = unsafe { load::(mint_info.borrow_data_unchecked()).map_err(|_| TokenError::InvalidMint)? }; @@ -78,6 +83,7 @@ pub fn process_initialize_account( if is_native_mint { account.set_native(true); account.set_native_amount(minimum_balance); + // SAFETY: single mutable borrow to `new_account_info` lamports. unsafe { account.set_amount( new_account_info diff --git a/program/src/processor/shared/initialize_multisig.rs b/program/src/processor/shared/initialize_multisig.rs index 8831c19..0d45fa8 100644 --- a/program/src/processor/shared/initialize_multisig.rs +++ b/program/src/processor/shared/initialize_multisig.rs @@ -32,12 +32,15 @@ pub fn process_initialize_multisig( let multisig_info_data_len = multisig_info.data_len(); let is_exempt = if let Some(rent_sysvar_info) = rent_sysvar_info { - let rent = unsafe { Rent::from_bytes(rent_sysvar_info.borrow_data_unchecked()) }; + // SAFETY: single immutable borrow to `rent_sysvar_info`; account ID and length are + // checked by `from_account_info_unchecked`. + let rent = unsafe { Rent::from_account_info_unchecked(rent_sysvar_info)? }; rent.is_exempt(multisig_info.lamports(), multisig_info_data_len) } else { Rent::get()?.is_exempt(multisig_info.lamports(), multisig_info_data_len) }; + // SAFETY: single mutable borrow to `multisig_info` account data. let multisig = unsafe { load_mut_unchecked::(multisig_info.borrow_mut_data_unchecked())? }; diff --git a/program/src/processor/shared/mint_to.rs b/program/src/processor/shared/mint_to.rs index 62141bb..773039f 100644 --- a/program/src/processor/shared/mint_to.rs +++ b/program/src/processor/shared/mint_to.rs @@ -18,6 +18,8 @@ pub fn process_mint_to( // Validates the destination account. + // SAFETY: single mutable borrow to `destination_account_info` account data and + // `load_mut` validates that the account is initialized. let destination_account = unsafe { load_mut::(destination_account_info.borrow_mut_data_unchecked())? }; @@ -33,6 +35,8 @@ pub fn process_mint_to( return Err(TokenError::MintMismatch.into()); } + // SAFETY: single mutable borrow to `mint_info` account data and + // `load_mut` validates that the mint is initialized. let mint = unsafe { load_mut::(mint_info.borrow_mut_data_unchecked())? }; if let Some(expected_decimals) = expected_decimals { diff --git a/program/src/processor/shared/toggle_account_state.rs b/program/src/processor/shared/toggle_account_state.rs index bf05bfd..c9ff11a 100644 --- a/program/src/processor/shared/toggle_account_state.rs +++ b/program/src/processor/shared/toggle_account_state.rs @@ -12,10 +12,12 @@ pub fn process_toggle_account_state(accounts: &[AccountInfo], freeze: bool) -> P return Err(ProgramError::NotEnoughAccountKeys); }; + // SAFETY: single mutable borrow to `source_account_info` account data and + // `load_mut` validates that the account is initialized. let source_account = unsafe { load_mut::(source_account_info.borrow_mut_data_unchecked())? }; - if freeze && source_account.is_frozen() || !freeze && !source_account.is_frozen() { + if freeze == source_account.is_frozen() { return Err(TokenError::InvalidState.into()); } if source_account.is_native() { @@ -25,6 +27,8 @@ pub fn process_toggle_account_state(accounts: &[AccountInfo], freeze: bool) -> P return Err(TokenError::MintMismatch.into()); } + // SAFETY: single immutable borrow of `mint_info` account data and + // `load` validates that the mint is initialized. let mint = unsafe { load::(mint_info.borrow_data_unchecked())? }; match mint.freeze_authority() { diff --git a/program/src/processor/shared/transfer.rs b/program/src/processor/shared/transfer.rs index 6d33d5f..aafd0a1 100644 --- a/program/src/processor/shared/transfer.rs +++ b/program/src/processor/shared/transfer.rs @@ -1,7 +1,7 @@ use pinocchio::{account_info::AccountInfo, program_error::ProgramError, ProgramResult}; use token_interface::{ error::TokenError, - state::{account::Account, load, load_mut, mint::Mint}, + state::{account::Account, load, load_mut, load_mut_unchecked, mint::Mint}, }; use crate::processor::{check_account_owner, validate_owner}; @@ -51,27 +51,49 @@ pub fn process_transfer( // Validates source and destination accounts. + // SAFETY: single mutable borrow to `source_account_info` account data and + // `load_mut` validates that the account is initialized. let source_account = unsafe { load_mut::(source_account_info.borrow_mut_data_unchecked())? }; - let destination_account = - unsafe { load_mut::(destination_account_info.borrow_mut_data_unchecked())? }; - - if source_account.is_frozen() || destination_account.is_frozen() { - return Err(TokenError::AccountFrozen.into()); - } + // Comparing whether the AccountInfo's "point" to the same account or + // not - this is a faster comparison since it just checks the internal + // raw pointer. + let self_transfer = source_account_info == destination_account_info; // Implicitly validates that the account has enough tokens by calculating the // remaining amount - the amount is only updated on the account if the transfer // is successful. - let remaining_amount = source_account - .amount() - .checked_sub(amount) - .ok_or(TokenError::InsufficientFunds)?; + let remaining_amount = if self_transfer { + if source_account.is_frozen() { + return Err(TokenError::AccountFrozen.into()); + } - if source_account.mint != destination_account.mint { - return Err(TokenError::MintMismatch.into()); - } + source_account + .amount() + .checked_sub(amount) + .ok_or(TokenError::InsufficientFunds)? + } else { + // SAFETY: scoped immutable borrow to `destination_account_info` account data and + // `load` validates that the account is initialized. + let destination_account = + unsafe { load::(destination_account_info.borrow_data_unchecked())? }; + + if source_account.is_frozen() || destination_account.is_frozen() { + return Err(TokenError::AccountFrozen.into()); + } + + let remaining_amount = source_account + .amount() + .checked_sub(amount) + .ok_or(TokenError::InsufficientFunds)?; + + if source_account.mint != destination_account.mint { + return Err(TokenError::MintMismatch.into()); + } + + remaining_amount + }; // Validates the mint information. @@ -80,6 +102,8 @@ pub fn process_transfer( return Err(TokenError::MintMismatch.into()); } + // SAFETY: single immutable borrow of `mint_info` account data and + // `load` validates that the mint is initialized. let mint = unsafe { load::(mint_info.borrow_data_unchecked())? }; if decimals != mint.decimals { @@ -87,11 +111,6 @@ pub fn process_transfer( } } - // Comparing whether the AccountInfo's "point" to the same account or - // not - this is a faster comparison since it just checks the internal - // raw pointer. - let self_transfer = source_account_info == destination_account_info; - // Validates the authority (delegate or owner). if source_account.delegate() == Some(authority_info.key()) { @@ -123,6 +142,11 @@ pub fn process_transfer( source_account.set_amount(remaining_amount); + // SAFETY: single mutable borrow to `destination_account_info` account data; the account + // is guaranteed to be initialized and different than `source_account_info`. + let destination_account = unsafe { + load_mut_unchecked::(destination_account_info.borrow_mut_data_unchecked())? + }; let destination_amount = destination_account .amount() .checked_add(amount) @@ -130,11 +154,13 @@ pub fn process_transfer( destination_account.set_amount(destination_amount); if source_account.is_native() { + // SAFETY: single mutable borrow to `source_account_info` lamports. let source_lamports = unsafe { source_account_info.borrow_mut_lamports_unchecked() }; *source_lamports = source_lamports .checked_sub(amount) .ok_or(TokenError::Overflow)?; + // SAFETY: single mutable borrow to `destination_account_info` lamports. let destination_lamports = unsafe { destination_account_info.borrow_mut_lamports_unchecked() }; *destination_lamports = destination_lamports diff --git a/program/src/processor/sync_native.rs b/program/src/processor/sync_native.rs index 9bba708..4ccc276 100644 --- a/program/src/processor/sync_native.rs +++ b/program/src/processor/sync_native.rs @@ -12,6 +12,8 @@ pub fn process_sync_native(accounts: &[AccountInfo]) -> ProgramResult { check_account_owner(native_account_info)?; + // SAFETY: single mutable borrow to `native_account_info` account data and + // `load_mut` validates that the account is initialized. let native_account = unsafe { load_mut::(native_account_info.borrow_mut_data_unchecked())? }; diff --git a/program/src/processor/transfer_checked.rs b/program/src/processor/transfer_checked.rs index 4c23ee5..ea75a28 100644 --- a/program/src/processor/transfer_checked.rs +++ b/program/src/processor/transfer_checked.rs @@ -7,20 +7,20 @@ pub fn process_transfer_checked( accounts: &[AccountInfo], instruction_data: &[u8], ) -> ProgramResult { - let (amount, decimals) = instruction_data.split_at(core::mem::size_of::()); - let amount = u64::from_le_bytes( - amount - .try_into() - .map_err(|_error| ProgramError::InvalidInstructionData)?, - ); + // expected u64 (8) + u8 (1) + let (amount, decimals) = if instruction_data.len() == 9 { + let (amount, decimals) = instruction_data.split_at(core::mem::size_of::()); + ( + u64::from_le_bytes( + amount + .try_into() + .map_err(|_error| ProgramError::InvalidInstructionData)?, + ), + decimals.first(), + ) + } else { + return Err(ProgramError::InvalidInstructionData); + }; - shared::transfer::process_transfer( - accounts, - amount, - Some( - *decimals - .first() - .ok_or(ProgramError::InvalidInstructionData)?, - ), - ) + shared::transfer::process_transfer(accounts, amount, decimals.copied()) } diff --git a/program/src/processor/ui_amount_to_amount.rs b/program/src/processor/ui_amount_to_amount.rs index b7299dc..823ffbc 100644 --- a/program/src/processor/ui_amount_to_amount.rs +++ b/program/src/processor/ui_amount_to_amount.rs @@ -19,7 +19,8 @@ pub fn process_ui_amount_to_amount( let mint_info = accounts.first().ok_or(ProgramError::NotEnoughAccountKeys)?; check_account_owner(mint_info)?; - // SAFETY: there is a single borrow to the `Mint` account. + // SAFETY: single immutable borrow to `mint_info` account data and + // `load` validates that the mint is initialized. let mint = unsafe { load::(mint_info.borrow_data_unchecked()).map_err(|_| TokenError::InvalidMint)? }; diff --git a/program/tests/amount_to_ui_amount.rs b/program/tests/amount_to_ui_amount.rs index e7cc8f6..402be08 100644 --- a/program/tests/amount_to_ui_amount.rs +++ b/program/tests/amount_to_ui_amount.rs @@ -6,7 +6,6 @@ use setup::{mint, TOKEN_PROGRAM_ID}; use solana_program_test::{tokio, ProgramTest}; use solana_sdk::{pubkey::Pubkey, signature::Signer, transaction::Transaction}; -#[test_case::test_case(spl_token::ID ; "spl-token")] #[test_case::test_case(TOKEN_PROGRAM_ID ; "p-token")] #[tokio::test] async fn amount_to_ui_amount(token_program: Pubkey) { diff --git a/program/tests/approve.rs b/program/tests/approve.rs index 37ba3bf..f3b28e2 100644 --- a/program/tests/approve.rs +++ b/program/tests/approve.rs @@ -11,7 +11,6 @@ use solana_sdk::{ transaction::Transaction, }; -#[test_case::test_case(spl_token::ID ; "spl-token")] #[test_case::test_case(TOKEN_PROGRAM_ID ; "p-token")] #[tokio::test] async fn approve(token_program: Pubkey) { diff --git a/program/tests/approve_checked.rs b/program/tests/approve_checked.rs index edd949b..e34fa7a 100644 --- a/program/tests/approve_checked.rs +++ b/program/tests/approve_checked.rs @@ -11,7 +11,6 @@ use solana_sdk::{ transaction::Transaction, }; -#[test_case::test_case(spl_token::ID ; "spl-token")] #[test_case::test_case(TOKEN_PROGRAM_ID ; "p-token")] #[tokio::test] async fn approve_checked(token_program: Pubkey) { diff --git a/program/tests/burn.rs b/program/tests/burn.rs index 57ced7b..6e5e2e5 100644 --- a/program/tests/burn.rs +++ b/program/tests/burn.rs @@ -11,7 +11,6 @@ use solana_sdk::{ transaction::Transaction, }; -#[test_case::test_case(spl_token::ID ; "spl-token")] #[test_case::test_case(TOKEN_PROGRAM_ID ; "p-token")] #[tokio::test] async fn burn(token_program: Pubkey) { diff --git a/program/tests/burn_checked.rs b/program/tests/burn_checked.rs index a9a05b1..a008262 100644 --- a/program/tests/burn_checked.rs +++ b/program/tests/burn_checked.rs @@ -11,7 +11,6 @@ use solana_sdk::{ transaction::Transaction, }; -#[test_case::test_case(spl_token::ID ; "spl-token")] #[test_case::test_case(TOKEN_PROGRAM_ID ; "p-token")] #[tokio::test] async fn burn_checked(token_program: Pubkey) { diff --git a/program/tests/close_account.rs b/program/tests/close_account.rs index 3378432..8a73432 100644 --- a/program/tests/close_account.rs +++ b/program/tests/close_account.rs @@ -10,7 +10,6 @@ use solana_sdk::{ transaction::Transaction, }; -#[test_case::test_case(spl_token::ID ; "spl-token")] #[test_case::test_case(TOKEN_PROGRAM_ID ; "p-token")] #[tokio::test] async fn close_account(token_program: Pubkey) { diff --git a/program/tests/freeze_account.rs b/program/tests/freeze_account.rs index 2f44d46..d5fb3af 100644 --- a/program/tests/freeze_account.rs +++ b/program/tests/freeze_account.rs @@ -12,7 +12,6 @@ use solana_sdk::{ }; use spl_token::state::AccountState; -#[test_case::test_case(spl_token::ID ; "spl-token")] #[test_case::test_case(TOKEN_PROGRAM_ID ; "p-token")] #[tokio::test] async fn freeze_account(token_program: Pubkey) { diff --git a/program/tests/initialize_account.rs b/program/tests/initialize_account.rs index 9bbea2e..4c491bb 100644 --- a/program/tests/initialize_account.rs +++ b/program/tests/initialize_account.rs @@ -12,7 +12,6 @@ use solana_sdk::{ transaction::Transaction, }; -#[test_case::test_case(spl_token::ID ; "spl-token")] #[test_case::test_case(TOKEN_PROGRAM_ID ; "p-token")] #[tokio::test] async fn initialize_account(token_program: Pubkey) { diff --git a/program/tests/initialize_account2.rs b/program/tests/initialize_account2.rs index 7ed0aec..9f250e4 100644 --- a/program/tests/initialize_account2.rs +++ b/program/tests/initialize_account2.rs @@ -12,7 +12,6 @@ use solana_sdk::{ transaction::Transaction, }; -#[test_case::test_case(spl_token::ID ; "spl-token")] #[test_case::test_case(TOKEN_PROGRAM_ID ; "p-token")] #[tokio::test] async fn initialize_account2(token_program: Pubkey) { diff --git a/program/tests/initialize_account3.rs b/program/tests/initialize_account3.rs index 74cf2df..7fee802 100644 --- a/program/tests/initialize_account3.rs +++ b/program/tests/initialize_account3.rs @@ -12,7 +12,6 @@ use solana_sdk::{ transaction::Transaction, }; -#[test_case::test_case(spl_token::ID ; "spl-token")] #[test_case::test_case(TOKEN_PROGRAM_ID ; "p-token")] #[tokio::test] async fn initialize_account3(token_program: Pubkey) { diff --git a/program/tests/initialize_mint.rs b/program/tests/initialize_mint.rs index f99ade1..175e805 100644 --- a/program/tests/initialize_mint.rs +++ b/program/tests/initialize_mint.rs @@ -16,7 +16,6 @@ use solana_sdk::{ }; use token_interface::state::mint::Mint; -#[test_case::test_case(spl_token::ID ; "spl-token")] #[test_case::test_case(TOKEN_PROGRAM_ID ; "p-token")] #[tokio::test] async fn initialize_mint(token_program: Pubkey) { diff --git a/program/tests/initialize_mint2.rs b/program/tests/initialize_mint2.rs index 1b8c24f..683e1e3 100644 --- a/program/tests/initialize_mint2.rs +++ b/program/tests/initialize_mint2.rs @@ -16,7 +16,6 @@ use solana_sdk::{ }; use token_interface::state::mint::Mint; -#[test_case::test_case(spl_token::ID ; "spl-token")] #[test_case::test_case(TOKEN_PROGRAM_ID ; "p-token")] #[tokio::test] async fn initialize_mint2(token_program: Pubkey) { diff --git a/program/tests/initialize_multisig.rs b/program/tests/initialize_multisig.rs index 574554d..5cf9e34 100644 --- a/program/tests/initialize_multisig.rs +++ b/program/tests/initialize_multisig.rs @@ -13,7 +13,6 @@ use solana_sdk::{ }; use spl_token::state::Multisig; -#[test_case::test_case(spl_token::ID ; "spl-token")] #[test_case::test_case(TOKEN_PROGRAM_ID ; "p-token")] #[tokio::test] async fn initialize_multisig(token_program: Pubkey) { diff --git a/program/tests/initialize_multisig2.rs b/program/tests/initialize_multisig2.rs index e1d95b8..443a988 100644 --- a/program/tests/initialize_multisig2.rs +++ b/program/tests/initialize_multisig2.rs @@ -13,7 +13,6 @@ use solana_sdk::{ }; use spl_token::state::Multisig; -#[test_case::test_case(spl_token::ID ; "spl-token")] #[test_case::test_case(TOKEN_PROGRAM_ID ; "p-token")] #[tokio::test] async fn initialize_multisig2(token_program: Pubkey) { diff --git a/program/tests/mint_to.rs b/program/tests/mint_to.rs index f735d2e..9cafae3 100644 --- a/program/tests/mint_to.rs +++ b/program/tests/mint_to.rs @@ -11,7 +11,6 @@ use solana_sdk::{ transaction::Transaction, }; -#[test_case::test_case(spl_token::ID ; "spl-token")] #[test_case::test_case(TOKEN_PROGRAM_ID ; "p-token")] #[tokio::test] async fn mint_to(token_program: Pubkey) { diff --git a/program/tests/mint_to_checked.rs b/program/tests/mint_to_checked.rs index cd126a5..1d0cb72 100644 --- a/program/tests/mint_to_checked.rs +++ b/program/tests/mint_to_checked.rs @@ -11,7 +11,6 @@ use solana_sdk::{ transaction::Transaction, }; -#[test_case::test_case(spl_token::ID ; "spl-token")] #[test_case::test_case(TOKEN_PROGRAM_ID ; "p-token")] #[tokio::test] async fn mint_to_checked(token_program: Pubkey) { diff --git a/program/tests/revoke.rs b/program/tests/revoke.rs index 2760dbf..689c48c 100644 --- a/program/tests/revoke.rs +++ b/program/tests/revoke.rs @@ -11,7 +11,6 @@ use solana_sdk::{ transaction::Transaction, }; -#[test_case::test_case(spl_token::ID ; "spl-token")] #[test_case::test_case(TOKEN_PROGRAM_ID ; "p-token")] #[tokio::test] async fn revoke(token_program: Pubkey) { diff --git a/program/tests/set_authority.rs b/program/tests/set_authority.rs index 2e37d7d..977d22e 100644 --- a/program/tests/set_authority.rs +++ b/program/tests/set_authority.rs @@ -13,7 +13,6 @@ use solana_sdk::{ }; use spl_token::instruction::AuthorityType; -#[test_case::test_case(spl_token::ID ; "spl-token")] #[test_case::test_case(TOKEN_PROGRAM_ID ; "p-token")] #[tokio::test] async fn set_authority(token_program: Pubkey) { diff --git a/program/tests/thaw_account.rs b/program/tests/thaw_account.rs index 6ddae1a..6fe5762 100644 --- a/program/tests/thaw_account.rs +++ b/program/tests/thaw_account.rs @@ -12,7 +12,6 @@ use solana_sdk::{ }; use spl_token::state::AccountState; -#[test_case::test_case(spl_token::ID ; "spl-token")] #[test_case::test_case(TOKEN_PROGRAM_ID ; "p-token")] #[tokio::test] async fn thaw_account(token_program: Pubkey) { diff --git a/program/tests/transfer.rs b/program/tests/transfer.rs index ae7413e..16491c2 100644 --- a/program/tests/transfer.rs +++ b/program/tests/transfer.rs @@ -11,7 +11,6 @@ use solana_sdk::{ transaction::Transaction, }; -#[test_case::test_case(spl_token::ID ; "spl-token")] #[test_case::test_case(TOKEN_PROGRAM_ID ; "p-token")] #[tokio::test] async fn transfer(token_program: Pubkey) { diff --git a/program/tests/transfer_checked.rs b/program/tests/transfer_checked.rs index 991a103..d03c9f5 100644 --- a/program/tests/transfer_checked.rs +++ b/program/tests/transfer_checked.rs @@ -11,7 +11,6 @@ use solana_sdk::{ transaction::Transaction, }; -#[test_case::test_case(spl_token::ID ; "spl-token")] #[test_case::test_case(TOKEN_PROGRAM_ID ; "p-token")] #[tokio::test] async fn transfer_checked(token_program: Pubkey) { diff --git a/program/tests/ui_amount_to_amount.rs b/program/tests/ui_amount_to_amount.rs index 45843fb..3b1a6db 100644 --- a/program/tests/ui_amount_to_amount.rs +++ b/program/tests/ui_amount_to_amount.rs @@ -6,7 +6,6 @@ use setup::{mint, TOKEN_PROGRAM_ID}; use solana_program_test::{tokio, ProgramTest}; use solana_sdk::{pubkey::Pubkey, signature::Signer, transaction::Transaction}; -#[test_case::test_case(spl_token::ID ; "spl-token")] #[test_case::test_case(TOKEN_PROGRAM_ID ; "p-token")] #[tokio::test] async fn ui_amount_to_amount(token_program: Pubkey) {