1use crate::{
4 error::PoolError,
5 instruction::PoolInstruction,
6 state::{Decision, Pool, POOL_VERSION},
7};
8use borsh::{BorshDeserialize, BorshSerialize};
9use solana_program::{
10 account_info::next_account_info,
11 account_info::AccountInfo,
12 clock::{Clock, Slot},
13 entrypoint::ProgramResult,
14 msg,
15 program::{invoke, invoke_signed},
16 program_error::ProgramError,
17 program_pack::{IsInitialized, Pack},
18 pubkey::Pubkey,
19 rent::Rent,
20 sysvar::Sysvar,
21};
22use spl_token::state::{Account, Mint};
23
24pub struct Processor {}
26impl Processor {
27 pub fn authority_id(
29 program_id: &Pubkey,
30 my_info: &Pubkey,
31 bump_seed: u8,
32 ) -> Result<Pubkey, ProgramError> {
33 Pubkey::create_program_address(&[&my_info.to_bytes()[..32], &[bump_seed]], program_id)
34 .map_err(|_| PoolError::InvalidAuthorityData.into())
35 }
36
37 #[allow(clippy::too_many_arguments)]
39 pub fn transfer<'a>(
40 token_program_id: AccountInfo<'a>,
41 source_account: AccountInfo<'a>,
42 destination_account: AccountInfo<'a>,
43 program_authority_account: AccountInfo<'a>,
44 user_authority_account: AccountInfo<'a>,
45 amount: u64,
46 pool_pub_key: &Pubkey,
47 bump_seed: u8,
48 ) -> ProgramResult {
49 if program_authority_account.key == user_authority_account.key {
50 let me_bytes = pool_pub_key.to_bytes();
51 let authority_signature_seeds = [&me_bytes[..32], &[bump_seed]];
52 let signers = &[&authority_signature_seeds[..]];
53
54 invoke_signed(
55 &spl_token::instruction::transfer(
56 token_program_id.key,
57 source_account.key,
58 destination_account.key,
59 program_authority_account.key,
60 &[program_authority_account.key],
61 amount,
62 )
63 .unwrap(),
64 &[
65 token_program_id,
66 program_authority_account,
67 source_account,
68 destination_account,
69 ],
70 signers,
71 )
72 } else {
73 invoke(
74 &spl_token::instruction::transfer(
75 token_program_id.key,
76 source_account.key,
77 destination_account.key,
78 user_authority_account.key,
79 &[user_authority_account.key],
80 amount,
81 )
82 .unwrap(),
83 &[
84 token_program_id,
85 user_authority_account,
86 source_account,
87 destination_account,
88 ],
89 )
90 }
91 }
92
93 pub fn mint<'a>(
95 token_program_id: AccountInfo<'a>,
96 mint_account: AccountInfo<'a>,
97 destination_account: AccountInfo<'a>,
98 authority_account: AccountInfo<'a>,
99 amount: u64,
100 pool_pub_key: &Pubkey,
101 bump_seed: u8,
102 ) -> ProgramResult {
103 let me_bytes = pool_pub_key.to_bytes();
104 let authority_signature_seeds = [&me_bytes[..32], &[bump_seed]];
105 let signers = &[&authority_signature_seeds[..]];
106
107 invoke_signed(
108 &spl_token::instruction::mint_to(
109 token_program_id.key,
110 mint_account.key,
111 destination_account.key,
112 authority_account.key,
113 &[authority_account.key],
114 amount,
115 )
116 .unwrap(),
117 &[
118 token_program_id,
119 mint_account,
120 destination_account,
121 authority_account,
122 ],
123 signers,
124 )
125 }
126
127 #[allow(clippy::too_many_arguments)]
129 pub fn burn<'a>(
130 token_program_id: AccountInfo<'a>,
131 source_account: AccountInfo<'a>,
132 mint_account: AccountInfo<'a>,
133 program_authority_account: AccountInfo<'a>,
134 user_authority_account: AccountInfo<'a>,
135 amount: u64,
136 pool_pub_key: &Pubkey,
137 bump_seed: u8,
138 ) -> ProgramResult {
139 if program_authority_account.key == user_authority_account.key {
140 let me_bytes = pool_pub_key.to_bytes();
141 let authority_signature_seeds = [&me_bytes[..32], &[bump_seed]];
142 let signers = &[&authority_signature_seeds[..]];
143
144 invoke_signed(
145 &spl_token::instruction::burn(
146 token_program_id.key,
147 source_account.key,
148 mint_account.key,
149 program_authority_account.key,
150 &[program_authority_account.key],
151 amount,
152 )
153 .unwrap(),
154 &[
155 token_program_id,
156 program_authority_account,
157 source_account,
158 mint_account,
159 ],
160 signers,
161 )
162 } else {
163 invoke(
164 &spl_token::instruction::burn(
165 token_program_id.key,
166 source_account.key,
167 mint_account.key,
168 user_authority_account.key,
169 &[user_authority_account.key],
170 amount,
171 )
172 .unwrap(),
173 &[
174 token_program_id,
175 user_authority_account,
176 source_account,
177 mint_account,
178 ],
179 )
180 }
181 }
182
183 pub fn process_init_pool(
185 program_id: &Pubkey,
186 accounts: &[AccountInfo],
187 mint_end_slot: Slot,
188 decide_end_slot: Slot,
189 bump_seed: u8,
190 ) -> ProgramResult {
191 let account_info_iter = &mut accounts.iter();
192 let pool_account_info = next_account_info(account_info_iter)?;
193 let authority_info = next_account_info(account_info_iter)?;
194 let decider_info = next_account_info(account_info_iter)?;
195 let deposit_token_mint_info = next_account_info(account_info_iter)?;
196 let deposit_account_info = next_account_info(account_info_iter)?;
197 let token_pass_mint_info = next_account_info(account_info_iter)?;
198 let token_fail_mint_info = next_account_info(account_info_iter)?;
199 let rent_info = next_account_info(account_info_iter)?;
200 let rent = &Rent::from_account_info(rent_info)?;
201 let token_program_info = next_account_info(account_info_iter)?;
202
203 let mut pool = Pool::try_from_slice(&pool_account_info.data.borrow())?;
204 if pool.is_initialized() {
206 return Err(PoolError::AlreadyInUse.into());
207 }
208
209 if !rent.is_exempt(pool_account_info.lamports(), pool_account_info.data_len()) {
211 return Err(PoolError::NotRentExempt.into());
212 }
213
214 if deposit_token_mint_info.owner != token_program_info.key {
216 return Err(PoolError::InvalidTokenMint.into());
217 }
218
219 let deposit_token_mint = Mint::unpack(&deposit_token_mint_info.data.borrow())?;
221
222 let authority = Self::authority_id(program_id, pool_account_info.key, bump_seed)?;
224 if &authority != authority_info.key {
225 return Err(PoolError::InvalidAuthorityAccount.into());
226 }
227
228 let deposit_account = Account::unpack_unchecked(&deposit_account_info.data.borrow())?;
229 if deposit_account.is_initialized() {
230 return Err(PoolError::DepositAccountInUse.into());
231 }
232
233 let token_pass = Mint::unpack_unchecked(&token_pass_mint_info.data.borrow())?;
234 if token_pass.is_initialized() {
235 return Err(PoolError::TokenMintInUse.into());
236 }
237
238 let token_fail = Mint::unpack_unchecked(&token_fail_mint_info.data.borrow())?;
239 if token_fail.is_initialized() {
240 return Err(PoolError::TokenMintInUse.into());
241 }
242
243 invoke(
244 &spl_token::instruction::initialize_account(
245 token_program_info.key,
246 deposit_account_info.key,
247 deposit_token_mint_info.key,
248 authority_info.key,
249 )
250 .unwrap(),
251 &[
252 token_program_info.clone(),
253 deposit_account_info.clone(),
254 deposit_token_mint_info.clone(),
255 authority_info.clone(),
256 rent_info.clone(),
257 ],
258 )?;
259
260 invoke(
261 &spl_token::instruction::initialize_mint(
262 &spl_token::id(),
263 token_pass_mint_info.key,
264 authority_info.key,
265 None,
266 deposit_token_mint.decimals,
267 )
268 .unwrap(),
269 &[
270 token_program_info.clone(),
271 token_pass_mint_info.clone(),
272 rent_info.clone(),
273 ],
274 )?;
275
276 invoke(
277 &spl_token::instruction::initialize_mint(
278 &spl_token::id(),
279 token_fail_mint_info.key,
280 authority_info.key,
281 None,
282 deposit_token_mint.decimals,
283 )
284 .unwrap(),
285 &[
286 token_program_info.clone(),
287 token_fail_mint_info.clone(),
288 rent_info.clone(),
289 ],
290 )?;
291
292 pool.version = POOL_VERSION;
293 pool.bump_seed = bump_seed;
294 pool.token_program_id = *token_program_info.key;
295 pool.deposit_account = *deposit_account_info.key;
296 pool.token_pass_mint = *token_pass_mint_info.key;
297 pool.token_fail_mint = *token_fail_mint_info.key;
298 pool.decider = *decider_info.key;
299 pool.mint_end_slot = mint_end_slot;
300 pool.decide_end_slot = decide_end_slot;
301 pool.decision = Decision::Undecided;
302
303 pool.serialize(&mut *pool_account_info.data.borrow_mut())
304 .map_err(|e| e.into())
305 }
306
307 pub fn process_deposit(
309 program_id: &Pubkey,
310 accounts: &[AccountInfo],
311 amount: u64,
312 ) -> ProgramResult {
313 let account_info_iter = &mut accounts.iter();
314 let pool_account_info = next_account_info(account_info_iter)?;
315 let authority_account_info = next_account_info(account_info_iter)?;
316 let user_transfer_authority_info = next_account_info(account_info_iter)?;
317 let user_token_account_info = next_account_info(account_info_iter)?;
318 let pool_deposit_token_account_info = next_account_info(account_info_iter)?;
319 let token_pass_mint_info = next_account_info(account_info_iter)?;
320 let token_fail_mint_info = next_account_info(account_info_iter)?;
321 let token_pass_destination_account_info = next_account_info(account_info_iter)?;
322 let token_fail_destination_account_info = next_account_info(account_info_iter)?;
323 let clock_info = next_account_info(account_info_iter)?;
324 let clock = &Clock::from_account_info(clock_info)?;
325 let token_program_id_info = next_account_info(account_info_iter)?;
326
327 if amount == 0 {
328 return Err(PoolError::InvalidAmount.into());
329 }
330
331 let pool = Pool::try_from_slice(&pool_account_info.data.borrow())?;
332
333 if clock.slot > pool.mint_end_slot {
334 return Err(PoolError::InvalidSlotForDeposit.into());
335 }
336
337 let authority_pub_key =
338 Self::authority_id(program_id, pool_account_info.key, pool.bump_seed)?;
339 if *authority_account_info.key != authority_pub_key {
340 return Err(PoolError::InvalidAuthorityAccount.into());
341 }
342
343 Self::transfer(
345 token_program_id_info.clone(),
346 user_token_account_info.clone(),
347 pool_deposit_token_account_info.clone(),
348 authority_account_info.clone(),
349 user_transfer_authority_info.clone(),
350 amount,
351 pool_account_info.key,
352 pool.bump_seed,
353 )?;
354
355 Self::mint(
357 token_program_id_info.clone(),
358 token_pass_mint_info.clone(),
359 token_pass_destination_account_info.clone(),
360 authority_account_info.clone(),
361 amount,
362 pool_account_info.key,
363 pool.bump_seed,
364 )?;
365 Self::mint(
367 token_program_id_info.clone(),
368 token_fail_mint_info.clone(),
369 token_fail_destination_account_info.clone(),
370 authority_account_info.clone(),
371 amount,
372 pool_account_info.key,
373 pool.bump_seed,
374 )?;
375
376 Ok(())
377 }
378
379 pub fn process_withdraw(
381 program_id: &Pubkey,
382 accounts: &[AccountInfo],
383 amount: u64,
384 ) -> ProgramResult {
385 let account_info_iter = &mut accounts.iter();
386 let pool_account_info = next_account_info(account_info_iter)?;
387 let authority_account_info = next_account_info(account_info_iter)?;
388 let user_transfer_authority_info = next_account_info(account_info_iter)?;
389 let pool_deposit_token_account_info = next_account_info(account_info_iter)?;
390 let token_pass_user_account_info = next_account_info(account_info_iter)?;
391 let token_fail_user_account_info = next_account_info(account_info_iter)?;
392 let token_pass_mint_info = next_account_info(account_info_iter)?;
393 let token_fail_mint_info = next_account_info(account_info_iter)?;
394 let user_token_destination_account_info = next_account_info(account_info_iter)?;
395 let clock_info = next_account_info(account_info_iter)?;
396 let clock = &Clock::from_account_info(clock_info)?;
397 let token_program_id_info = next_account_info(account_info_iter)?;
398
399 if amount == 0 {
400 return Err(PoolError::InvalidAmount.into());
401 }
402
403 let user_pass_token_account = Account::unpack(&token_pass_user_account_info.data.borrow())?;
404 let user_fail_token_account = Account::unpack(&token_fail_user_account_info.data.borrow())?;
405
406 let pool = Pool::try_from_slice(&pool_account_info.data.borrow())?;
407
408 if pool.token_pass_mint != *token_pass_mint_info.key {
409 return Err(PoolError::InvalidTokenMint.into());
410 }
411 if pool.token_fail_mint != *token_fail_mint_info.key {
412 return Err(PoolError::InvalidTokenMint.into());
413 }
414 let authority_pub_key =
415 Self::authority_id(program_id, pool_account_info.key, pool.bump_seed)?;
416 if *authority_account_info.key != authority_pub_key {
417 return Err(PoolError::InvalidAuthorityAccount.into());
418 }
419
420 match pool.decision {
421 Decision::Pass => {
422 Self::burn(
424 token_program_id_info.clone(),
425 token_pass_user_account_info.clone(),
426 token_pass_mint_info.clone(),
427 authority_account_info.clone(),
428 user_transfer_authority_info.clone(),
429 amount,
430 pool_account_info.key,
431 pool.bump_seed,
432 )?;
433
434 Self::transfer(
436 token_program_id_info.clone(),
437 pool_deposit_token_account_info.clone(),
438 user_token_destination_account_info.clone(),
439 authority_account_info.clone(),
440 authority_account_info.clone(),
441 amount,
442 pool_account_info.key,
443 pool.bump_seed,
444 )?;
445 }
446 Decision::Fail => {
447 Self::burn(
449 token_program_id_info.clone(),
450 token_fail_user_account_info.clone(),
451 token_fail_mint_info.clone(),
452 authority_account_info.clone(),
453 user_transfer_authority_info.clone(),
454 amount,
455 pool_account_info.key,
456 pool.bump_seed,
457 )?;
458
459 Self::transfer(
461 token_program_id_info.clone(),
462 pool_deposit_token_account_info.clone(),
463 user_token_destination_account_info.clone(),
464 authority_account_info.clone(),
465 authority_account_info.clone(),
466 amount,
467 pool_account_info.key,
468 pool.bump_seed,
469 )?;
470 }
471 Decision::Undecided => {
472 let current_slot = clock.slot;
473 if current_slot < pool.mint_end_slot || current_slot > pool.decide_end_slot {
474 let possible_withdraw_amount = amount
475 .min(user_pass_token_account.amount)
476 .min(user_fail_token_account.amount);
477
478 Self::burn(
480 token_program_id_info.clone(),
481 token_pass_user_account_info.clone(),
482 token_pass_mint_info.clone(),
483 authority_account_info.clone(),
484 user_transfer_authority_info.clone(),
485 possible_withdraw_amount,
486 pool_account_info.key,
487 pool.bump_seed,
488 )?;
489
490 Self::burn(
492 token_program_id_info.clone(),
493 token_fail_user_account_info.clone(),
494 token_fail_mint_info.clone(),
495 authority_account_info.clone(),
496 user_transfer_authority_info.clone(),
497 amount,
498 pool_account_info.key,
499 pool.bump_seed,
500 )?;
501
502 Self::transfer(
504 token_program_id_info.clone(),
505 pool_deposit_token_account_info.clone(),
506 user_token_destination_account_info.clone(),
507 authority_account_info.clone(),
508 authority_account_info.clone(),
509 amount,
510 pool_account_info.key,
511 pool.bump_seed,
512 )?;
513 } else {
514 return Err(PoolError::NoDecisionMadeYet.into());
515 }
516 }
517 }
518
519 Ok(())
520 }
521
522 pub fn process_decide(
524 _program_id: &Pubkey,
525 accounts: &[AccountInfo],
526 decision: bool,
527 ) -> ProgramResult {
528 let account_info_iter = &mut accounts.iter();
529 let pool_account_info = next_account_info(account_info_iter)?;
530 let decider_account_info = next_account_info(account_info_iter)?;
531 let clock_info = next_account_info(account_info_iter)?;
532 let clock = &Clock::from_account_info(clock_info)?;
533
534 let mut pool = Pool::try_from_slice(&pool_account_info.data.borrow())?;
535
536 if *decider_account_info.key != pool.decider {
537 return Err(PoolError::WrongDeciderAccount.into());
538 }
539
540 if !decider_account_info.is_signer {
541 return Err(PoolError::SignatureMissing.into());
542 }
543
544 if pool.decision != Decision::Undecided {
545 return Err(PoolError::DecisionAlreadyMade.into());
546 }
547
548 let current_slot = clock.slot;
549 if current_slot < pool.mint_end_slot || current_slot > pool.decide_end_slot {
550 return Err(PoolError::InvalidSlotForDecision.into());
551 }
552
553 pool.decision = if decision {
554 Decision::Pass
555 } else {
556 Decision::Fail
557 };
558
559 pool.serialize(&mut *pool_account_info.data.borrow_mut())
560 .map_err(|e| e.into())
561 }
562
563 pub fn process_instruction(
565 program_id: &Pubkey,
566 accounts: &[AccountInfo],
567 input: &[u8],
568 ) -> ProgramResult {
569 let instruction = PoolInstruction::try_from_slice(input)?;
570 match instruction {
571 PoolInstruction::InitPool(init_args) => {
572 msg!("Instruction: InitPool");
573 Self::process_init_pool(
574 program_id,
575 accounts,
576 init_args.mint_end_slot,
577 init_args.decide_end_slot,
578 init_args.bump_seed,
579 )
580 }
581 PoolInstruction::Deposit(amount) => {
582 msg!("Instruction: Deposit");
583 Self::process_deposit(program_id, accounts, amount)
584 }
585 PoolInstruction::Withdraw(amount) => {
586 msg!("Instruction: Withdraw");
587 Self::process_withdraw(program_id, accounts, amount)
588 }
589 PoolInstruction::Decide(decision) => {
590 msg!("Instruction: Decide");
591 Self::process_decide(program_id, accounts, decision)
592 }
593 }
594 }
595}