1use crate::{instruction::*, state::*, *};
4use solana_program::{
5 account_info::{next_account_info, AccountInfo},
6 clock::Clock,
7 entrypoint::ProgramResult,
8 feature::{self, Feature},
9 info,
10 program::{invoke, invoke_signed},
11 program_error::ProgramError,
12 pubkey::Pubkey,
13 rent::Rent,
14 system_instruction,
15 sysvar::Sysvar,
16};
17
18pub fn process_instruction(
20 program_id: &Pubkey,
21 accounts: &[AccountInfo],
22 input: &[u8],
23) -> ProgramResult {
24 let instruction = FeatureProposalInstruction::unpack_from_slice(input)?;
25 let account_info_iter = &mut accounts.iter();
26
27 match instruction {
28 FeatureProposalInstruction::Propose {
29 tokens_to_mint,
30 acceptance_criteria,
31 } => {
32 info!("FeatureProposalInstruction::Propose");
33
34 let funder_info = next_account_info(account_info_iter)?;
35 let feature_proposal_info = next_account_info(account_info_iter)?;
36 let mint_info = next_account_info(account_info_iter)?;
37 let distributor_token_info = next_account_info(account_info_iter)?;
38 let acceptance_token_info = next_account_info(account_info_iter)?;
39 let feature_id_info = next_account_info(account_info_iter)?;
40 let system_program_info = next_account_info(account_info_iter)?;
41 let spl_token_program_info = next_account_info(account_info_iter)?;
42 let rent_sysvar_info = next_account_info(account_info_iter)?;
43 let rent = &Rent::from_account_info(rent_sysvar_info)?;
44
45 let (mint_address, mint_bump_seed) =
46 get_mint_address_with_seed(feature_proposal_info.key);
47 if mint_address != *mint_info.key {
48 info!("Error: mint address derivation mismatch");
49 return Err(ProgramError::InvalidArgument);
50 }
51
52 let (distributor_token_address, distributor_token_bump_seed) =
53 get_distributor_token_address_with_seed(feature_proposal_info.key);
54 if distributor_token_address != *distributor_token_info.key {
55 info!("Error: distributor token address derivation mismatch");
56 return Err(ProgramError::InvalidArgument);
57 }
58
59 let (acceptance_token_address, acceptance_token_bump_seed) =
60 get_acceptance_token_address_with_seed(feature_proposal_info.key);
61 if acceptance_token_address != *acceptance_token_info.key {
62 info!("Error: acceptance token address derivation mismatch");
63 return Err(ProgramError::InvalidArgument);
64 }
65
66 let (feature_id_address, feature_id_bump_seed) =
67 get_feature_id_address_with_seed(feature_proposal_info.key);
68 if feature_id_address != *feature_id_info.key {
69 info!("Error: feature-id address derivation mismatch");
70 return Err(ProgramError::InvalidArgument);
71 }
72
73 let mint_signer_seeds: &[&[_]] = &[
74 &feature_proposal_info.key.to_bytes(),
75 br"mint",
76 &[mint_bump_seed],
77 ];
78
79 let distributor_token_signer_seeds: &[&[_]] = &[
80 &feature_proposal_info.key.to_bytes(),
81 br"distributor",
82 &[distributor_token_bump_seed],
83 ];
84
85 let acceptance_token_signer_seeds: &[&[_]] = &[
86 &feature_proposal_info.key.to_bytes(),
87 br"acceptance",
88 &[acceptance_token_bump_seed],
89 ];
90
91 let feature_id_signer_seeds: &[&[_]] = &[
92 &feature_proposal_info.key.to_bytes(),
93 br"feature-id",
94 &[feature_id_bump_seed],
95 ];
96
97 info!("Creating feature proposal account");
98 invoke(
99 &system_instruction::create_account(
100 funder_info.key,
101 feature_proposal_info.key,
102 1.max(rent.minimum_balance(FeatureProposal::get_packed_len())),
103 FeatureProposal::get_packed_len() as u64,
104 program_id,
105 ),
106 &[
107 funder_info.clone(),
108 feature_proposal_info.clone(),
109 system_program_info.clone(),
110 ],
111 )?;
112 FeatureProposal::Pending(acceptance_criteria)
113 .pack_into_slice(&mut feature_proposal_info.data.borrow_mut());
114
115 info!("Creating mint");
116 invoke_signed(
117 &system_instruction::create_account(
118 funder_info.key,
119 mint_info.key,
120 1.max(rent.minimum_balance(spl_token::state::Mint::get_packed_len())),
121 spl_token::state::Mint::get_packed_len() as u64,
122 &spl_token::id(),
123 ),
124 &[
125 funder_info.clone(),
126 mint_info.clone(),
127 system_program_info.clone(),
128 ],
129 &[&mint_signer_seeds],
130 )?;
131
132 info!("Initializing mint");
133 invoke(
134 &spl_token::instruction::initialize_mint(
135 &spl_token::id(),
136 mint_info.key,
137 mint_info.key,
138 None,
139 spl_token::native_mint::DECIMALS,
140 )?,
141 &[
142 mint_info.clone(),
143 spl_token_program_info.clone(),
144 rent_sysvar_info.clone(),
145 ],
146 )?;
147
148 info!("Creating distributor token account");
149 invoke_signed(
150 &system_instruction::create_account(
151 funder_info.key,
152 distributor_token_info.key,
153 1.max(rent.minimum_balance(spl_token::state::Account::get_packed_len())),
154 spl_token::state::Account::get_packed_len() as u64,
155 &spl_token::id(),
156 ),
157 &[
158 funder_info.clone(),
159 distributor_token_info.clone(),
160 system_program_info.clone(),
161 ],
162 &[&distributor_token_signer_seeds],
163 )?;
164
165 info!("Initializing distributor token account");
166 invoke(
167 &spl_token::instruction::initialize_account(
168 &spl_token::id(),
169 distributor_token_info.key,
170 mint_info.key,
171 feature_proposal_info.key,
172 )?,
173 &[
174 distributor_token_info.clone(),
175 spl_token_program_info.clone(),
176 rent_sysvar_info.clone(),
177 feature_proposal_info.clone(),
178 mint_info.clone(),
179 ],
180 )?;
181
182 info!("Creating acceptance token account");
183 invoke_signed(
184 &system_instruction::create_account(
185 funder_info.key,
186 acceptance_token_info.key,
187 1.max(rent.minimum_balance(spl_token::state::Account::get_packed_len())),
188 spl_token::state::Account::get_packed_len() as u64,
189 &spl_token::id(),
190 ),
191 &[
192 funder_info.clone(),
193 acceptance_token_info.clone(),
194 system_program_info.clone(),
195 ],
196 &[&acceptance_token_signer_seeds],
197 )?;
198
199 info!("Initializing acceptance token account");
200 invoke(
201 &spl_token::instruction::initialize_account(
202 &spl_token::id(),
203 acceptance_token_info.key,
204 mint_info.key,
205 feature_proposal_info.key,
206 )?,
207 &[
208 acceptance_token_info.clone(),
209 spl_token_program_info.clone(),
210 rent_sysvar_info.clone(),
211 feature_proposal_info.clone(),
212 mint_info.clone(),
213 ],
214 )?;
215 invoke(
216 &spl_token::instruction::set_authority(
217 &spl_token::id(),
218 acceptance_token_info.key,
219 Some(&feature_proposal_info.key),
220 spl_token::instruction::AuthorityType::CloseAccount,
221 feature_proposal_info.key,
222 &[],
223 )?,
224 &[
225 spl_token_program_info.clone(),
226 acceptance_token_info.clone(),
227 feature_proposal_info.clone(),
228 ],
229 )?;
230 invoke(
231 &spl_token::instruction::set_authority(
232 &spl_token::id(),
233 acceptance_token_info.key,
234 Some(&program_id),
235 spl_token::instruction::AuthorityType::AccountOwner,
236 feature_proposal_info.key,
237 &[],
238 )?,
239 &[
240 spl_token_program_info.clone(),
241 acceptance_token_info.clone(),
242 feature_proposal_info.clone(),
243 ],
244 )?;
245
246 info!(&format!("Minting {} tokens", tokens_to_mint));
249 invoke_signed(
250 &spl_token::instruction::mint_to(
251 &spl_token::id(),
252 mint_info.key,
253 distributor_token_info.key,
254 mint_info.key,
255 &[],
256 tokens_to_mint,
257 )?,
258 &[
259 mint_info.clone(),
260 distributor_token_info.clone(),
261 spl_token_program_info.clone(),
262 ],
263 &[&mint_signer_seeds],
264 )?;
265
266 info!("Funding feature id account");
269 invoke(
270 &system_instruction::transfer(
271 funder_info.key,
272 feature_id_info.key,
273 1.max(rent.minimum_balance(Feature::size_of())),
274 ),
275 &[
276 funder_info.clone(),
277 feature_id_info.clone(),
278 system_program_info.clone(),
279 ],
280 )?;
281
282 info!("Allocating feature id account");
283 invoke_signed(
284 &system_instruction::allocate(feature_id_info.key, Feature::size_of() as u64),
285 &[feature_id_info.clone(), system_program_info.clone()],
286 &[&feature_id_signer_seeds],
287 )?;
288 }
289
290 FeatureProposalInstruction::Tally => {
291 info!("FeatureProposalInstruction::Tally");
292
293 let feature_proposal_info = next_account_info(account_info_iter)?;
294 let feature_proposal_state =
295 FeatureProposal::unpack_from_slice(&feature_proposal_info.data.borrow())?;
296
297 match feature_proposal_state {
298 FeatureProposal::Pending(acceptance_criteria) => {
299 let acceptance_token_info = next_account_info(account_info_iter)?;
300 let feature_id_info = next_account_info(account_info_iter)?;
301 let system_program_info = next_account_info(account_info_iter)?;
302 let clock_sysvar_info = next_account_info(account_info_iter)?;
303 let clock = &Clock::from_account_info(clock_sysvar_info)?;
304
305 let acceptance_token_address =
308 get_acceptance_token_address(feature_proposal_info.key);
309 if acceptance_token_address != *acceptance_token_info.key {
310 info!("Error: acceptance token address derivation mismatch");
311 return Err(ProgramError::InvalidArgument);
312 }
313
314 let (feature_id_address, feature_id_bump_seed) =
315 get_feature_id_address_with_seed(feature_proposal_info.key);
316 if feature_id_address != *feature_id_info.key {
317 info!("Error: feature-id address derivation mismatch");
318 return Err(ProgramError::InvalidArgument);
319 }
320
321 let feature_id_signer_seeds: &[&[_]] = &[
322 &feature_proposal_info.key.to_bytes(),
323 br"feature-id",
324 &[feature_id_bump_seed],
325 ];
326
327 if clock.unix_timestamp >= acceptance_criteria.deadline {
328 info!("Feature proposal expired");
329 FeatureProposal::Expired
330 .pack_into_slice(&mut feature_proposal_info.data.borrow_mut());
331 return Ok(());
332 }
333
334 info!("Unpacking acceptance token account");
335 let acceptance_token =
336 spl_token::state::Account::unpack(&acceptance_token_info.data.borrow())?;
337
338 info!(&format!(
339 "Feature proposal has received {} tokens, and {} tokens required for acceptance",
340 acceptance_token.amount, acceptance_criteria.tokens_required
341 ));
342 if acceptance_token.amount < acceptance_criteria.tokens_required {
343 info!("Activation threshold has not been reached");
344 return Ok(());
345 }
346
347 info!("Assigning feature id account");
348 invoke_signed(
349 &system_instruction::assign(feature_id_info.key, &feature::id()),
350 &[feature_id_info.clone(), system_program_info.clone()],
351 &[&feature_id_signer_seeds],
352 )?;
353
354 info!("Feature proposal accepted");
355 FeatureProposal::Accepted {
356 tokens_upon_acceptance: acceptance_token.amount,
357 }
358 .pack_into_slice(&mut feature_proposal_info.data.borrow_mut());
359 }
360 _ => {
361 info!("Error: feature proposal account not in the pending state");
362 return Err(ProgramError::InvalidAccountData);
363 }
364 }
365 }
366 }
367
368 Ok(())
369}