spherenet-program-whitelist-interface 0.3.0

Interface of SphereNet's Program Whitelist Program
Documentation
//! Onchain functionality for the `RejectWhitelistEntry` instruction

use {
    crate::{
        instructions::ProgramWhitelistInstruction,
        onchain::{write_bytes, UNINIT_BYTE},
        program::ID,
    },
    core::{mem::MaybeUninit, slice::from_raw_parts},
    pinocchio::{
        account_info::AccountInfo,
        instruction::{AccountMeta, Instruction, Signer},
        program::invoke_signed,
        pubkey::Pubkey,
        ProgramResult,
    },
};

/// Rejects a pending whitelist entry, closing it and reclaiming its rent
/// lamports to the payer. The `whitelist_authority` must sign.
///
/// ### Accounts:
///   0. `[SIGNER, WRITE]` Payer (receives reclaimed rent)
///   1. `[SIGNER]` The whitelist authority
///   2. `[]` The whitelist account (used for validation)
///   3. `[WRITE]` The whitelist entry account to reject
pub struct RejectWhitelistEntry<'a> {
    /// Payer account (receives reclaimed rent)
    pub payer: &'a AccountInfo,
    /// The whitelist authority
    pub whitelist_authority: &'a AccountInfo,
    /// The whitelist account (used for validation)
    pub whitelist_account: &'a AccountInfo,
    /// The whitelist entry account to reject
    pub whitelist_entry_account: &'a AccountInfo,
    /// deploy authority public key
    pub deploy_authority: Pubkey,
}

impl<'a> RejectWhitelistEntry<'a> {
    #[inline(always)]
    pub fn invoke(&self) -> ProgramResult {
        self.invoke_signed(&[])
    }

    /// Creates the instruction data for the RejectWhitelistEntry instruction
    fn create_instruction_data(deploy_authority: &Pubkey) -> [MaybeUninit<u8>; 33] {
        let mut instruction_data = [UNINIT_BYTE; 33];

        // Set discriminator as u8 at offset [0]
        write_bytes(
            &mut instruction_data,
            &[ProgramWhitelistInstruction::REJECT_WHITELIST_ENTRY_TAG],
        );
        // Set deploy_authority as Pubkey at offset [1..33]
        write_bytes(&mut instruction_data[1..33], deploy_authority.as_ref());

        instruction_data
    }

    pub fn invoke_signed(&self, signers: &[Signer]) -> ProgramResult {
        // account metadata
        let account_metas: [AccountMeta; 4] = [
            AccountMeta::writable_signer(self.payer.key()),
            AccountMeta::readonly_signer(self.whitelist_authority.key()),
            AccountMeta::readonly(self.whitelist_account.key()),
            AccountMeta::writable(self.whitelist_entry_account.key()),
        ];

        // Create instruction data
        let instruction_data = Self::create_instruction_data(&self.deploy_authority);

        let instruction = Instruction {
            program_id: &ID,
            accounts: &account_metas,
            data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 33) },
        };

        invoke_signed(
            &instruction,
            &[
                self.payer,
                self.whitelist_authority,
                self.whitelist_account,
                self.whitelist_entry_account,
            ],
            signers,
        )
    }
}

/// Convenience helper for the `RejectWhitelistEntry` instruction
pub fn reject_whitelist_entry(
    payer: &AccountInfo,
    whitelist_authority: &AccountInfo,
    whitelist_account: &AccountInfo,
    whitelist_entry_account: &AccountInfo,
    deploy_authority: Pubkey,
) -> ProgramResult {
    let ix = RejectWhitelistEntry {
        payer,
        whitelist_authority,
        whitelist_account,
        whitelist_entry_account,
        deploy_authority,
    };
    ix.invoke()
}

/// Convenience helper for the `RejectWhitelistEntry` instruction with the given
/// signers.
pub fn reject_whitelist_entry_with_seed(
    payer: &AccountInfo,
    whitelist_authority: &AccountInfo,
    whitelist_account: &AccountInfo,
    whitelist_entry_account: &AccountInfo,
    deploy_authority: Pubkey,
    signer_seeds: &[Signer],
) -> ProgramResult {
    let ix = RejectWhitelistEntry {
        payer,
        whitelist_authority,
        whitelist_account,
        whitelist_entry_account,
        deploy_authority,
    };
    ix.invoke_signed(signer_seeds)
}

#[cfg(test)]
mod tests {
    use {super::*, crate::instructions::ProgramWhitelistInstruction};

    #[test]
    fn test_instruction_data_creation() {
        // Create a dummy pubkey for deploy authority
        let deploy_authority_bytes = [42u8; 32]; // Use a fixed value for testing
        let deploy_authority = Pubkey::from(deploy_authority_bytes);

        // Create instruction data using our function
        let instruction_data =
            RejectWhitelistEntry::create_instruction_data(&deploy_authority);

        // Create an instruction with this data for parsing
        let instruction = Instruction {
            program_id: &ID,
            accounts: &[], // Accounts not relevant for this test
            data: unsafe { from_raw_parts(instruction_data.as_ptr() as _, 33) },
        };

        // Parse the instruction data
        let parsed_ix =
            ProgramWhitelistInstruction::unpack(instruction.data).unwrap();

        // Verify that the parsed instruction matches what we expect
        match parsed_ix {
            ProgramWhitelistInstruction::RejectWhitelistEntry {
                deploy_authority: parsed_deploy_authority,
            } => {
                assert_eq!(parsed_deploy_authority, deploy_authority);
            }
            _ => panic!("Parsed incorrect instruction type"),
        }
    }
}