xprogram 0.1.0

program for testing
Documentation
use crate::util::{str_pack, str_unpack};
use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs};
use solana_program::{program_error::ProgramError, pubkey::Pubkey};

const MAX_TOPIC_NAME: usize = 100;
const MAX_OPTION_NAME: usize = 100;

#[repr(C)]
#[derive(Clone, Debug, PartialEq)]
pub struct Topic {
    pub name: [u8; 100],
    pub options: [Option; 10],
    pub opt_current_idx: u8,
    pub owner: Pubkey,
    pub result_idx: u8,
    pub is_finished: bool,
}

impl Default for Topic {
    fn default() -> Self {
        Topic {
            name: [0; 100],
            options: [Option::default(); 10],
            opt_current_idx: 0,
            owner: Pubkey::default(),
            result_idx: 0,
            is_finished: false,
        }
    }
}

impl Topic {
    pub fn new(name: &str, owner: &Pubkey) -> Topic {
        let mut name_data: [u8; 100] = [0; 100];
        str_pack(name, &mut &mut name_data);
        Topic {
            name: name_data,
            options: [Option::default(); 10],
            opt_current_idx: 0,
            owner: *owner,
            result_idx: 0,
            is_finished: false,
        }
    }

    pub fn finalize(&mut self) -> Result<(), ProgramError> {
        self.is_finished = true;
        self.result_idx = 1;
        Ok(())
    }

    pub fn set_name(&mut self, name: &str) {
        let mut name_data: [u8; 100] = [0; 100];
        str_pack(name, &mut &mut name_data);
        self.name = name_data;
    }

    pub fn name_is_empty(&self) -> bool {
        let name_str = str_unpack(&self.name);
        name_str.is_empty()
    }

    pub fn add_option(&mut self, topic_key: &Pubkey, opt_name: &str) -> Result<(), ProgramError> {
        if self.opt_current_idx as usize == self.options.len() - 1 {
            return Err(ProgramError::InvalidArgument);
        }
        let opt = Option::new(topic_key, self.opt_current_idx, opt_name);
        self.options[self.opt_current_idx as usize] = opt;
        self.opt_current_idx += 1;
        Ok(())
    }

    pub fn vote(&mut self, opt_idx: u8, voter: &Pubkey) -> Result<(), ProgramError> {
        if self.opt_current_idx < opt_idx {
            return Err(ProgramError::InvalidArgument);
        }
        self.options[opt_idx as usize].add_voter(voter)?;
        Ok(())
    }

    pub fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
        let src = array_ref!(src, 0, 11075);
        let (name, options_bytes, opt_current_idx, owner, result_idx, is_finished) =
            array_refs![src, MAX_TOPIC_NAME, 10940, 1, 32, 1, 1];
        let mut options = [Option::default(); 10];
        for (i, option) in options.iter_mut().enumerate() {
            let (start, end) = (i * 1094, i * 1094 + 1094);
            *option = Option::unpack_from_slice(&options_bytes[start..end])?;
        }
        let opt_current_idx = opt_current_idx[0];
        let owner = Pubkey::new(owner);
        let result_idx = result_idx[0];
        let is_finished = if is_finished[0] == 1 { true } else { false };
        Ok(Topic {
            name: *name,
            options,
            opt_current_idx,
            owner,
            result_idx,
            is_finished,
        })
    }

    pub fn pack_into_slice(&self, dst: &mut [u8]) {
        let src = array_mut_ref!(dst, 0, 11075);
        let (name, options_bytes, opt_current_idx, owner, result_idx, is_finished) =
            mut_array_refs![src, MAX_TOPIC_NAME, 10940, 1, 32, 1, 1];
        name.copy_from_slice(&self.name);
        for (i, option) in self.options.iter().enumerate() {
            let (start, end) = (i * 1094, i * 1094 + 1094);
            option.pack_into_slice(&mut options_bytes[start..end]);
        }
        opt_current_idx[0] = self.opt_current_idx;
        owner.copy_from_slice(&self.owner.to_bytes());
        result_idx[0] = self.result_idx;
        if self.is_finished {
            is_finished[0] = 1;
        }
    }

    pub fn empty_bytes() -> [u8; 12168] {
        [0; 12168]
    }

    pub fn get_packed_len() -> usize {
        12168
    }
}

