use crate::{state::AcceptanceCriteria, *};
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use solana_program::{
info,
instruction::{AccountMeta, Instruction},
program_error::ProgramError,
program_pack::{Pack, Sealed},
pubkey::Pubkey,
sysvar,
};
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, PartialEq)]
pub enum FeatureProposalInstruction {
Propose {
#[allow(dead_code)]
tokens_to_mint: u64,
#[allow(dead_code)]
acceptance_criteria: AcceptanceCriteria,
},
Tally,
}
impl Sealed for FeatureProposalInstruction {}
impl Pack for FeatureProposalInstruction {
const LEN: usize = 25;
fn pack_into_slice(&self, dst: &mut [u8]) {
let data = self.pack_into_vec();
dst[..data.len()].copy_from_slice(&data);
}
fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
let mut mut_src: &[u8] = src;
Self::deserialize(&mut mut_src).map_err(|err| {
info!(&format!(
"Error: failed to deserialize feature proposal instruction: {}",
err
));
ProgramError::InvalidInstructionData
})
}
}
impl FeatureProposalInstruction {
fn pack_into_vec(&self) -> Vec<u8> {
self.try_to_vec().expect("try_to_vec")
}
}
pub fn propose(
funding_address: &Pubkey,
feature_proposal_address: &Pubkey,
tokens_to_mint: u64,
acceptance_criteria: AcceptanceCriteria,
) -> Instruction {
let mint_address = get_mint_address(feature_proposal_address);
let distributor_token_address = get_distributor_token_address(feature_proposal_address);
let acceptance_token_address = get_acceptance_token_address(feature_proposal_address);
let feature_id_address = get_feature_id_address(feature_proposal_address);
Instruction {
program_id: id(),
accounts: vec![
AccountMeta::new(*funding_address, true),
AccountMeta::new(*feature_proposal_address, true),
AccountMeta::new(mint_address, false),
AccountMeta::new(distributor_token_address, false),
AccountMeta::new(acceptance_token_address, false),
AccountMeta::new(feature_id_address, false),
AccountMeta::new_readonly(solana_program::system_program::id(), false),
AccountMeta::new_readonly(spl_token::id(), false),
AccountMeta::new_readonly(sysvar::rent::id(), false),
],
data: FeatureProposalInstruction::Propose {
tokens_to_mint,
acceptance_criteria,
}
.pack_into_vec(),
}
}
pub fn tally(feature_proposal_address: &Pubkey) -> Instruction {
let acceptance_token_address = get_acceptance_token_address(feature_proposal_address);
let feature_id_address = get_feature_id_address(feature_proposal_address);
Instruction {
program_id: id(),
accounts: vec![
AccountMeta::new(*feature_proposal_address, false),
AccountMeta::new_readonly(acceptance_token_address, false),
AccountMeta::new(feature_id_address, false),
AccountMeta::new_readonly(solana_program::system_program::id(), false),
AccountMeta::new_readonly(sysvar::clock::id(), false),
],
data: FeatureProposalInstruction::Tally.pack_into_vec(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::borsh_utils;
#[test]
fn test_get_packed_len() {
assert_eq!(
FeatureProposalInstruction::get_packed_len(),
borsh_utils::get_packed_len::<FeatureProposalInstruction>()
)
}
#[test]
fn test_serialize_bytes() {
assert_eq!(
FeatureProposalInstruction::Tally.try_to_vec().unwrap(),
vec![1]
);
assert_eq!(
FeatureProposalInstruction::Propose {
tokens_to_mint: 42,
acceptance_criteria: AcceptanceCriteria {
tokens_required: 0xdeadbeefdeadbeef,
deadline: -1,
}
}
.try_to_vec()
.unwrap(),
vec![
0, 42, 0, 0, 0, 0, 0, 0, 0, 239, 190, 173, 222, 239, 190, 173, 222, 255, 255, 255,
255, 255, 255, 255, 255
]
);
}
#[test]
fn test_serialize_large_slice() {
let mut dst = vec![0xff; 4];
FeatureProposalInstruction::Tally.pack_into_slice(&mut dst);
assert_eq!(dst, vec![1, 0xff, 0xff, 0xff]);
}
#[test]
fn state_deserialize_invalid() {
assert_eq!(
FeatureProposalInstruction::unpack_from_slice(&[1]),
Ok(FeatureProposalInstruction::Tally),
);
assert_eq!(
FeatureProposalInstruction::unpack_from_slice(&[1, 0xff, 0xff, 0xff]),
Ok(FeatureProposalInstruction::Tally),
);
assert_eq!(
FeatureProposalInstruction::unpack_from_slice(&[2]),
Err(ProgramError::InvalidInstructionData),
);
}
}