Browse Source

Few more instructions

febo 10 months ago
parent
commit
4b1f886bad

+ 82 - 1
p-token/src/entrypoint.rs

@@ -5,17 +5,25 @@ use pinocchio::{
 
 use crate::processor::{
     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,
     initialize_account::process_initialize_account,
+    initialize_account2::process_initialize_account2,
+    initialize_account3::process_initialize_account3,
     initialize_mint::{process_initialize_mint, InitializeMint},
+    initialize_mint2::process_initialize_mint2,
     initialize_multisig::process_initialize_multisig,
+    initialize_multisig2::process_initialize_multisig2,
     mint_to::process_mint_to,
+    mint_to_checked::{process_mint_to_checked, MintToChecked},
     revoke::process_revoke,
     set_authority::{process_set_authority, SetAuthority},
     thaw_account::process_thaw_account,
     transfer::process_transfer,
+    transfer_checked::{process_transfer_checked, TransferChecked},
 };
 
 entrypoint!(process_instruction);
@@ -33,6 +41,7 @@ pub fn process_instruction(
             pinocchio::msg!("Instruction: InitializeMint");
 
             let instruction = InitializeMint::try_from_bytes(data)?;
+
             process_initialize_mint(accounts, &instruction, true)
         }
         // 1 - InitializeAccount
@@ -133,13 +142,85 @@ pub fn process_instruction(
 
             process_freeze_account(program_id, accounts)
         }
-        // 10 - ThawAccount
+        // 11 - ThawAccount
         Some((&11, _)) => {
             #[cfg(feature = "logging")]
             pinocchio::msg!("Instruction: ThawAccount");
 
             process_thaw_account(program_id, accounts)
         }
+        // 12 - TransferChecked
+        Some((&12, data)) => {
+            #[cfg(feature = "logging")]
+            pinocchio::msg!("Instruction: TransferChecked");
+
+            let args = TransferChecked::try_from_bytes(data)?;
+
+            process_transfer_checked(program_id, accounts, args.amount(), args.decimals())
+        }
+        // 13 - ApproveChecked
+        Some((&13, data)) => {
+            #[cfg(feature = "logging")]
+            pinocchio::msg!("Instruction: ApproveChecked");
+
+            let args = ApproveChecked::try_from_bytes(data)?;
+
+            process_approve_checked(program_id, accounts, args.amount(), args.decimals())
+        }
+        // 14 - MintToChecked
+        Some((&14, data)) => {
+            #[cfg(feature = "logging")]
+            pinocchio::msg!("Instruction: MintToChecked");
+
+            let args = MintToChecked::try_from_bytes(data)?;
+
+            process_mint_to_checked(program_id, accounts, args.amount(), args.decimals())
+        }
+        // 15 - BurnChecked
+        Some((&15, data)) => {
+            #[cfg(feature = "logging")]
+            pinocchio::msg!("Instruction: BurnChecked");
+
+            let args = BurnChecked::try_from_bytes(data)?;
+
+            process_burn_checked(program_id, accounts, args.amount(), args.decimals())
+        }
+        // 16 - InitializeAccount2
+        Some((&16, data)) => {
+            #[cfg(feature = "logging")]
+            pinocchio::msg!("Instruction: InitializeAccount2");
+
+            let owner = unsafe { &*(data.as_ptr() as *const Pubkey) };
+
+            process_initialize_account2(program_id, accounts, owner)
+        }
+        // 18 - InitializeAccount3
+        Some((&18, data)) => {
+            #[cfg(feature = "logging")]
+            pinocchio::msg!("Instruction: InitializeAccount3");
+
+            let owner = unsafe { &*(data.as_ptr() as *const Pubkey) };
+
+            process_initialize_account3(program_id, accounts, owner)
+        }
+        // 19 - InitializeMultisig2
+        Some((&19, data)) => {
+            #[cfg(feature = "logging")]
+            pinocchio::msg!("Instruction: InitializeMultisig2");
+
+            let m = data.first().ok_or(ProgramError::InvalidInstructionData)?;
+
+            process_initialize_multisig2(accounts, *m)
+        }
+        // 20 - InitializeMint2
+        Some((&20, data)) => {
+            #[cfg(feature = "logging")]
+            pinocchio::msg!("Instruction: InitializeMint2");
+
+            let instruction = InitializeMint::try_from_bytes(data)?;
+
+            process_initialize_mint2(accounts, &instruction)
+        }
         _ => Err(ProgramError::InvalidInstructionData),
     }
 }

+ 47 - 0
p-token/src/processor/approve_checked.rs