#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Option {
    belongs_to: Pubkey,
    belongs_idx: u8,
    name: [u8; 100],
    voters: [Pubkey; 30],
    current_voter_index: u8,
}

impl Default for Option {
    fn default() -> Self {
        Option {
            belongs_to: Pubkey::default(),
            belongs_idx: 0,
            name: [0; 100],
            voters: [Pubkey::default(); 30],
            current_voter_index: 0,
        }
    }
}

impl Option {
    pub fn new(belongs_to: &Pubkey, belongs_idx: u8, name: &str) -> Option {
        let mut name_data = [0; 100];
        str_pack(name, &mut name_data);
        Option {
            belongs_to: *belongs_to,
            belongs_idx,
            name: name_data,
            voters: [Pubkey::default(); 30],
            current_voter_index: 0,
        }
    }

    pub fn add_voter(&mut self, voter: &Pubkey) -> Result<(), ProgramError> {
        if self.current_voter_index as usize == self.voters.len() - 1 {
            return Err(ProgramError::InvalidArgument);
        }

        self.voters[self.current_voter_index as usize] = *voter;
        self.current_voter_index += 1;
        Ok(())
    }

    pub fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
        let src = array_ref!(src, 0, 1094);
        let (belongs_to, belongs_idx, name, voters_bytes, current_voter_index) =
            array_refs![src, 32, 1, MAX_OPTION_NAME, 960, 1];
        let belongs_to = Pubkey::new(belongs_to);
        let belongs_idx = belongs_idx[0];
        let mut voters = [Pubkey::new_unique(); 30];
        for (i, voter) in voters.iter_mut().enumerate() {
            let (start, end) = (i * 32, i * 32 + 32);
            *voter = Pubkey::new(&voters_bytes[start..end]);
        }
        let current_voter_index = current_voter_index[0];

        Ok(Option {
            belongs_to,
            belongs_idx,
            name: *name,
            voters,
            current_voter_index,
        })
    }

    pub fn pack_into_slice(&self, dst: &mut [u8]) {
        let src = array_mut_ref![dst, 0, 1094];
        let (belongs_to, belongs_idx, name, voters_bytes, current_voter_index) =
            mut_array_refs![src, 32, 1, MAX_OPTION_NAME, 960, 1];
        belongs_to.copy_from_slice(&self.belongs_to.to_bytes());
        name.copy_from_slice(&self.name);
        belongs_idx[0] = self.belongs_idx;
        for (i, pubkey) in self.voters.iter().enumerate() {
            let (start, end) = (i * 32, i * 32 + 32);
            voters_bytes[start..end].copy_from_slice(&pubkey.to_bytes());
        }
        current_voter_index[0] = self.current_voter_index;
    }

    pub fn empty_bytes() -> [u8; 1094] {
        [0; 1094]
    }

    pub fn get_packed_len() -> usize {
        1094
    }
}

#[cfg(test)]
mod tests {
    use crate::state::{Option, Topic};
    use solana_program::pubkey::Pubkey;

    #[test]
    fn test_option_pack_unpack() {
        let pkey = Pubkey::default();
        let mut opt = Option::new(&pkey, 0, "test_option");
        let mut data = Option::empty_bytes();
        opt.add_voter(&Pubkey::new_unique()).unwrap();
        opt.pack_into_slice(&mut data[..]);

        let opt2 = Option::unpack_from_slice(&data).unwrap();
        assert_eq!(opt, opt2);
    }

    #[test]
    fn test_topic_pack_unpack() {
        let pk = Pubkey::new_unique();
        let mut topic = Topic::new("test_topic", &pk);
        topic.add_option(&pk, "option_name").unwrap();
        let mut data = Topic::empty_bytes();
        topic.pack_into_slice(&mut data);

        let topic2 = Topic::unpack_from_slice(&mut data).unwrap();
        assert_eq!(topic, topic2);
    }
}