1use crate::{state::AcceptanceCriteria, *};
4use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
5use solana_program::{
6 info,
7 instruction::{AccountMeta, Instruction},
8 program_error::ProgramError,
9 program_pack::{Pack, Sealed},
10 pubkey::Pubkey,
11 sysvar,
12};
13
14#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema, PartialEq)]
16pub enum FeatureProposalInstruction {
17 Propose {
52 #[allow(dead_code)] tokens_to_mint: u64,
55
56 #[allow(dead_code)] acceptance_criteria: AcceptanceCriteria,
59 },
60
61 Tally,
75}
76
77impl Sealed for FeatureProposalInstruction {}
78impl Pack for FeatureProposalInstruction {
79 const LEN: usize = 25; fn pack_into_slice(&self, dst: &mut [u8]) {
82 let data = self.pack_into_vec();
83 dst[..data.len()].copy_from_slice(&data);
84 }
85
86 fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
87 let mut mut_src: &[u8] = src;
88 Self::deserialize(&mut mut_src).map_err(|err| {
89 info!(&format!(
90 "Error: failed to deserialize feature proposal instruction: {}",
91 err
92 ));
93 ProgramError::InvalidInstructionData
94 })
95 }
96}
97
98impl FeatureProposalInstruction {
99 fn pack_into_vec(&self) -> Vec<u8> {
100 self.try_to_vec().expect("try_to_vec")
101 }
102}
103
104pub fn propose(
106 funding_address: &Pubkey,
107 feature_proposal_address: &Pubkey,
108 tokens_to_mint: u64,
109 acceptance_criteria: AcceptanceCriteria,
110) -> Instruction {
111 let mint_address = get_mint_address(feature_proposal_address);
112 let distributor_token_address = get_distributor_token_address(feature_proposal_address);
113 let acceptance_token_address = get_acceptance_token_address(feature_proposal_address);
114 let feature_id_address = get_feature_id_address(feature_proposal_address);
115
116 Instruction {
117 program_id: id(),
118 accounts: vec![
119 AccountMeta::new(*funding_address, true),
120 AccountMeta::new(*feature_proposal_address, true),
121 AccountMeta::new(mint_address, false),
122 AccountMeta::new(distributor_token_address, false),
123 AccountMeta::new(acceptance_token_address, false),
124 AccountMeta::new(feature_id_address, false),
125 AccountMeta::new_readonly(solana_program::system_program::id(), false),
126 AccountMeta::new_readonly(spl_token::id(), false),
127 AccountMeta::new_readonly(sysvar::rent::id(), false),
128 ],
129 data: FeatureProposalInstruction::Propose {
130 tokens_to_mint,
131 acceptance_criteria,
132 }
133 .pack_into_vec(),
134 }
135}
136
137pub fn tally(feature_proposal_address: &Pubkey) -> Instruction {
139 let acceptance_token_address = get_acceptance_token_address(feature_proposal_address);
140 let feature_id_address = get_feature_id_address(feature_proposal_address);
141
142 Instruction {
143 program_id: id(),
144 accounts: vec![
145 AccountMeta::new(*feature_proposal_address, false),
146 AccountMeta::new_readonly(acceptance_token_address, false),
147 AccountMeta::new(feature_id_address, false),
148 AccountMeta::new_readonly(solana_program::system_program::id(), false),
149 AccountMeta::new_readonly(sysvar::clock::id(), false),
150 ],
151 data: FeatureProposalInstruction::Tally.pack_into_vec(),
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158 use crate::borsh_utils;
159
160 #[test]
161 fn test_get_packed_len() {
162 assert_eq!(
163 FeatureProposalInstruction::get_packed_len(),
164 borsh_utils::get_packed_len::<FeatureProposalInstruction>()
165 )
166 }
167
168 #[test]
169 fn test_serialize_bytes() {
170 assert_eq!(
171 FeatureProposalInstruction::Tally.try_to_vec().unwrap(),
172 vec![1]
173 );
174
175 assert_eq!(
176 FeatureProposalInstruction::Propose {
177 tokens_to_mint: 42,
178 acceptance_criteria: AcceptanceCriteria {
179 tokens_required: 0xdeadbeefdeadbeef,
180 deadline: -1,
181 }
182 }
183 .try_to_vec()
184 .unwrap(),
185 vec![
186 0, 42, 0, 0, 0, 0, 0, 0, 0, 239, 190, 173, 222, 239, 190, 173, 222, 255, 255, 255,
187 255, 255, 255, 255, 255
188 ]
189 );
190 }
191
192 #[test]
193 fn test_serialize_large_slice() {
194 let mut dst = vec![0xff; 4];
195 FeatureProposalInstruction::Tally.pack_into_slice(&mut dst);
196
197 assert_eq!(dst, vec![1, 0xff, 0xff, 0xff]);
199 }
200
201 #[test]
202 fn state_deserialize_invalid() {
203 assert_eq!(
204 FeatureProposalInstruction::unpack_from_slice(&[1]),
205 Ok(FeatureProposalInstruction::Tally),
206 );
207
208 assert_eq!(
210 FeatureProposalInstruction::unpack_from_slice(&[1, 0xff, 0xff, 0xff]),
211 Ok(FeatureProposalInstruction::Tally),
212 );
213
214 assert_eq!(
215 FeatureProposalInstruction::unpack_from_slice(&[2]),
216 Err(ProgramError::InvalidInstructionData),
217 );
218 }
219}