@@ -0,0 +1,47 @@
+use std::marker::PhantomData;
+
+use pinocchio::{
+    account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult,
+};
+
+use super::approve::process_approve;
+
+#[inline(always)]
+pub fn process_approve_checked(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    amount: u64,
+    decimals: u8,
+) -> ProgramResult {
+    process_approve(program_id, accounts, amount, Some(decimals))
+}
+
+pub struct ApproveChecked<'a> {
+    raw: *const u8,
+
+    _data: PhantomData<&'a [u8]>,
+}
+
+impl ApproveChecked<'_> {
+    pub fn try_from_bytes(bytes: &[u8]) -> Result<ApproveChecked, ProgramError> {
+        if bytes.len() != 9 {
+            return Err(ProgramError::InvalidInstructionData);
+        }
+
+        Ok(ApproveChecked {
+            raw: bytes.as_ptr(),
+            _data: PhantomData,
+        })
+    }
+
+    pub fn amount(&self) -> u64 {
+        unsafe {
+            let amount = self.raw as *const u64;
+            amount.read_unaligned()
+        }
+    }
+
+    pub fn decimals(&self) -> u8 {
+        unsafe { *self.raw.add(8) }
+    }
+}

+ 47 - 0
p-token/src/processor/burn_checked.rs

@@ -0,0 +1,47 @@
+use std::marker::PhantomData;
+
+use pinocchio::{
+    account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult,
+};
+
+use super::burn::process_burn;
+
+#[inline(always)]
+pub fn process_burn_checked(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    amount: u64,
+    decimals: u8,
+) -> ProgramResult {
+    process_burn(program_id, accounts, amount, Some(decimals))
+}
+
+pub struct BurnChecked<'a> {
+    raw: *const u8,
+
+    _data: PhantomData<&'a [u8]>,
+}
+
+impl BurnChecked<'_> {
+    pub fn try_from_bytes(bytes: &[u8]) -> Result<BurnChecked, ProgramError> {
+        if bytes.len() != 9 {
+            return Err(ProgramError::InvalidInstructionData);
+        }
+
+        Ok(BurnChecked {
+            raw: bytes.as_ptr(),
+            _data: PhantomData,
+        })
+    }
+
+    pub fn amount(&self) -> u64 {
+        unsafe {
+            let amount = self.raw as *const u64;
+            amount.read_unaligned()
+        }
+    }
+
+    pub fn decimals(&self) -> u8 {
+        unsafe { *self.raw.add(8) }
+    }
+}

+ 12 - 0
p-token/src/processor/initialize_account2.rs

@@ -0,0 +1,12 @@
+use pinocchio::{account_info::AccountInfo, pubkey::Pubkey, ProgramResult};
+
+use super::initialize_account::process_initialize_account;
+
+#[inline(always)]
+pub fn process_initialize_account2(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    owner: &Pubkey,
+) -> ProgramResult {
+    process_initialize_account(program_id, accounts, Some(owner), true)
+}

+ 12 - 0
p-token/src/processor/initialize_account3.rs

@@ -0,0 +1,12 @@
+use pinocchio::{account_info::AccountInfo, pubkey::Pubkey, ProgramResult};
+
+use super::initialize_account::process_initialize_account;
+
+#[inline(always)]
+pub fn process_initialize_account3(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    owner: &Pubkey,
+) -> ProgramResult {
+    process_initialize_account(program_id, accounts, Some(owner), false)
+}

+ 48 - 6
p-token/src/processor/initialize_mint.rs

@@ -1,12 +1,11 @@
-use bytemuck::{Pod, Zeroable};
 use pinocchio::{
     account_info::AccountInfo,
     program_error::ProgramError,
-    pubkey::{Pubkey, PUBKEY_BYTES},
+    pubkey::Pubkey,
     sysvars::{rent::Rent, Sysvar},
     ProgramResult,
 };
