xprogram 0.1.0

program for testing
Documentation
use solana_program::{
    account_info::{next_account_info, AccountInfo},
    entrypoint::ProgramResult,
    msg,
    program_error::ProgramError,
    pubkey::Pubkey,
};

use crate::{instruction::TopicInstruction, state::Topic};

pub struct Processor {}
impl Processor {
    pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
        let i = TopicInstruction::unpack(input)?;

        Ok(match i {
            TopicInstruction::CreateTopic {
                topic_name,
                option_name,
            } => {
                msg!("process create topic");
                Processor::process_create_topic(program_id, accounts, topic_name, option_name)?
            }
            TopicInstruction::AddOption { option_name } => {
                msg!("process add option");
                Processor::process_add_option(program_id, accounts, option_name)?
            }
            TopicInstruction::VoteTopic { opt_idx } => {
                msg!("process vote topic");
                Processor::process_vote(program_id, accounts, opt_idx)?
            }
            TopicInstruction::FinishTopic => {
                msg!("process finish topic");
                Processor::process_finish(program_id, accounts)?
            }
        })
    }

    pub fn process_create_topic(
        program_id: &Pubkey,
        accounts: &[AccountInfo],
        topic_name: &str,
        option_name: &str,
    ) -> ProgramResult {
        let accs_iter = &mut accounts.iter();
        let topic_account = next_account_info(accs_iter)?;
        let topic_owner = next_account_info(accs_iter)?;

        if topic_account.owner != program_id {
            return Err(ProgramError::IllegalOwner);
        }
        if !topic_owner.is_signer {
            return Err(ProgramError::MissingRequiredSignature);
        }

        let mut topic = Topic::unpack_from_slice(&topic_account.data.borrow())?;
        if !topic.name_is_empty() {
            return Err(ProgramError::AccountAlreadyInitialized);
        }
        topic.set_name(topic_name);
        topic.owner = *topic_owner.key;
        topic.add_option(topic_account.key, option_name)?;
        topic.pack_into_slice(&mut topic_account.data.borrow_mut());
        Ok(())
    }

    pub fn process_add_option(
        program_id: &Pubkey,
        accounts: &[AccountInfo],
        option_name: &str,
    ) -> ProgramResult {
        let accs_iter = &mut accounts.iter();
        let topic_account = next_account_info(accs_iter)?;
        let option_adder = next_account_info(accs_iter)?;

        if topic_account.owner != program_id {
            return Err(ProgramError::IllegalOwner);
        }
        if !option_adder.is_signer {
            return Err(ProgramError::MissingRequiredSignature);
        }

        let mut topic = Topic::unpack_from_slice(&topic_account.data.borrow())?;
        if topic.name.is_empty() || topic.is_finished {
            return Err(ProgramError::InvalidAccountData);
        }
        topic.add_option(topic_account.key, option_name)?;
        topic.pack_into_slice(&mut topic_account.data.borrow_mut());
        Ok(())
    }

    pub fn process_vote(
        program_id: &Pubkey,
        accounts: &[AccountInfo],
        opt_idx: u8,
    ) -> ProgramResult {
        let accs_iter = &mut accounts.iter();
        let topic_account = next_account_info(accs_iter)?;
        let voter = next_account_info(accs_iter)?;

        if topic_account.owner != program_id {
            return Err(ProgramError::IllegalOwner);
        }
        if !voter.is_signer {
            return Err(ProgramError::MissingRequiredSignature);
        }

        let mut topic = Topic::unpack_from_slice(&topic_account.data.borrow())?;
        if topic.name.is_empty() || topic.is_finished {
            return Err(ProgramError::InvalidAccountData);
        }
        topic.vote(opt_idx, voter.key)?;
        topic.pack_into_slice(&mut topic_account.data.borrow_mut());
        Ok(())
    }

    pub fn process_finish(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
        let accs_iter = &mut accounts.iter();
        let topic_account = next_account_info(accs_iter)?;
        let topic_owner = next_account_info(accs_iter)?;

        if topic_account.owner != program_id {
            return Err(ProgramError::IllegalOwner);
        }
        if !topic_owner.is_signer {
            return Err(ProgramError::MissingRequiredSignature);
        }
        let mut topic = Topic::unpack_from_slice(&topic_account.data.borrow())?;
        if topic.name.is_empty() || topic.is_finished {
            return Err(ProgramError::InvalidAccountData);
        }
        if topic.owner != *topic_owner.key {
            return Err(ProgramError::IllegalOwner);
        }
        topic.finalize()?;
        topic.pack_into_slice(&mut topic_account.data.borrow_mut());
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::instruction::{add_option, create_topic, finish_topic, vote_topic};
    use solana_program::{instruction::Instruction, system_program};
    use solana_sdk::account::{create_is_signer_account_infos, Account as SolanaAccount};

    fn do_process_instruction(
        instruction: Instruction,
        accounts: Vec<&mut SolanaAccount>,
    ) -> ProgramResult {
        let mut meta = instruction
            .accounts
            .iter()
            .zip(accounts)
            .map(|(account_meta, account)| (&account_meta.pubkey, account_meta.is_signer, account))
            .collect::<Vec<_>>();

        let account_infos = create_is_signer_account_infos(&mut meta);
        Processor::process(&instruction.program_id, &account_infos, &instruction.data)
    }

    struct TestSuite {
        program_id: Pubkey,
        topic_key: (Pubkey, SolanaAccount),
        topic_owner: (Pubkey, SolanaAccount),
    }

    impl TestSuite {
        fn new() -> TestSuite {
            let pid = Pubkey::default();
            TestSuite {
                program_id: pid,
                topic_key: Self::get_key_account(&pid, Topic::get_packed_len()),
                topic_owner: Self::get_key_account(&system_program::ID, Topic::get_packed_len()),
            }
        }

        fn topic_eq(&self, expect_topic: &Topic) -> Result<bool, ProgramError> {
            let topic = Topic::unpack_from_slice(&self.topic_key.1.data)?;
            Ok(topic.eq(expect_topic))
        }

        fn get_key_account(owner: &Pubkey, space: usize) -> (Pubkey, SolanaAccount) {
            (
                Pubkey::new_unique(),
                SolanaAccount::new(10000, space, owner),
            )
        }

        fn process_create_topic(&mut self, topic_name: &str, option_name: &str) -> ProgramResult {
            let i = create_topic(
                &self.program_id,
                &self.topic_key.0,
                &self.topic_owner.0,
                topic_name,
                option_name,
            )?;
            do_process_instruction(i, vec![&mut self.topic_key.1, &mut self.topic_owner.1])
        }

        fn process_add_option(&mut self, option_name: &str) -> ProgramResult {
            let i = add_option(
                &self.program_id,
                &self.topic_key.0,
                &self.topic_owner.0,
                option_name,
            )?;
            do_process_instruction(i, vec![&mut self.topic_key.1, &mut self.topic_owner.1])
        }

        fn process_vote(
            &mut self,
            option_idx: u8,
            voter: &mut (Pubkey, SolanaAccount),
        ) -> ProgramResult {
            let i = vote_topic(&self.program_id, &self.topic_key.0, &voter.0, option_idx)?;
            do_process_instruction(i, vec![&mut self.topic_key.1, &mut voter.1])
        }

        fn process_finish(&mut self) -> ProgramResult {
            let i = finish_topic(&self.program_id, &self.topic_key.0, &self.topic_owner.0)?;
            do_process_instruction(i, vec![&mut self.topic_key.1, &mut self.topic_owner.1])
        }

        fn process_init_topic(
            &mut self,
            topic_name: &str,
            first_opt: &str,
            opts: Vec<&str>,
        ) -> ProgramResult {
            self.process_create_topic(topic_name, first_opt)?;
            for opt in opts {
                self.process_add_option(opt)?;
            }
            Ok(())
        }
    }

    #[test]
    fn test_create_topic() {
        let mut ts = TestSuite::new();
        let topic_name = "test_topic";
        let opt_name = "test_option";
        ts.process_create_topic(topic_name, opt_name).unwrap();
        let mut expect = Topic::new("test_topic", &ts.topic_owner.0);
        expect.add_option(&ts.topic_key.0, opt_name).unwrap();
        assert_eq!(Ok(true), ts.topic_eq(&expect));

        assert_eq!(
            Err(ProgramError::AccountAlreadyInitialized),
            ts.process_create_topic("test_topic_2", "test_option")
        )
    }

    #[test]
    fn test_add_option() {
        let mut ts = TestSuite::new();
        let topic_name = "test_topic";
        let opt_name = "test_option";
        ts.process_create_topic(topic_name, opt_name).unwrap();
        let mut expect_topic = Topic::new(topic_name, &ts.topic_owner.0);
        expect_topic.add_option(&ts.topic_key.0, opt_name).unwrap();
        let opt_name = "test_option2";
        ts.process_add_option(opt_name).unwrap();
        expect_topic.add_option(&ts.topic_key.0, opt_name).unwrap();

        assert_eq!(Ok(true), ts.topic_eq(&expect_topic))
    }

    #[test]
    fn test_vote() {
        let mut ts = TestSuite::new();
        let topic_name = "test_topic";
        let opt_name = "test_option";
        let opt_name2 = "test_option2";
        ts.process_init_topic(topic_name, opt_name, vec![opt_name2])
            .unwrap();
        let mut expect_topic = Topic::new(topic_name, &ts.topic_owner.0);
        expect_topic.add_option(&ts.topic_key.0, opt_name).unwrap();
        expect_topic.add_option(&ts.topic_key.0, opt_name2).unwrap();

        let mut key_acc = TestSuite::get_key_account(&system_program::ID, 100);
        ts.process_vote(0, &mut key_acc).unwrap();
        expect_topic.options[0].add_voter(&key_acc.0).unwrap();
        assert_eq!(Ok(true), ts.topic_eq(&expect_topic))
    }

    #[test]
    fn test_finish_topic() {
        let mut ts = TestSuite::new();
        let topic_name = "test_topic";
        let opt_name = "test_option";
        let opt_name2 = "test_option2";
        ts.process_init_topic(topic_name, opt_name, vec![opt_name2])
            .unwrap();
        let mut expect_topic = Topic::new(topic_name, &ts.topic_owner.0);
        expect_topic.add_option(&ts.topic_key.0, opt_name).unwrap();
        expect_topic.add_option(&ts.topic_key.0, opt_name2).unwrap();
        ts.process_finish().unwrap();
        expect_topic.is_finished = true;
        expect_topic.result_idx = 1;
        assert_eq!(Ok(true), ts.topic_eq(&expect_topic))
    }
}