spl_binary_oracle_pair/
processor.rs

1//! Program state processor
2
3use 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
24/// Program state handler.
25pub struct Processor {}
26impl Processor {
27    /// Calculates the authority id by generating a program address.
28    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    /// Transfer tokens with authority
38    #[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    /// Mint tokens
94    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    /// Burn tokens
128    #[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    /// Initialize the pool
184    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        // Pool account should not be already initialized
205        if pool.is_initialized() {
206            return Err(PoolError::AlreadyInUse.into());
207        }
208
209        // Check if pool account is rent-exempt
210        if !rent.is_exempt(pool_account_info.lamports(), pool_account_info.data_len()) {
211            return Err(PoolError::NotRentExempt.into());
212        }
213
214        // Check if deposit token's mint owner is token program
215        if deposit_token_mint_info.owner != token_program_info.key {
216            return Err(PoolError::InvalidTokenMint.into());
217        }
218
219        // Check if deposit token mint is initialized
220        let deposit_token_mint = Mint::unpack(&deposit_token_mint_info.data.borrow())?;
221
222        // Check if bump seed is correct
223        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    /// Process Deposit instruction
308    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        // Transfer deposit tokens from user's account to our deposit account
344        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        // Mint PASS tokens to user account
356        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        // Mint FAIL tokens to user account
366        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    /// Process Withdraw instruction
380    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                // Burn PASS tokens
423                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                // Transfer deposit tokens from pool deposit account to user destination account
435                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                // Burn FAIL tokens
448                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                // Transfer deposit tokens from pool deposit account to user destination account
460                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                    // Burn PASS tokens
479                    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                    // Burn FAIL tokens
491                    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                    // Transfer deposit tokens from pool deposit account to user destination account
503                    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    /// Process Decide instruction
523    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    /// Processes an instruction
564    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}