-use std::mem::size_of;
+use std::{marker::PhantomData, mem::size_of};
 use token_interface::{
     error::TokenError,
     state::{mint::Mint, PodCOption},
@@ -52,17 +51,18 @@ pub fn process_initialize_mint(
 
     // Initialize the mint.
 
-    mint.mint_authority = PodCOption::from(Some(args.data.mint_authority));
-    mint.decimals = args.data.decimals;
+    mint.mint_authority = PodCOption::from(Some(*args.mint_authority()));
+    mint.decimals = args.decimals();
     mint.is_initialized = true.into();
 
-    if let Some(freeze_authority) = args.freeze_authority {
+    if let Some(freeze_authority) = args.freeze_authority() {
         mint.freeze_authority = PodCOption::from(Some(*freeze_authority));
     }
 
     Ok(())
 }
 
+/*
 /// Instruction data for the `InitializeMint` instruction.
 pub struct InitializeMint<'a> {
     pub data: &'a MintData,
@@ -107,3 +107,45 @@ pub struct MintData {
     /// The authority/multisignature to mint tokens.
     pub mint_authority: Pubkey,
 }
+*/
+/// Instruction data for the `InitializeMint2` instruction.
+pub struct InitializeMint<'a> {
+    raw: *const u8,
+
+    _data: PhantomData<&'a [u8]>,
+}
+
+impl InitializeMint<'_> {
+    pub fn try_from_bytes(bytes: &[u8]) -> Result<InitializeMint, ProgramError> {
+        // The minimum expected size of the instruction data.
+        // - decimals (1 byte)
+        // - mint_authority (32 bytes)
+        // - option + freeze_authority (1 byte + 32 bytes)
+        if bytes.len() < 34 {
+            return Err(ProgramError::InvalidInstructionData);
+        }
+
+        Ok(InitializeMint {
+            raw: bytes.as_ptr(),
+            _data: PhantomData,
+        })
+    }
+
+    pub fn decimals(&self) -> u8 {
+        unsafe { *self.raw }
+    }
+
+    pub fn mint_authority(&self) -> &Pubkey {
+        unsafe { &*(self.raw.add(1) as *const Pubkey) }
+    }
+
+    pub fn freeze_authority(&self) -> Option<&Pubkey> {
+        unsafe {
+            if *self.raw.add(33) == 0 {
+                Option::None
+            } else {
+                Option::Some(&*(self.raw.add(34) as *const Pubkey))
+            }
+        }
+    }
+}

+ 7 - 0
p-token/src/processor/initialize_mint2.rs

@@ -0,0 +1,7 @@
+use pinocchio::{account_info::AccountInfo, ProgramResult};
+
+use super::initialize_mint::{process_initialize_mint, InitializeMint};
+
+pub fn process_initialize_mint2(accounts: &[AccountInfo], args: &InitializeMint) -> ProgramResult {
+    process_initialize_mint(accounts, args, false)
+}

+ 7 - 0
p-token/src/processor/initialize_multisig2.rs

@@ -0,0 +1,7 @@
+use pinocchio::{account_info::AccountInfo, ProgramResult};
+
+use super::initialize_multisig::process_initialize_multisig;
+
+pub fn process_initialize_multisig2(accounts: &[AccountInfo], m: u8) -> ProgramResult {
+    process_initialize_multisig(accounts, m, false)
+}

+ 47 - 0
p-token/src/processor/mint_to_checked.rs

@@ -0,0 +1,47 @@
+use std::marker::PhantomData;
+
+use pinocchio::{
+    account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult,
+};
+
+use super::mint_to::process_mint_to;
+
+#[inline(always)]
+pub fn process_mint_to_checked(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    amount: u64,
+    decimals: u8,
+) -> ProgramResult {
+    process_mint_to(program_id, accounts, amount, Some(decimals))
+}
+
+pub struct MintToChecked<'a> {
+    raw: *const u8,
+
+    _data: PhantomData<&'a [u8]>,
+}
+
+impl MintToChecked<'_> {
+    pub fn try_from_bytes(bytes: &[u8]) -> Result<MintToChecked, ProgramError> {
+        if bytes.len() != 9 {
+            return Err(ProgramError::InvalidInstructionData);
+        }
+
+        Ok(MintToChecked {
+            raw: bytes.as_ptr(),
+            _data: PhantomData,
+        })
+    }
+
+    pub fn amount(&self) -> u64 {
+        unsafe {
+            let amount = self.raw as *const u64;
+            amount.read_unaligned()
+        }
+    }
+
+    pub fn decimals(&self) -> u8 {
+        unsafe { *self.raw.add(8) }
+    }
+}

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

@@ -7,17 +7,25 @@ use token_interface::{
 };
 
 pub mod approve;
+pub mod approve_checked;
 pub mod burn;
+pub mod burn_checked;
 pub mod close_account;
 pub mod freeze_account;
 pub mod initialize_account;
+pub mod initialize_account2;
+pub mod initialize_account3;
 pub mod initialize_mint;
+pub mod initialize_mint2;
 pub mod initialize_multisig;
+pub mod initialize_multisig2;
 pub mod mint_to;
+pub mod mint_to_checked;
 pub mod revoke;
 pub mod set_authority;
 pub mod thaw_account;
 pub mod transfer;
+pub mod transfer_checked;
 // Private processor to toggle the account state. This logic is reused by the
 // freeze and thaw account instructions.
 mod toggle_account_state;

+ 11 - 8
p-token/src/processor/transfer.rs

@@ -54,13 +54,15 @@ pub fn process_transfer(
 
     // Validates source and destination accounts.
 
-    let source_account_data = unsafe { source_account_info.borrow_mut_data_unchecked() };
-    let source_account = bytemuck::try_from_bytes_mut::<Account>(source_account_data)
-        .map_err(|_error| ProgramError::InvalidAccountData)?;
+    let source_account = bytemuck::try_from_bytes_mut::<Account>(unsafe {
+        source_account_info.borrow_mut_data_unchecked()
+    })
+    .map_err(|_error| ProgramError::InvalidAccountData)?;
 
-    let destination_account_data = unsafe { destination_account_info.borrow_mut_data_unchecked() };
-    let destination_account = bytemuck::try_from_bytes_mut::<Account>(destination_account_data)
-        .map_err(|_error| ProgramError::InvalidAccountData)?;
+    let destination_account = bytemuck::try_from_bytes_mut::<Account>(unsafe {
+        destination_account_info.borrow_mut_data_unchecked()
+    })
+    .map_err(|_error| ProgramError::InvalidAccountData)?;
 
     if source_account.is_frozen() || destination_account.is_frozen() {
         return Err(TokenError::AccountFrozen.into());
@@ -84,8 +86,9 @@ pub fn process_transfer(
             return Err(TokenError::MintMismatch.into());
         }
 
-        let mint_data = mint_info.try_borrow_data()?;
-        let mint = bytemuck::from_bytes::<Mint>(&mint_data);
+        let mint =
+            bytemuck::try_from_bytes_mut::<Mint>(unsafe { mint_info.borrow_mut_data_unchecked() })
+                .map_err(|_error| ProgramError::InvalidAccountData)?;
 
         if decimals != mint.decimals {
             return Err(TokenError::MintDecimalsMismatch.into());

+ 47 - 0
p-token/src/processor/transfer_checked.rs

@@ -0,0 +1,47 @@
+use std::marker::PhantomData;
+
+use pinocchio::{
+    account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey, ProgramResult,
+};
+
+use super::transfer::process_transfer;
+
+#[inline(always)]
+pub fn process_transfer_checked(
+    program_id: &Pubkey,
+    accounts: &[AccountInfo],
+    amount: u64,
+    decimals: u8,
+) -> ProgramResult {
+    process_transfer(program_id, accounts, amount, Some(decimals))
+}
+
+pub struct TransferChecked<'a> {
+    raw: *const u8,
+
+    _data: PhantomData<&'a [u8]>,
+}
+
+impl TransferChecked<'_> {
+    pub fn try_from_bytes(bytes: &[u8]) -> Result<TransferChecked, ProgramError> {
+        if bytes.len() != 9 {
+            return Err(ProgramError::InvalidInstructionData);
+        }
+
+        Ok(TransferChecked {
+            raw: bytes.as_ptr(),
+            _data: PhantomData,
+        })
+    }
+
+    pub fn amount(&self) -> u64 {
+        unsafe {
+            let amount = self.raw as *const u64;
+            amount.read_unaligned()
+        }
+    }
+
+    pub fn decimals(&self) -> u8 {
+        unsafe { *self.raw.add(8) }
+    }
+}

+ 91 - 0
p-token/tests/approve_checked.rs

@@ -0,0 +1,91 @@
+#![cfg(feature = "test-sbf")]
+
+mod setup;
+
+use setup::{account, mint};
+use solana_program_test::{tokio, ProgramTest};
+use solana_sdk::{
+    program_pack::Pack,
+    pubkey::Pubkey,
+    signature::{Keypair, Signer},
+    transaction::Transaction,
+};
+
+#[test_case::test_case(spl_token::ID ; "spl-token")]
+#[test_case::test_case(Pubkey::new_from_array(token_program::ID) ; "p-token")]
+#[tokio::test]
+async fn approve_checked(token_program: Pubkey) {
+    let program_id = Pubkey::new_from_array(token_program::ID);
+    let mut context = ProgramTest::new("token_program", program_id, None)
+        .start_with_context()
+        .await;
+
+    // Given a mint account.
+
+    let mint_authority = Keypair::new();
+    let freeze_authority = Pubkey::new_unique();
+
+    let mint = mint::initialize(
+        &mut context,
+        mint_authority.pubkey(),
+        Some(freeze_authority),
+        &token_program,
+    )
+    .await
+    .unwrap();
+
+    // And a token account with 100 tokens.
+
+    let owner = Keypair::new();
+
+    let account = account::initialize(&mut context, &mint, &owner.pubkey(), &token_program).await;
+
+    mint::mint(
+        &mut context,
+        &mint,
+        &account,
+        &mint_authority,
+        100,
+        &token_program,
+    )
+    .await
+    .unwrap();
+
+    // When we approve a delegate.
+
+    let delegate = Pubkey::new_unique();
+
+    let mut approve_ix = spl_token::instruction::approve_checked(
+        &spl_token::ID,
+        &account,
+        &mint,
+        &delegate,
+        &owner.pubkey(),
+        &[],
+        50,
+        0,
+    )
+    .unwrap();
+    approve_ix.program_id = token_program;
+
+    let tx = Transaction::new_signed_with_payer(
+        &[approve_ix],
+        Some(&context.payer.pubkey()),
+        &[&context.payer, &owner],
+        context.last_blockhash,
+    );
+    context.banks_client.process_transaction(tx).await.unwrap();
+
+    // Then the account should have the delegate and delegated amount.
+
+    let account = context.banks_client.get_account(account).await.unwrap();
+
+    assert!(account.is_some());
+
+    let account = account.unwrap();
+    let account = spl_token::state::Account::unpack(&account.data).unwrap();
+
+    assert!(account.delegate.is_some());
+    assert!(account.delegate.unwrap() == delegate);
+    assert!(account.delegated_amount == 50);
+}

+ 86 - 0
p-token/tests/burn_checked.rs

@@ -0,0 +1,86 @@
+#![cfg(feature = "test-sbf")]
+
+mod setup;
+
+use setup::{account, mint};
+use solana_program_test::{tokio, ProgramTest};
+use solana_sdk::{
+    program_pack::Pack,
+    pubkey::Pubkey,
+    signature::{Keypair, Signer},
+    transaction::Transaction,
+};
+
+#[test_case::test_case(spl_token::ID ; "spl-token")]
+#[test_case::test_case(Pubkey::new_from_array(token_program::ID) ; "p-token")]
+#[tokio::test]
+async fn burn_checked(token_program: Pubkey) {
+    let program_id = Pubkey::new_from_array(token_program::ID);
+    let mut context = ProgramTest::new("token_program", program_id, None)
+        .start_with_context()
+        .await;
+
+    // Given a mint account.
+
+    let mint_authority = Keypair::new();
+    let freeze_authority = Pubkey::new_unique();
+
+    let mint = mint::initialize(
+        &mut context,
+        mint_authority.pubkey(),
+        Some(freeze_authority),
+        &token_program,
+    )
+    .await
+    .unwrap();
+
+    // And a token account with 100 tokens.
+
+    let owner = Keypair::new();
+
+    let account = account::initialize(&mut context, &mint, &owner.pubkey(), &token_program).await;
+
+    mint::mint(
+        &mut context,
+        &mint,
+        &account,
+        &mint_authority,
+        100,
+        &token_program,
+    )
+    .await
+    .unwrap();
+
+    // When we burn 50 tokens.
+
+    let mut burn_ix = spl_token::instruction::burn_checked(
+        &spl_token::ID,
+        &account,
+        &mint,
+        &owner.pubkey(),
+        &[],
+        50,
+        0,
+    )
+    .unwrap();
+    burn_ix.program_id = token_program;
+
+    let tx = Transaction::new_signed_with_payer(
+        &[burn_ix],
+        Some(&context.payer.pubkey()),
+        &[&context.payer, &owner],
+        context.last_blockhash,
+    );
+    context.banks_client.process_transaction(tx).await.unwrap();
+
+    // Then the account should have 50 tokens remaining.
+
+    let account = context.banks_client.get_account(account).await.unwrap();
+
+    assert!(account.is_some());
+
+    let account = account.unwrap();
+    let account = spl_token::state::Account::unpack(&account.data).unwrap();
+
+    assert!(account.amount == 50);
+}

+ 93 - 0
p-token/tests/initialize_account2.rs

@@ -0,0 +1,93 @@
+#![cfg(feature = "test-sbf")]
+
+mod setup;
+
+use setup::mint;
+use solana_program_test::{tokio, ProgramTest};
+use solana_sdk::{
+    program_pack::Pack,
+    pubkey::Pubkey,
+    signature::{Keypair, Signer},
+    system_instruction,
+    transaction::Transaction,
+};
+
+#[test_case::test_case(spl_token::ID ; "spl-token")]
+#[test_case::test_case(Pubkey::new_from_array(token_program::ID) ; "p-token")]
+#[tokio::test]
+async fn initialize_account2(token_program: Pubkey) {
+    let program_id = Pubkey::new_from_array(token_program::ID);
+    let mut context = ProgramTest::new("token_program", program_id, None)
+        .start_with_context()
+        .await;
+
+    // Given a mint account.
+
+    let mint_authority = Pubkey::new_unique();
+    let freeze_authority = Pubkey::new_unique();
+
+    let mint = mint::initialize(
+        &mut context,
+        mint_authority,
+        Some(freeze_authority),
+        &token_program,
+    )
+    .await
+    .unwrap();
+
+    // Given a mint authority, freeze authority and an account keypair.
+
+    let owner = Pubkey::new_unique();
+    let account = Keypair::new();
+
+    let account_size = 165;
+    let rent = context.banks_client.get_rent().await.unwrap();
+
+    let mut initialize_ix = spl_token::instruction::initialize_account2(
+        &spl_token::ID,
+        &account.pubkey(),
+        &mint,
+        &owner,
+    )
+    .unwrap();
+    // Switches the program id to the token program.
+    initialize_ix.program_id = token_program;
+
+    // When a new mint account is created and initialized.
+
+    let instructions = vec![
+        system_instruction::create_account(
+            &context.payer.pubkey(),
+            &account.pubkey(),
+            rent.minimum_balance(account_size),
+            account_size as u64,
+            &token_program,
+        ),
+        initialize_ix,
+    ];
+
+    let tx = Transaction::new_signed_with_payer(
+        &instructions,
+        Some(&context.payer.pubkey()),
+        &[&context.payer, &account],
+        context.last_blockhash,
+    );
+    context.banks_client.process_transaction(tx).await.unwrap();
+
+    // Then an account has the correct data.
+
+    let account = context
+        .banks_client
+        .get_account(account.pubkey())
+        .await
+        .unwrap();
+
+    assert!(account.is_some());
+
+    let account = account.unwrap();
+    let account = spl_token::state::Account::unpack(&account.data).unwrap();
+
+    assert!(!account.is_frozen());
+    assert!(account.owner == owner);
+    assert!(account.mint == mint);
+}

+ 93 - 0
p-token/tests/initialize_account3.rs

@@ -0,0 +1,93 @@
+#![cfg(feature = "test-sbf")]
+
+mod setup;
+
+use setup::mint;
+use solana_program_test::{tokio, ProgramTest};
+use solana_sdk::{
+    program_pack::Pack,
+    pubkey::Pubkey,
+    signature::{Keypair, Signer},
+    system_instruction,
+    transaction::Transaction,
+};
+
+#[test_case::test_case(spl_token::ID ; "spl-token")]
+#[test_case::test_case(Pubkey::new_from_array(token_program::ID) ; "p-token")]
+#[tokio::test]
+async fn initialize_account3(token_program: Pubkey) {
+    let program_id = Pubkey::new_from_array(token_program::ID);
+    let mut context = ProgramTest::new("token_program", program_id, None)
+        .start_with_context()
+        .await;
+
+    // Given a mint account.
+
+    let mint_authority = Pubkey::new_unique();
+    let freeze_authority = Pubkey::new_unique();
+
+    let mint = mint::initialize(
+        &mut context,
+        mint_authority,
+        Some(freeze_authority),
+        &token_program,
+    )
+    .await
+    .unwrap();
+
+    // Given a mint authority, freeze authority and an account keypair.
+
+    let owner = Pubkey::new_unique();
+    let account = Keypair::new();
+
+    let account_size = 165;
+    let rent = context.banks_client.get_rent().await.unwrap();
+
+    let mut initialize_ix = spl_token::instruction::initialize_account3(
+        &spl_token::ID,
+        &account.pubkey(),
+        &mint,
+        &owner,
+    )
+    .unwrap();
+    // Switches the program id to the token program.
+    initialize_ix.program_id = token_program;
+
+    // When a new mint account is created and initialized.
+
+    let instructions = vec![
+        system_instruction::create_account(
+            &context.payer.pubkey(),
+            &account.pubkey(),
+            rent.minimum_balance(account_size),
+            account_size as u64,
+            &token_program,
+        ),
+        initialize_ix,
+    ];
+
+    let tx = Transaction::new_signed_with_payer(
+        &instructions,
+        Some(&context.payer.pubkey()),
+        &[&context.payer, &account],
+        context.last_blockhash,
+    );
+    context.banks_client.process_transaction(tx).await.unwrap();
+
+    // Then an account has the correct data.
+
+    let account = context
+        .banks_client
+        .get_account(account.pubkey())
+        .await
+        .unwrap();
+
+    assert!(account.is_some());
+
+    let account = account.unwrap();
+    let account = spl_token::state::Account::unpack(&account.data).unwrap();
+
+    assert!(!account.is_frozen());
+    assert!(account.owner == owner);
+    assert!(account.mint == mint);
+}

+ 83 - 0
p-token/tests/initialize_mint2.rs

@@ -0,0 +1,83 @@
+#![cfg(feature = "test-sbf")]
+
+use std::mem::size_of;
+
+use solana_program_test::{tokio, ProgramTest};
+use solana_sdk::{
+    program_option::COption,
+    program_pack::Pack,
+    pubkey::Pubkey,
+    signature::{Keypair, Signer},
+    system_instruction,
+    transaction::Transaction,
+};
+use token_interface::state::mint::Mint;
+
+#[test_case::test_case(spl_token::ID ; "spl-token")]
+#[test_case::test_case(Pubkey::new_from_array(token_program::ID) ; "p-token")]
+#[tokio::test]
+async fn initialize_mint2(token_program: Pubkey) {
+    let program_id = Pubkey::new_from_array(token_program::ID);
+    let mut context = ProgramTest::new("token_program", program_id, None)
+        .start_with_context()
+        .await;
+
+    // Given a mint authority, freeze authority and an account keypair.
+
+    let mint_authority = Pubkey::new_unique();
+    let freeze_authority = Pubkey::new_unique();
+    let account = Keypair::new();
+
+    let account_size = size_of::<Mint>();
+    let rent = context.banks_client.get_rent().await.unwrap();
+
+    let mut initialize_ix = spl_token::instruction::initialize_mint2(
+        &spl_token::ID,
+        &account.pubkey(),
+        &mint_authority,
+        Some(&freeze_authority),
+        0,
+    )
+    .unwrap();
+    // Switches the program id to the token program.
+    initialize_ix.program_id = token_program;
+
+    // When a new mint account is created and initialized.
+
+    let instructions = vec![
+        system_instruction::create_account(
+            &context.payer.pubkey(),
+            &account.pubkey(),
+            rent.minimum_balance(account_size),
+            account_size as u64,
+            &token_program,
+        ),
+        initialize_ix,
+    ];
+
+    let tx = Transaction::new_signed_with_payer(
+        &instructions,
+        Some(&context.payer.pubkey()),
+        &[&context.payer, &account],
+        context.last_blockhash,
+    );
+    context.banks_client.process_transaction(tx).await.unwrap();
+
+    // Then an account has the correct data.
+
+    let account = context
+        .banks_client
+        .get_account(account.pubkey())
+        .await
+        .unwrap();
+
+    assert!(account.is_some());
+
+    let account = account.unwrap();
+    let mint = spl_token::state::Mint::unpack(&account.data).unwrap();
+
+    assert!(mint.is_initialized);
+    assert!(mint.mint_authority == COption::Some(mint_authority));
+    assert!(mint.freeze_authority == COption::Some(freeze_authority));
+    assert!(mint.decimals == 0)
+}

+ 79 - 0
p-token/tests/initialize_multisig2.rs

@@ -0,0 +1,79 @@
+#![cfg(feature = "test-sbf")]
+
+use solana_program_test::{tokio, ProgramTest};
+use solana_sdk::{
+    program_pack::Pack,
+    pubkey::Pubkey,
+    signature::{Keypair, Signer},
+    system_instruction,
+    transaction::Transaction,
+};
+use spl_token::state::Multisig;
+
+#[test_case::test_case(spl_token::ID ; "spl-token")]
+#[test_case::test_case(Pubkey::new_from_array(token_program::ID) ; "p-token")]
+#[tokio::test]
+async fn initialize_multisig2(token_program: Pubkey) {
+    let program_id = Pubkey::new_from_array(token_program::ID);
+    let mut context = ProgramTest::new("token_program", program_id, None)
+        .start_with_context()
+        .await;
+
+    // Given an account
+
+    let multisig = Keypair::new();
+    let signer1 = Pubkey::new_unique();
+    let signer2 = Pubkey::new_unique();
+    let signer3 = Pubkey::new_unique();
+    let signers = vec![&signer1, &signer2, &signer3];
+
+    let rent = context.banks_client.get_rent().await.unwrap();
+
+    let mut initialize_ix = spl_token::instruction::initialize_multisig2(
+        &spl_token::ID,
+        &multisig.pubkey(),
+        &signers,
+        2,
+    )
+    .unwrap();
+    // Switches the program id to the token program.
+    initialize_ix.program_id = token_program;
+
+    // When a new multisig account is created and initialized.
+
+    let instructions = vec![
+        system_instruction::create_account(
+            &context.payer.pubkey(),
+            &multisig.pubkey(),
+            rent.minimum_balance(Multisig::LEN),
+            Multisig::LEN as u64,
+            &token_program,
+        ),
+        initialize_ix,
+    ];
+
+    let tx = Transaction::new_signed_with_payer(
+        &instructions,
+        Some(&context.payer.pubkey()),
+        &[&context.payer, &multisig],
+        context.last_blockhash,
+    );
+    context.banks_client.process_transaction(tx).await.unwrap();
+
+    // Then the multisig has the correct data.
+
+    let account = context
+        .banks_client
+        .get_account(multisig.pubkey())
+        .await
+        .unwrap();
+
+    assert!(account.is_some());
+
+    let account = account.unwrap();
+    let multisig = spl_token::state::Multisig::unpack(&account.data).unwrap();
+
+    assert!(multisig.is_initialized);
+    assert_eq!(multisig.n, 3);
+    assert_eq!(multisig.m, 2);
+}

+ 76 - 0
p-token/tests/mint_to_checked.rs

@@ -0,0 +1,76 @@
+#![cfg(feature = "test-sbf")]
+
+mod setup;
+
+use setup::{account, mint};
+use solana_program_test::{tokio, ProgramTest};
+use solana_sdk::{
+    program_pack::Pack,
+    pubkey::Pubkey,
+    signature::{Keypair, Signer},
+    transaction::Transaction,
+};
+
+#[test_case::test_case(spl_token::ID ; "spl-token")]
+#[test_case::test_case(Pubkey::new_from_array(token_program::ID) ; "p-token")]
+#[tokio::test]
+async fn mint_to_checked(token_program: Pubkey) {
+    let program_id = Pubkey::new_from_array(token_program::ID);
+    let mut context = ProgramTest::new("token_program", program_id, None)
+        .start_with_context()
+        .await;
+
+    // Given a mint account.
+
+    let mint_authority = Keypair::new();
+    let freeze_authority = Pubkey::new_unique();
+
+    let mint = mint::initialize(
+        &mut context,
+        mint_authority.pubkey(),
+        Some(freeze_authority),
+        &token_program,
+    )
+    .await
+    .unwrap();
+
+    // And a token account.
+
+    let owner = Keypair::new();
+
+    let account = account::initialize(&mut context, &mint, &owner.pubkey(), &token_program).await;
+
+    // When we mint tokens to it.
+
+    let mut mint_ix = spl_token::instruction::mint_to_checked(
+        &spl_token::ID,
+        &mint,
+        &account,
+        &mint_authority.pubkey(),
+        &[],
+        100,
+        0,
+    )
+    .unwrap();
+    // Switches the program id to the token program.
+    mint_ix.program_id = token_program;
+
+    let tx = Transaction::new_signed_with_payer(
+        &[mint_ix],
+        Some(&context.payer.pubkey()),
+        &[&context.payer, &mint_authority],
+        context.last_blockhash,
+    );
+    context.banks_client.process_transaction(tx).await.unwrap();
+
+    // Then an account has the correct data.
+
+    let account = context.banks_client.get_account(account).await.unwrap();
+
+    assert!(account.is_some());
+
+    let account = account.unwrap();
+    let account = spl_token::state::Account::unpack(&account.data).unwrap();
+
+    assert!(account.amount == 100);
+}

+ 92 - 0
p-token/tests/transfer_checked.rs

@@ -0,0 +1,92 @@
+#![cfg(feature = "test-sbf")]
+
+mod setup;
+
+use setup::{account, mint};
+use solana_program_test::{tokio, ProgramTest};
+use solana_sdk::{
+    program_pack::Pack,
+    pubkey::Pubkey,
+    signature::{Keypair, Signer},
+    transaction::Transaction,
+};
+
+#[test_case::test_case(spl_token::ID ; "spl-token")]
+#[test_case::test_case(Pubkey::new_from_array(token_program::ID) ; "p-token")]
+#[tokio::test]
+async fn transfer_checked(token_program: Pubkey) {
+    let program_id = Pubkey::new_from_array(token_program::ID);
+    let mut context = ProgramTest::new("token_program", program_id, None)
+        .start_with_context()
+        .await;
+
+    // Given a mint account.
+
+    let mint_authority = Keypair::new();
+    let freeze_authority = Pubkey::new_unique();
+
+    let mint = mint::initialize(
+        &mut context,
+        mint_authority.pubkey(),
+        Some(freeze_authority),
+        &token_program,
+    )
+    .await
+    .unwrap();
+
+    // And a token account with 100 tokens.
+
+    let owner = Keypair::new();
+
+    let account = account::initialize(&mut context, &mint, &owner.pubkey(), &token_program).await;
+
+    mint::mint(
+        &mut context,
+        &mint,
+        &account,
+        &mint_authority,
+        100,
+        &token_program,
+    )
+    .await
+    .unwrap();
+
+    // When we transfer the tokens.
+
+    let destination = Pubkey::new_unique();
+
+    let destination_account =
+        account::initialize(&mut context, &mint, &destination, &token_program).await;
+
+    let mut transfer_ix = spl_token::instruction::transfer_checked(
+        &spl_token::ID,
+        &account,
+        &mint,
+        &destination_account,
+        &owner.pubkey(),
+        &[],
+        100,
+        0,
+    )
+    .unwrap();
+    transfer_ix.program_id = token_program;
+
+    let tx = Transaction::new_signed_with_payer(
+        &[transfer_ix],
+        Some(&context.payer.pubkey()),
+        &[&context.payer, &owner],
+        context.last_blockhash,
+    );
+    context.banks_client.process_transaction(tx).await.unwrap();
+
+    // Then an account has the correct data.
+
+    let account = context.banks_client.get_account(account).await.unwrap();
+
+    assert!(account.is_some());
+
+    let account = account.unwrap();
+    let account = spl_token::state::Account::unpack(&account.data).unwrap();
+
+    assert!(account.amount == 0);
+}