spl_single_pool/
processor.rs

1//! program state processor
2
3use {
4    crate::{
5        error::SinglePoolError,
6        inline_mpl_token_metadata::{
7            self,
8            instruction::{create_metadata_accounts_v3, update_metadata_accounts_v2},
9            pda::find_metadata_account,
10            state::DataV2,
11        },
12        instruction::SinglePoolInstruction,
13        state::{SinglePool, SinglePoolAccountType},
14        MINT_DECIMALS, POOL_MINT_AUTHORITY_PREFIX, POOL_MINT_PREFIX, POOL_MPL_AUTHORITY_PREFIX,
15        POOL_PREFIX, POOL_STAKE_AUTHORITY_PREFIX, POOL_STAKE_PREFIX,
16        VOTE_STATE_AUTHORIZED_WITHDRAWER_END, VOTE_STATE_AUTHORIZED_WITHDRAWER_START,
17        VOTE_STATE_DISCRIMINATOR_END,
18    },
19    borsh::BorshDeserialize,
20    solana_program::{
21        account_info::{next_account_info, AccountInfo},
22        borsh1::{get_packed_len, try_from_slice_unchecked},
23        entrypoint::ProgramResult,
24        msg,
25        native_token::LAMPORTS_PER_SOL,
26        program::invoke_signed,
27        program_error::ProgramError,
28        program_pack::Pack,
29        pubkey::Pubkey,
30        rent::Rent,
31        stake::{
32            self,
33            state::{Meta, Stake, StakeStateV2},
34        },
35        stake_history::Epoch,
36        system_instruction, system_program,
37        sysvar::{clock::Clock, Sysvar},
38        vote::program as vote_program,
39    },
40    spl_token::state::Mint,
41};
42
43/// Calculate pool tokens to mint, given outstanding token supply, pool active
44/// stake, and deposit active stake
45fn calculate_deposit_amount(
46    pre_token_supply: u64,
47    pre_pool_stake: u64,
48    user_stake_to_deposit: u64,
49) -> Option<u64> {
50    if pre_pool_stake == 0 || pre_token_supply == 0 {
51        Some(user_stake_to_deposit)
52    } else {
53        u64::try_from(
54            (user_stake_to_deposit as u128)
55                .checked_mul(pre_token_supply as u128)?
56                .checked_div(pre_pool_stake as u128)?,
57        )
58        .ok()
59    }
60}
61
62/// Calculate pool stake to return, given outstanding token supply, pool active
63/// stake, and tokens to redeem
64fn calculate_withdraw_amount(
65    pre_token_supply: u64,
66    pre_pool_stake: u64,
67    user_tokens_to_burn: u64,
68) -> Option<u64> {
69    let numerator = (user_tokens_to_burn as u128).checked_mul(pre_pool_stake as u128)?;
70    let denominator = pre_token_supply as u128;
71    if numerator < denominator || denominator == 0 {
72        Some(0)
73    } else {
74        u64::try_from(numerator.checked_div(denominator)?).ok()
75    }
76}
77
78/// Deserialize the stake state from `AccountInfo`
79fn get_stake_state(stake_account_info: &AccountInfo) -> Result<(Meta, Stake), ProgramError> {
80    let stake_state = try_from_slice_unchecked::<StakeStateV2>(&stake_account_info.data.borrow())?;
81
82    match stake_state {
83        StakeStateV2::Stake(meta, stake, _) => Ok((meta, stake)),
84        _ => Err(SinglePoolError::WrongStakeStake.into()),
85    }
86}
87
88/// Deserialize the stake amount from `AccountInfo`
89fn get_stake_amount(stake_account_info: &AccountInfo) -> Result<u64, ProgramError> {
90    Ok(get_stake_state(stake_account_info)?.1.delegation.stake)
91}
92
93/// Determine if stake is active
94fn is_stake_active_without_history(stake: &Stake, current_epoch: Epoch) -> bool {
95    stake.delegation.activation_epoch < current_epoch
96        && stake.delegation.deactivation_epoch == Epoch::MAX
97}
98
99/// Check pool account address for the validator vote account
100fn check_pool_address(
101    program_id: &Pubkey,
102    vote_account_address: &Pubkey,
103    check_address: &Pubkey,
104) -> Result<u8, ProgramError> {
105    check_pool_pda(
106        program_id,
107        vote_account_address,
108        check_address,
109        &crate::find_pool_address_and_bump,
110        "pool",
111        SinglePoolError::InvalidPoolAccount,
112    )
113}
114
115/// Check pool stake account address for the pool account
116fn check_pool_stake_address(
117    program_id: &Pubkey,
118    pool_address: &Pubkey,
119    check_address: &Pubkey,
120) -> Result<u8, ProgramError> {
121    check_pool_pda(
122        program_id,
123        pool_address,
124        check_address,
125        &crate::find_pool_stake_address_and_bump,
126        "stake account",
127        SinglePoolError::InvalidPoolStakeAccount,
128    )
129}
130
131/// Check pool mint address for the pool account
132fn check_pool_mint_address(
133    program_id: &Pubkey,
134    pool_address: &Pubkey,
135    check_address: &Pubkey,
136) -> Result<u8, ProgramError> {
137    check_pool_pda(
138        program_id,
139        pool_address,
140        check_address,
141        &crate::find_pool_mint_address_and_bump,
142        "mint",
143        SinglePoolError::InvalidPoolMint,
144    )
145}
146
147/// Check pool stake authority address for the pool account
148fn check_pool_stake_authority_address(
149    program_id: &Pubkey,
150    pool_address: &Pubkey,
151    check_address: &Pubkey,
152) -> Result<u8, ProgramError> {
153    check_pool_pda(
154        program_id,
155        pool_address,
156        check_address,
157        &crate::find_pool_stake_authority_address_and_bump,
158        "stake authority",
159        SinglePoolError::InvalidPoolStakeAuthority,
160    )
161}
162
163/// Check pool mint authority address for the pool account
164fn check_pool_mint_authority_address(
165    program_id: &Pubkey,
166    pool_address: &Pubkey,
167    check_address: &Pubkey,
168) -> Result<u8, ProgramError> {
169    check_pool_pda(
170        program_id,
171        pool_address,
172        check_address,
173        &crate::find_pool_mint_authority_address_and_bump,
174        "mint authority",
175        SinglePoolError::InvalidPoolMintAuthority,
176    )
177}
178
179/// Check pool MPL authority address for the pool account
180fn check_pool_mpl_authority_address(
181    program_id: &Pubkey,
182    pool_address: &Pubkey,
183    check_address: &Pubkey,
184) -> Result<u8, ProgramError> {
185    check_pool_pda(
186        program_id,
187        pool_address,
188        check_address,
189        &crate::find_pool_mpl_authority_address_and_bump,
190        "MPL authority",
191        SinglePoolError::InvalidPoolMplAuthority,
192    )
193}
194
195fn check_pool_pda(
196    program_id: &Pubkey,
197    base_address: &Pubkey,
198    check_address: &Pubkey,
199    pda_lookup_fn: &dyn Fn(&Pubkey, &Pubkey) -> (Pubkey, u8),
200    pda_name: &str,
201    pool_error: SinglePoolError,
202) -> Result<u8, ProgramError> {
203    let (derived_address, bump_seed) = pda_lookup_fn(program_id, base_address);
204    if *check_address != derived_address {
205        msg!(
206            "Incorrect {} address for base {}: expected {}, received {}",
207            pda_name,
208            base_address,
209            derived_address,
210            check_address,
211        );
212        Err(pool_error.into())
213    } else {
214        Ok(bump_seed)
215    }
216}
217
218/// Check vote account is owned by the vote program and not a legacy variant
219fn check_vote_account(vote_account_info: &AccountInfo) -> Result<(), ProgramError> {
220    check_account_owner(vote_account_info, &vote_program::id())?;
221
222    let vote_account_data = &vote_account_info.try_borrow_data()?;
223    let state_variant = vote_account_data
224        .get(..VOTE_STATE_DISCRIMINATOR_END)
225        .and_then(|s| s.try_into().ok())
226        .ok_or(SinglePoolError::UnparseableVoteAccount)?;
227
228    match u32::from_le_bytes(state_variant) {
229        1 | 2 => Ok(()),
230        0 => Err(SinglePoolError::LegacyVoteAccount.into()),
231        _ => Err(SinglePoolError::UnparseableVoteAccount.into()),
232    }
233}
234
235/// Check MPL metadata account address for the pool mint
236fn check_mpl_metadata_account_address(
237    metadata_address: &Pubkey,
238    pool_mint: &Pubkey,
239) -> Result<(), ProgramError> {
240    let (metadata_account_pubkey, _) = find_metadata_account(pool_mint);
241    if metadata_account_pubkey != *metadata_address {
242        Err(SinglePoolError::InvalidMetadataAccount.into())
243    } else {
244        Ok(())
245    }
246}
247
248/// Check system program address
249fn check_system_program(program_id: &Pubkey) -> Result<(), ProgramError> {
250    if *program_id != system_program::id() {
251        msg!(
252            "Expected system program {}, received {}",
253            system_program::id(),
254            program_id
255        );
256        Err(ProgramError::IncorrectProgramId)
257    } else {
258        Ok(())
259    }
260}
261
262/// Check token program address
263fn check_token_program(address: &Pubkey) -> Result<(), ProgramError> {
264    if *address != spl_token::id() {
265        msg!(
266            "Incorrect token program, expected {}, received {}",
267            spl_token::id(),
268            address
269        );
270        Err(ProgramError::IncorrectProgramId)
271    } else {
272        Ok(())
273    }
274}
275
276/// Check stake program address
277fn check_stake_program(program_id: &Pubkey) -> Result<(), ProgramError> {
278    if *program_id != stake::program::id() {
279        msg!(
280            "Expected stake program {}, received {}",
281            stake::program::id(),
282            program_id
283        );
284        Err(ProgramError::IncorrectProgramId)
285    } else {
286        Ok(())
287    }
288}
289
290/// Check MPL metadata program
291fn check_mpl_metadata_program(program_id: &Pubkey) -> Result<(), ProgramError> {
292    if *program_id != inline_mpl_token_metadata::id() {
293        msg!(
294            "Expected MPL metadata program {}, received {}",
295            inline_mpl_token_metadata::id(),
296            program_id
297        );
298        Err(ProgramError::IncorrectProgramId)
299    } else {
300        Ok(())
301    }
302}
303
304/// Check account owner is the given program
305fn check_account_owner(
306    account_info: &AccountInfo,
307    program_id: &Pubkey,
308) -> Result<(), ProgramError> {
309    if *program_id != *account_info.owner {
310        msg!(
311            "Expected account to be owned by program {}, received {}",
312            program_id,
313            account_info.owner
314        );
315        Err(ProgramError::IncorrectProgramId)
316    } else {
317        Ok(())
318    }
319}
320
321/// Minimum delegation to create a pool
322/// We floor at 1 sol to avoid over-minting tokens before the relevant feature is
323/// active
324fn minimum_delegation() -> Result<u64, ProgramError> {
325    Ok(std::cmp::max(
326        stake::tools::get_minimum_delegation()?,
327        LAMPORTS_PER_SOL,
328    ))
329}
330
331/// Program state handler.
332pub struct Processor {}
333impl Processor {
334    #[allow(clippy::too_many_arguments)]
335    fn stake_merge<'a>(
336        pool_account_key: &Pubkey,
337        source_account: AccountInfo<'a>,
338        authority: AccountInfo<'a>,
339        bump_seed: u8,
340        destination_account: AccountInfo<'a>,
341        clock: AccountInfo<'a>,
342        stake_history: AccountInfo<'a>,
343    ) -> Result<(), ProgramError> {
344        let authority_seeds = &[
345            POOL_STAKE_AUTHORITY_PREFIX,
346            pool_account_key.as_ref(),
347            &[bump_seed],
348        ];
349        let signers = &[&authority_seeds[..]];
350
351        invoke_signed(
352            &stake::instruction::merge(destination_account.key, source_account.key, authority.key)
353                [0],
354            &[
355                destination_account,
356                source_account,
357                clock,
358                stake_history,
359                authority,
360            ],
361            signers,
362        )
363    }
364
365    fn stake_split<'a>(
366        pool_account_key: &Pubkey,
367        stake_account: AccountInfo<'a>,
368        authority: AccountInfo<'a>,
369        bump_seed: u8,
370        amount: u64,
371        split_stake: AccountInfo<'a>,
372    ) -> Result<(), ProgramError> {
373        let authority_seeds = &[
374            POOL_STAKE_AUTHORITY_PREFIX,
375            pool_account_key.as_ref(),
376            &[bump_seed],
377        ];
378        let signers = &[&authority_seeds[..]];
379
380        let split_instruction =
381            stake::instruction::split(stake_account.key, authority.key, amount, split_stake.key);
382
383        invoke_signed(
384            split_instruction.last().unwrap(),
385            &[stake_account, split_stake, authority],
386            signers,
387        )
388    }
389
390    #[allow(clippy::too_many_arguments)]
391    fn stake_authorize<'a>(
392        pool_account_key: &Pubkey,
393        stake_account: AccountInfo<'a>,
394        stake_authority: AccountInfo<'a>,
395        bump_seed: u8,
396        new_stake_authority: &Pubkey,
397        clock: AccountInfo<'a>,
398    ) -> Result<(), ProgramError> {
399        let authority_seeds = &[
400            POOL_STAKE_AUTHORITY_PREFIX,
401            pool_account_key.as_ref(),
402            &[bump_seed],
403        ];
404        let signers = &[&authority_seeds[..]];
405
406        let authorize_instruction = stake::instruction::authorize(
407            stake_account.key,
408            stake_authority.key,
409            new_stake_authority,
410            stake::state::StakeAuthorize::Staker,
411            None,
412        );
413
414        invoke_signed(
415            &authorize_instruction,
416            &[
417                stake_account.clone(),
418                clock.clone(),
419                stake_authority.clone(),
420            ],
421            signers,
422        )?;
423
424        let authorize_instruction = stake::instruction::authorize(
425            stake_account.key,
426            stake_authority.key,
427            new_stake_authority,
428            stake::state::StakeAuthorize::Withdrawer,
429            None,
430        );
431        invoke_signed(
432            &authorize_instruction,
433            &[stake_account, clock, stake_authority],
434            signers,
435        )
436    }
437
438    #[allow(clippy::too_many_arguments)]
439    fn stake_withdraw<'a>(
440        pool_account_key: &Pubkey,
441        stake_account: AccountInfo<'a>,
442        stake_authority: AccountInfo<'a>,
443        bump_seed: u8,
444        destination_account: AccountInfo<'a>,
445        clock: AccountInfo<'a>,
446        stake_history: AccountInfo<'a>,
447        lamports: u64,
448    ) -> Result<(), ProgramError> {
449        let authority_seeds = &[
450            POOL_STAKE_AUTHORITY_PREFIX,
451            pool_account_key.as_ref(),
452            &[bump_seed],
453        ];
454        let signers = &[&authority_seeds[..]];
455
456        let withdraw_instruction = stake::instruction::withdraw(
457            stake_account.key,
458            stake_authority.key,
459            destination_account.key,
460            lamports,
461            None,
462        );
463
464        invoke_signed(
465            &withdraw_instruction,
466            &[
467                stake_account,
468                destination_account,
469                clock,
470                stake_history,
471                stake_authority,
472            ],
473            signers,
474        )
475    }
476
477    #[allow(clippy::too_many_arguments)]
478    fn token_mint_to<'a>(
479        pool_account_key: &Pubkey,
480        token_program: AccountInfo<'a>,
481        mint: AccountInfo<'a>,
482        destination: AccountInfo<'a>,
483        authority: AccountInfo<'a>,
484        bump_seed: u8,
485        amount: u64,
486    ) -> Result<(), ProgramError> {
487        let authority_seeds = &[
488            POOL_MINT_AUTHORITY_PREFIX,
489            pool_account_key.as_ref(),
490            &[bump_seed],
491        ];
492        let signers = &[&authority_seeds[..]];
493
494        let ix = spl_token::instruction::mint_to(
495            token_program.key,
496            mint.key,
497            destination.key,
498            authority.key,
499            &[],
500            amount,
501        )?;
502
503        invoke_signed(&ix, &[mint, destination, authority], signers)
504    }
505
506    #[allow(clippy::too_many_arguments)]
507    fn token_burn<'a>(
508        pool_account_key: &Pubkey,
509        token_program: AccountInfo<'a>,
510        burn_account: AccountInfo<'a>,
511        mint: AccountInfo<'a>,
512        authority: AccountInfo<'a>,
513        bump_seed: u8,
514        amount: u64,
515    ) -> Result<(), ProgramError> {
516        let authority_seeds = &[
517            POOL_MINT_AUTHORITY_PREFIX,
518            pool_account_key.as_ref(),
519            &[bump_seed],
520        ];
521        let signers = &[&authority_seeds[..]];
522
523        let ix = spl_token::instruction::burn(
524            token_program.key,
525            burn_account.key,
526            mint.key,
527            authority.key,
528            &[],
529            amount,
530        )?;
531
532        invoke_signed(&ix, &[burn_account, mint, authority], signers)
533    }
534
535    fn process_initialize_pool(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
536        let account_info_iter = &mut accounts.iter();
537        let vote_account_info = next_account_info(account_info_iter)?;
538        let pool_info = next_account_info(account_info_iter)?;
539        let pool_stake_info = next_account_info(account_info_iter)?;
540        let pool_mint_info = next_account_info(account_info_iter)?;
541        let pool_stake_authority_info = next_account_info(account_info_iter)?;
542        let pool_mint_authority_info = next_account_info(account_info_iter)?;
543        let rent_info = next_account_info(account_info_iter)?;
544        let rent = &Rent::from_account_info(rent_info)?;
545        let clock_info = next_account_info(account_info_iter)?;
546        let stake_history_info = next_account_info(account_info_iter)?;
547        let stake_config_info = next_account_info(account_info_iter)?;
548        let system_program_info = next_account_info(account_info_iter)?;
549        let token_program_info = next_account_info(account_info_iter)?;
550        let stake_program_info = next_account_info(account_info_iter)?;
551
552        check_vote_account(vote_account_info)?;
553        let pool_bump_seed = check_pool_address(program_id, vote_account_info.key, pool_info.key)?;
554        let stake_bump_seed =
555            check_pool_stake_address(program_id, pool_info.key, pool_stake_info.key)?;
556        let mint_bump_seed =
557            check_pool_mint_address(program_id, pool_info.key, pool_mint_info.key)?;
558        let stake_authority_bump_seed = check_pool_stake_authority_address(
559            program_id,
560            pool_info.key,
561            pool_stake_authority_info.key,
562        )?;
563        let mint_authority_bump_seed = check_pool_mint_authority_address(
564            program_id,
565            pool_info.key,
566            pool_mint_authority_info.key,
567        )?;
568        check_system_program(system_program_info.key)?;
569        check_token_program(token_program_info.key)?;
570        check_stake_program(stake_program_info.key)?;
571
572        let pool_seeds = &[
573            POOL_PREFIX,
574            vote_account_info.key.as_ref(),
575            &[pool_bump_seed],
576        ];
577        let pool_signers = &[&pool_seeds[..]];
578
579        let stake_seeds = &[
580            POOL_STAKE_PREFIX,
581            pool_info.key.as_ref(),
582            &[stake_bump_seed],
583        ];
584        let stake_signers = &[&stake_seeds[..]];
585
586        let mint_seeds = &[POOL_MINT_PREFIX, pool_info.key.as_ref(), &[mint_bump_seed]];
587        let mint_signers = &[&mint_seeds[..]];
588
589        let stake_authority_seeds = &[
590            POOL_STAKE_AUTHORITY_PREFIX,
591            pool_info.key.as_ref(),
592            &[stake_authority_bump_seed],
593        ];
594        let stake_authority_signers = &[&stake_authority_seeds[..]];
595
596        let mint_authority_seeds = &[
597            POOL_MINT_AUTHORITY_PREFIX,
598            pool_info.key.as_ref(),
599            &[mint_authority_bump_seed],
600        ];
601        let mint_authority_signers = &[&mint_authority_seeds[..]];
602
603        // create the pool. user has already transferred in rent
604        let pool_space = get_packed_len::<SinglePool>();
605        if !rent.is_exempt(pool_info.lamports(), pool_space) {
606            return Err(SinglePoolError::WrongRentAmount.into());
607        }
608        if pool_info.data_len() != 0 {
609            return Err(SinglePoolError::PoolAlreadyInitialized.into());
610        }
611
612        invoke_signed(
613            &system_instruction::allocate(pool_info.key, pool_space as u64),
614            &[pool_info.clone()],
615            pool_signers,
616        )?;
617
618        invoke_signed(
619            &system_instruction::assign(pool_info.key, program_id),
620            &[pool_info.clone()],
621            pool_signers,
622        )?;
623
624        let mut pool = try_from_slice_unchecked::<SinglePool>(&pool_info.data.borrow())?;
625        pool.account_type = SinglePoolAccountType::Pool;
626        pool.vote_account_address = *vote_account_info.key;
627        borsh::to_writer(&mut pool_info.data.borrow_mut()[..], &pool)?;
628
629        // create the pool mint. user has already transferred in rent
630        let mint_space = spl_token::state::Mint::LEN;
631
632        invoke_signed(
633            &system_instruction::allocate(pool_mint_info.key, mint_space as u64),
634            &[pool_mint_info.clone()],
635            mint_signers,
636        )?;
637
638        invoke_signed(
639            &system_instruction::assign(pool_mint_info.key, token_program_info.key),
640            &[pool_mint_info.clone()],
641            mint_signers,
642        )?;
643
644        invoke_signed(
645            &spl_token::instruction::initialize_mint2(
646                token_program_info.key,
647                pool_mint_info.key,
648                pool_mint_authority_info.key,
649                None,
650                MINT_DECIMALS,
651            )?,
652            &[pool_mint_info.clone()],
653            mint_authority_signers,
654        )?;
655
656        // create the pool stake account. user has already transferred in rent plus at
657        // least the minimum
658        let minimum_delegation = minimum_delegation()?;
659        let stake_space = std::mem::size_of::<stake::state::StakeStateV2>();
660        let stake_rent_plus_initial = rent
661            .minimum_balance(stake_space)
662            .saturating_add(minimum_delegation);
663
664        if pool_stake_info.lamports() < stake_rent_plus_initial {
665            return Err(SinglePoolError::WrongRentAmount.into());
666        }
667
668        let authorized = stake::state::Authorized::auto(pool_stake_authority_info.key);
669
670        invoke_signed(
671            &system_instruction::allocate(pool_stake_info.key, stake_space as u64),
672            &[pool_stake_info.clone()],
673            stake_signers,
674        )?;
675
676        invoke_signed(
677            &system_instruction::assign(pool_stake_info.key, stake_program_info.key),
678            &[pool_stake_info.clone()],
679            stake_signers,
680        )?;
681
682        invoke_signed(
683            &stake::instruction::initialize_checked(pool_stake_info.key, &authorized),
684            &[
685                pool_stake_info.clone(),
686                rent_info.clone(),
687                pool_stake_authority_info.clone(),
688                pool_stake_authority_info.clone(),
689            ],
690            stake_authority_signers,
691        )?;
692
693        // delegate stake so it activates
694        invoke_signed(
695            &stake::instruction::delegate_stake(
696                pool_stake_info.key,
697                pool_stake_authority_info.key,
698                vote_account_info.key,
699            ),
700            &[
701                pool_stake_info.clone(),
702                vote_account_info.clone(),
703                clock_info.clone(),
704                stake_history_info.clone(),
705                stake_config_info.clone(),
706                pool_stake_authority_info.clone(),
707            ],
708            stake_authority_signers,
709        )?;
710
711        Ok(())
712    }
713
714    fn process_reactivate_pool_stake(
715        program_id: &Pubkey,
716        accounts: &[AccountInfo],
717    ) -> ProgramResult {
718        let account_info_iter = &mut accounts.iter();
719        let vote_account_info = next_account_info(account_info_iter)?;
720        let pool_info = next_account_info(account_info_iter)?;
721        let pool_stake_info = next_account_info(account_info_iter)?;
722        let pool_stake_authority_info = next_account_info(account_info_iter)?;
723        let clock_info = next_account_info(account_info_iter)?;
724        let clock = &Clock::from_account_info(clock_info)?;
725        let stake_history_info = next_account_info(account_info_iter)?;
726        let stake_config_info = next_account_info(account_info_iter)?;
727        let stake_program_info = next_account_info(account_info_iter)?;
728
729        check_vote_account(vote_account_info)?;
730        check_pool_address(program_id, vote_account_info.key, pool_info.key)?;
731
732        SinglePool::from_account_info(pool_info, program_id)?;
733
734        check_pool_stake_address(program_id, pool_info.key, pool_stake_info.key)?;
735        let stake_authority_bump_seed = check_pool_stake_authority_address(
736            program_id,
737            pool_info.key,
738            pool_stake_authority_info.key,
739        )?;
740        check_stake_program(stake_program_info.key)?;
741
742        let (_, pool_stake_state) = get_stake_state(pool_stake_info)?;
743        if pool_stake_state.delegation.deactivation_epoch > clock.epoch {
744            return Err(SinglePoolError::WrongStakeStake.into());
745        }
746
747        let stake_authority_seeds = &[
748            POOL_STAKE_AUTHORITY_PREFIX,
749            pool_info.key.as_ref(),
750            &[stake_authority_bump_seed],
751        ];
752        let stake_authority_signers = &[&stake_authority_seeds[..]];
753
754        // delegate stake so it activates
755        invoke_signed(
756            &stake::instruction::delegate_stake(
757                pool_stake_info.key,
758                pool_stake_authority_info.key,
759                vote_account_info.key,
760            ),
761            &[
762                pool_stake_info.clone(),
763                vote_account_info.clone(),
764                clock_info.clone(),
765                stake_history_info.clone(),
766                stake_config_info.clone(),
767                pool_stake_authority_info.clone(),
768            ],
769            stake_authority_signers,
770        )?;
771
772        Ok(())
773    }
774
775    fn process_deposit_stake(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
776        let account_info_iter = &mut accounts.iter();
777        let pool_info = next_account_info(account_info_iter)?;
778        let pool_stake_info = next_account_info(account_info_iter)?;
779        let pool_mint_info = next_account_info(account_info_iter)?;
780        let pool_stake_authority_info = next_account_info(account_info_iter)?;
781        let pool_mint_authority_info = next_account_info(account_info_iter)?;
782        let user_stake_info = next_account_info(account_info_iter)?;
783        let user_token_account_info = next_account_info(account_info_iter)?;
784        let user_lamport_account_info = next_account_info(account_info_iter)?;
785        let clock_info = next_account_info(account_info_iter)?;
786        let clock = &Clock::from_account_info(clock_info)?;
787        let stake_history_info = next_account_info(account_info_iter)?;
788        let token_program_info = next_account_info(account_info_iter)?;
789        let stake_program_info = next_account_info(account_info_iter)?;
790
791        SinglePool::from_account_info(pool_info, program_id)?;
792
793        check_pool_stake_address(program_id, pool_info.key, pool_stake_info.key)?;
794        let stake_authority_bump_seed = check_pool_stake_authority_address(
795            program_id,
796            pool_info.key,
797            pool_stake_authority_info.key,
798        )?;
799        let mint_authority_bump_seed = check_pool_mint_authority_address(
800            program_id,
801            pool_info.key,
802            pool_mint_authority_info.key,
803        )?;
804        check_pool_mint_address(program_id, pool_info.key, pool_mint_info.key)?;
805        check_token_program(token_program_info.key)?;
806        check_stake_program(stake_program_info.key)?;
807
808        if pool_stake_info.key == user_stake_info.key {
809            return Err(SinglePoolError::InvalidPoolStakeAccountUsage.into());
810        }
811
812        let minimum_delegation = minimum_delegation()?;
813
814        let (_, pool_stake_state) = get_stake_state(pool_stake_info)?;
815        let pre_pool_stake = pool_stake_state
816            .delegation
817            .stake
818            .saturating_sub(minimum_delegation);
819        msg!("Available stake pre merge {}", pre_pool_stake);
820
821        // user can deposit active stake into an active pool or inactive stake into an
822        // activating pool
823        let (user_stake_meta, user_stake_state) = get_stake_state(user_stake_info)?;
824        if user_stake_meta.authorized
825            != stake::state::Authorized::auto(pool_stake_authority_info.key)
826            || is_stake_active_without_history(&pool_stake_state, clock.epoch)
827                != is_stake_active_without_history(&user_stake_state, clock.epoch)
828        {
829            return Err(SinglePoolError::WrongStakeStake.into());
830        }
831
832        // merge the user stake account, which is preauthed to us, into the pool stake
833        // account this merge succeeding implicitly validates authority/lockup
834        // of the user stake account
835        Self::stake_merge(
836            pool_info.key,
837            user_stake_info.clone(),
838            pool_stake_authority_info.clone(),
839            stake_authority_bump_seed,
840            pool_stake_info.clone(),
841            clock_info.clone(),
842            stake_history_info.clone(),
843        )?;
844
845        let (pool_stake_meta, pool_stake_state) = get_stake_state(pool_stake_info)?;
846        let post_pool_stake = pool_stake_state
847            .delegation
848            .stake
849            .saturating_sub(minimum_delegation);
850        let post_pool_lamports = pool_stake_info.lamports();
851        msg!("Available stake post merge {}", post_pool_stake);
852
853        // stake lamports added, as a stake difference
854        let stake_added = post_pool_stake
855            .checked_sub(pre_pool_stake)
856            .ok_or(SinglePoolError::ArithmeticOverflow)?;
857
858        // we calculate absolute rather than relative to deposit amount to allow
859        // claiming lamports mistakenly transferred in
860        let excess_lamports = post_pool_lamports
861            .checked_sub(pool_stake_state.delegation.stake)
862            .and_then(|amount| amount.checked_sub(pool_stake_meta.rent_exempt_reserve))
863            .ok_or(SinglePoolError::ArithmeticOverflow)?;
864
865        // sanity check: the user stake account is empty
866        if user_stake_info.lamports() != 0 {
867            return Err(SinglePoolError::UnexpectedMathError.into());
868        }
869
870        let token_supply = {
871            let pool_mint_data = pool_mint_info.try_borrow_data()?;
872            let pool_mint = Mint::unpack_from_slice(&pool_mint_data)?;
873            pool_mint.supply
874        };
875
876        // deposit amount is determined off stake because we return excess rent
877        let new_pool_tokens = calculate_deposit_amount(token_supply, pre_pool_stake, stake_added)
878            .ok_or(SinglePoolError::UnexpectedMathError)?;
879
880        if new_pool_tokens == 0 {
881            return Err(SinglePoolError::DepositTooSmall.into());
882        }
883
884        // mint tokens to the user corresponding to their stake deposit
885        Self::token_mint_to(
886            pool_info.key,
887            token_program_info.clone(),
888            pool_mint_info.clone(),
889            user_token_account_info.clone(),
890            pool_mint_authority_info.clone(),
891            mint_authority_bump_seed,
892            new_pool_tokens,
893        )?;
894
895        // return the lamports their stake account previously held for rent-exemption
896        if excess_lamports > 0 {
897            Self::stake_withdraw(
898                pool_info.key,
899                pool_stake_info.clone(),
900                pool_stake_authority_info.clone(),
901                stake_authority_bump_seed,
902                user_lamport_account_info.clone(),
903                clock_info.clone(),
904                stake_history_info.clone(),
905                excess_lamports,
906            )?;
907        }
908
909        Ok(())
910    }
911
912    fn process_withdraw_stake(
913        program_id: &Pubkey,
914        accounts: &[AccountInfo],
915        user_stake_authority: &Pubkey,
916        token_amount: u64,
917    ) -> ProgramResult {
918        let account_info_iter = &mut accounts.iter();
919        let pool_info = next_account_info(account_info_iter)?;
920        let pool_stake_info = next_account_info(account_info_iter)?;
921        let pool_mint_info = next_account_info(account_info_iter)?;
922        let pool_stake_authority_info = next_account_info(account_info_iter)?;
923        let pool_mint_authority_info = next_account_info(account_info_iter)?;
924        let user_stake_info = next_account_info(account_info_iter)?;
925        let user_token_account_info = next_account_info(account_info_iter)?;
926        let clock_info = next_account_info(account_info_iter)?;
927        let token_program_info = next_account_info(account_info_iter)?;
928        let stake_program_info = next_account_info(account_info_iter)?;
929
930        SinglePool::from_account_info(pool_info, program_id)?;
931
932        check_pool_stake_address(program_id, pool_info.key, pool_stake_info.key)?;
933        let stake_authority_bump_seed = check_pool_stake_authority_address(
934            program_id,
935            pool_info.key,
936            pool_stake_authority_info.key,
937        )?;
938        let mint_authority_bump_seed = check_pool_mint_authority_address(
939            program_id,
940            pool_info.key,
941            pool_mint_authority_info.key,
942        )?;
943        check_pool_mint_address(program_id, pool_info.key, pool_mint_info.key)?;
944        check_token_program(token_program_info.key)?;
945        check_stake_program(stake_program_info.key)?;
946
947        if pool_stake_info.key == user_stake_info.key {
948            return Err(SinglePoolError::InvalidPoolStakeAccountUsage.into());
949        }
950
951        let minimum_delegation = minimum_delegation()?;
952
953        let pre_pool_stake = get_stake_amount(pool_stake_info)?.saturating_sub(minimum_delegation);
954        msg!("Available stake pre split {}", pre_pool_stake);
955
956        let token_supply = {
957            let pool_mint_data = pool_mint_info.try_borrow_data()?;
958            let pool_mint = Mint::unpack_from_slice(&pool_mint_data)?;
959            pool_mint.supply
960        };
961
962        // withdraw amount is determined off stake just like deposit amount
963        let withdraw_stake = calculate_withdraw_amount(token_supply, pre_pool_stake, token_amount)
964            .ok_or(SinglePoolError::UnexpectedMathError)?;
965
966        if withdraw_stake == 0 {
967            return Err(SinglePoolError::WithdrawalTooSmall.into());
968        }
969
970        // the second case should never be true, but its best to be sure
971        if withdraw_stake > pre_pool_stake || withdraw_stake == pool_stake_info.lamports() {
972            return Err(SinglePoolError::WithdrawalTooLarge.into());
973        }
974
975        // burn user tokens corresponding to the amount of stake they wish to withdraw
976        Self::token_burn(
977            pool_info.key,
978            token_program_info.clone(),
979            user_token_account_info.clone(),
980            pool_mint_info.clone(),
981            pool_mint_authority_info.clone(),
982            mint_authority_bump_seed,
983            token_amount,
984        )?;
985
986        // split stake into a blank stake account the user has created for this purpose
987        Self::stake_split(
988            pool_info.key,
989            pool_stake_info.clone(),
990            pool_stake_authority_info.clone(),
991            stake_authority_bump_seed,
992            withdraw_stake,
993            user_stake_info.clone(),
994        )?;
995
996        // assign both authorities on the new stake account to the user
997        Self::stake_authorize(
998            pool_info.key,
999            user_stake_info.clone(),
1000            pool_stake_authority_info.clone(),
1001            stake_authority_bump_seed,
1002            user_stake_authority,
1003            clock_info.clone(),
1004        )?;
1005
1006        let post_pool_stake = get_stake_amount(pool_stake_info)?.saturating_sub(minimum_delegation);
1007        msg!("Available stake post split {}", post_pool_stake);
1008
1009        Ok(())
1010    }
1011
1012    fn process_create_pool_token_metadata(
1013        program_id: &Pubkey,
1014        accounts: &[AccountInfo],
1015    ) -> ProgramResult {
1016        let account_info_iter = &mut accounts.iter();
1017        let pool_info = next_account_info(account_info_iter)?;
1018        let pool_mint_info = next_account_info(account_info_iter)?;
1019        let pool_mint_authority_info = next_account_info(account_info_iter)?;
1020        let pool_mpl_authority_info = next_account_info(account_info_iter)?;
1021        let payer_info = next_account_info(account_info_iter)?;
1022        let metadata_info = next_account_info(account_info_iter)?;
1023        let mpl_token_metadata_program_info = next_account_info(account_info_iter)?;
1024        let system_program_info = next_account_info(account_info_iter)?;
1025
1026        let pool = SinglePool::from_account_info(pool_info, program_id)?;
1027
1028        let mint_authority_bump_seed = check_pool_mint_authority_address(
1029            program_id,
1030            pool_info.key,
1031            pool_mint_authority_info.key,
1032        )?;
1033        let mpl_authority_bump_seed = check_pool_mpl_authority_address(
1034            program_id,
1035            pool_info.key,
1036            pool_mpl_authority_info.key,
1037        )?;
1038        check_pool_mint_address(program_id, pool_info.key, pool_mint_info.key)?;
1039        check_system_program(system_program_info.key)?;
1040        check_account_owner(payer_info, &system_program::id())?;
1041        check_mpl_metadata_program(mpl_token_metadata_program_info.key)?;
1042        check_mpl_metadata_account_address(metadata_info.key, pool_mint_info.key)?;
1043
1044        if !payer_info.is_signer {
1045            msg!("Payer did not sign metadata creation");
1046            return Err(SinglePoolError::SignatureMissing.into());
1047        }
1048
1049        let vote_address_str = pool.vote_account_address.to_string();
1050        let token_name = format!("SPL Single Pool {}", &vote_address_str[0..15]);
1051        let token_symbol = format!("st{}", &vote_address_str[0..7]);
1052
1053        let new_metadata_instruction = create_metadata_accounts_v3(
1054            *mpl_token_metadata_program_info.key,
1055            *metadata_info.key,
1056            *pool_mint_info.key,
1057            *pool_mint_authority_info.key,
1058            *payer_info.key,
1059            *pool_mpl_authority_info.key,
1060            token_name,
1061            token_symbol,
1062            "".to_string(),
1063        );
1064
1065        let mint_authority_seeds = &[
1066            POOL_MINT_AUTHORITY_PREFIX,
1067            pool_info.key.as_ref(),
1068            &[mint_authority_bump_seed],
1069        ];
1070        let mpl_authority_seeds = &[
1071            POOL_MPL_AUTHORITY_PREFIX,
1072            pool_info.key.as_ref(),
1073            &[mpl_authority_bump_seed],
1074        ];
1075        let signers = &[&mint_authority_seeds[..], &mpl_authority_seeds[..]];
1076
1077        invoke_signed(
1078            &new_metadata_instruction,
1079            &[
1080                metadata_info.clone(),
1081                pool_mint_info.clone(),
1082                pool_mint_authority_info.clone(),
1083                payer_info.clone(),
1084                pool_mpl_authority_info.clone(),
1085                system_program_info.clone(),
1086            ],
1087            signers,
1088        )?;
1089
1090        Ok(())
1091    }
1092
1093    fn process_update_pool_token_metadata(
1094        program_id: &Pubkey,
1095        accounts: &[AccountInfo],
1096        name: String,
1097        symbol: String,
1098        uri: String,
1099    ) -> ProgramResult {
1100        let account_info_iter = &mut accounts.iter();
1101        let vote_account_info = next_account_info(account_info_iter)?;
1102        let pool_info = next_account_info(account_info_iter)?;
1103        let pool_mpl_authority_info = next_account_info(account_info_iter)?;
1104        let authorized_withdrawer_info = next_account_info(account_info_iter)?;
1105        let metadata_info = next_account_info(account_info_iter)?;
1106        let mpl_token_metadata_program_info = next_account_info(account_info_iter)?;
1107
1108        check_vote_account(vote_account_info)?;
1109        check_pool_address(program_id, vote_account_info.key, pool_info.key)?;
1110
1111        let pool = SinglePool::from_account_info(pool_info, program_id)?;
1112        if pool.vote_account_address != *vote_account_info.key {
1113            return Err(SinglePoolError::InvalidPoolAccount.into());
1114        }
1115
1116        let mpl_authority_bump_seed = check_pool_mpl_authority_address(
1117            program_id,
1118            pool_info.key,
1119            pool_mpl_authority_info.key,
1120        )?;
1121        let pool_mint_address = crate::find_pool_mint_address(program_id, pool_info.key);
1122        check_mpl_metadata_program(mpl_token_metadata_program_info.key)?;
1123        check_mpl_metadata_account_address(metadata_info.key, &pool_mint_address)?;
1124
1125        // we use authorized_withdrawer to authenticate the caller controls the vote
1126        // account this is safer than using an authorized_voter since those keys
1127        // live hot and validator-operators we spoke with indicated this would
1128        // be their preference as well
1129        let vote_account_data = &vote_account_info.try_borrow_data()?;
1130        let vote_account_withdrawer = vote_account_data
1131            .get(VOTE_STATE_AUTHORIZED_WITHDRAWER_START..VOTE_STATE_AUTHORIZED_WITHDRAWER_END)
1132            .and_then(|x| Pubkey::try_from(x).ok())
1133            .ok_or(SinglePoolError::UnparseableVoteAccount)?;
1134
1135        if *authorized_withdrawer_info.key != vote_account_withdrawer {
1136            msg!("Vote account authorized withdrawer does not match the account provided.");
1137            return Err(SinglePoolError::InvalidMetadataSigner.into());
1138        }
1139
1140        if !authorized_withdrawer_info.is_signer {
1141            msg!("Vote account authorized withdrawer did not sign metadata update.");
1142            return Err(SinglePoolError::SignatureMissing.into());
1143        }
1144
1145        let update_metadata_accounts_instruction = update_metadata_accounts_v2(
1146            *mpl_token_metadata_program_info.key,
1147            *metadata_info.key,
1148            *pool_mpl_authority_info.key,
1149            None,
1150            Some(DataV2 {
1151                name,
1152                symbol,
1153                uri,
1154                seller_fee_basis_points: 0,
1155                creators: None,
1156                collection: None,
1157                uses: None,
1158            }),
1159            None,
1160            Some(true),
1161        );
1162
1163        let mpl_authority_seeds = &[
1164            POOL_MPL_AUTHORITY_PREFIX,
1165            pool_info.key.as_ref(),
1166            &[mpl_authority_bump_seed],
1167        ];
1168        let signers = &[&mpl_authority_seeds[..]];
1169
1170        invoke_signed(
1171            &update_metadata_accounts_instruction,
1172            &[metadata_info.clone(), pool_mpl_authority_info.clone()],
1173            signers,
1174        )?;
1175
1176        Ok(())
1177    }
1178
1179    /// Processes [Instruction](enum.Instruction.html).
1180    pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
1181        let instruction = SinglePoolInstruction::try_from_slice(input)?;
1182        match instruction {
1183            SinglePoolInstruction::InitializePool => {
1184                msg!("Instruction: InitializePool");
1185                Self::process_initialize_pool(program_id, accounts)
1186            }
1187            SinglePoolInstruction::ReactivatePoolStake => {
1188                msg!("Instruction: ReactivatePoolStake");
1189                Self::process_reactivate_pool_stake(program_id, accounts)
1190            }
1191            SinglePoolInstruction::DepositStake => {
1192                msg!("Instruction: DepositStake");
1193                Self::process_deposit_stake(program_id, accounts)
1194            }
1195            SinglePoolInstruction::WithdrawStake {
1196                user_stake_authority,
1197                token_amount,
1198            } => {
1199                msg!("Instruction: WithdrawStake");
1200                Self::process_withdraw_stake(
1201                    program_id,
1202                    accounts,
1203                    &user_stake_authority,
1204                    token_amount,
1205                )
1206            }
1207            SinglePoolInstruction::CreateTokenMetadata => {
1208                msg!("Instruction: CreateTokenMetadata");
1209                Self::process_create_pool_token_metadata(program_id, accounts)
1210            }
1211            SinglePoolInstruction::UpdateTokenMetadata { name, symbol, uri } => {
1212                msg!("Instruction: UpdateTokenMetadata");
1213                Self::process_update_pool_token_metadata(program_id, accounts, name, symbol, uri)
1214            }
1215        }
1216    }
1217}
1218
1219#[cfg(test)]
1220#[allow(clippy::arithmetic_side_effects)]
1221mod tests {
1222    use {
1223        super::*,
1224        approx::assert_relative_eq,
1225        rand::{
1226            distributions::{Distribution, Uniform},
1227            rngs::StdRng,
1228            seq::{IteratorRandom, SliceRandom},
1229            Rng, SeedableRng,
1230        },
1231        std::collections::BTreeMap,
1232        test_case::test_case,
1233    };
1234
1235    // approximately 6%/yr assuking 146 epochs
1236    const INFLATION_BASE_RATE: f64 = 0.0004;
1237
1238    #[derive(Clone, Debug, Default)]
1239    struct PoolState {
1240        pub token_supply: u64,
1241        pub total_stake: u64,
1242        pub user_token_balances: BTreeMap<Pubkey, u64>,
1243    }
1244    impl PoolState {
1245        // deposits a given amount of stake and returns the equivalent tokens on success
1246        // note this is written as unsugared do-notation, so *any* failure returns None
1247        // otherwise returns the value produced by its respective calculate function
1248        #[rustfmt::skip]
1249        pub fn deposit(&mut self, user_pubkey: &Pubkey, stake_to_deposit: u64) -> Option<u64> {
1250            calculate_deposit_amount(self.token_supply, self.total_stake, stake_to_deposit)
1251                .and_then(|tokens_to_mint| self.token_supply.checked_add(tokens_to_mint)
1252                .and_then(|new_token_supply| self.total_stake.checked_add(stake_to_deposit)
1253                .and_then(|new_total_stake| self.user_token_balances.remove(user_pubkey).or(Some(0))
1254                .and_then(|old_user_token_balance| old_user_token_balance.checked_add(tokens_to_mint)
1255                .map(|new_user_token_balance| {
1256                    self.token_supply = new_token_supply;
1257                    self.total_stake = new_total_stake;
1258                    let _ = self.user_token_balances.insert(*user_pubkey, new_user_token_balance);
1259                    tokens_to_mint
1260            })))))
1261        }
1262
1263        // burns a given amount of tokens and returns the equivalent stake on success
1264        // note this is written as unsugared do-notation, so *any* failure returns None
1265        // otherwise returns the value produced by its respective calculate function
1266        #[rustfmt::skip]
1267        pub fn withdraw(&mut self, user_pubkey: &Pubkey, tokens_to_burn: u64) -> Option<u64> {
1268            calculate_withdraw_amount(self.token_supply, self.total_stake, tokens_to_burn)
1269                .and_then(|stake_to_withdraw| self.token_supply.checked_sub(tokens_to_burn)
1270                .and_then(|new_token_supply| self.total_stake.checked_sub(stake_to_withdraw)
1271                .and_then(|new_total_stake| self.user_token_balances.remove(user_pubkey)
1272                .and_then(|old_user_token_balance| old_user_token_balance.checked_sub(tokens_to_burn)
1273                .map(|new_user_token_balance| {
1274                    self.token_supply = new_token_supply;
1275                    self.total_stake = new_total_stake;
1276                    let _ = self.user_token_balances.insert(*user_pubkey, new_user_token_balance);
1277                    stake_to_withdraw
1278            })))))
1279        }
1280
1281        // adds an arbitrary amount of stake, as if inflation rewards were granted
1282        pub fn reward(&mut self, reward_amount: u64) {
1283            self.total_stake = self.total_stake.checked_add(reward_amount).unwrap();
1284        }
1285
1286        // get the token balance for a user
1287        pub fn tokens(&self, user_pubkey: &Pubkey) -> u64 {
1288            *self.user_token_balances.get(user_pubkey).unwrap_or(&0)
1289        }
1290
1291        // get the amount of stake that belongs to a user
1292        pub fn stake(&self, user_pubkey: &Pubkey) -> u64 {
1293            let tokens = self.tokens(user_pubkey);
1294            if tokens > 0 {
1295                u64::try_from(tokens as u128 * self.total_stake as u128 / self.token_supply as u128)
1296                    .unwrap()
1297            } else {
1298                0
1299            }
1300        }
1301
1302        // get the share of the pool that belongs to a user, as a float between 0 and 1
1303        pub fn share(&self, user_pubkey: &Pubkey) -> f64 {
1304            let tokens = self.tokens(user_pubkey);
1305            if tokens > 0 {
1306                tokens as f64 / self.token_supply as f64
1307            } else {
1308                0.0
1309            }
1310        }
1311    }
1312
1313    // this deterministically tests basic behavior of calculate_deposit_amount and
1314    // calculate_withdraw_amount
1315    #[test]
1316    fn simple_deposit_withdraw() {
1317        let mut pool = PoolState::default();
1318        let alice = Pubkey::new_unique();
1319        let bob = Pubkey::new_unique();
1320        let chad = Pubkey::new_unique();
1321
1322        // first deposit. alice now has 250
1323        pool.deposit(&alice, 250).unwrap();
1324        assert_eq!(pool.tokens(&alice), 250);
1325        assert_eq!(pool.token_supply, 250);
1326        assert_eq!(pool.total_stake, 250);
1327
1328        // second deposit. bob now has 750
1329        pool.deposit(&bob, 750).unwrap();
1330        assert_eq!(pool.tokens(&bob), 750);
1331        assert_eq!(pool.token_supply, 1000);
1332        assert_eq!(pool.total_stake, 1000);
1333
1334        // alice controls 25% of the pool and bob controls 75%. rewards should accrue
1335        // likewise use nice even numbers, we can test fiddly stuff in the
1336        // stochastic cases
1337        assert_relative_eq!(pool.share(&alice), 0.25);
1338        assert_relative_eq!(pool.share(&bob), 0.75);
1339        pool.reward(1000);
1340        assert_eq!(pool.stake(&alice), pool.tokens(&alice) * 2);
1341        assert_eq!(pool.stake(&bob), pool.tokens(&bob) * 2);
1342        assert_relative_eq!(pool.share(&alice), 0.25);
1343        assert_relative_eq!(pool.share(&bob), 0.75);
1344
1345        // alice harvests rewards, reducing her share of the *previous* pool size to
1346        // 12.5% but because the pool itself has shrunk to 87.5%, its actually
1347        // more like 14.3% luckily chad deposits immediately after to make our
1348        // math easier
1349        let stake_removed = pool.withdraw(&alice, 125).unwrap();
1350        pool.deposit(&chad, 250).unwrap();
1351        assert_eq!(stake_removed, 250);
1352        assert_relative_eq!(pool.share(&alice), 0.125);
1353        assert_relative_eq!(pool.share(&bob), 0.75);
1354
1355        // bob and chad exit the pool
1356        let stake_removed = pool.withdraw(&bob, 750).unwrap();
1357        assert_eq!(stake_removed, 1500);
1358        assert_relative_eq!(pool.share(&bob), 0.0);
1359        pool.withdraw(&chad, 125).unwrap();
1360        assert_relative_eq!(pool.share(&alice), 1.0);
1361    }
1362
1363    // this stochastically tests calculate_deposit_amount and
1364    // calculate_withdraw_amount the objective is specifically to ensure that
1365    // the math does not fail on any combination of state changes the no_minimum
1366    // case is to account for a future where small deposits are possible through
1367    // multistake
1368    #[test_case(rand::random(), false, false; "no_rewards")]
1369    #[test_case(rand::random(), true, false; "with_rewards")]
1370    #[test_case(rand::random(), true, true; "no_minimum")]
1371    fn random_deposit_withdraw(seed: u64, with_rewards: bool, no_minimum: bool) {
1372        println!(
1373            "TEST SEED: {}. edit the test case to pass this value if needed to debug failures",
1374            seed
1375        );
1376        let mut prng = rand::rngs::StdRng::seed_from_u64(seed);
1377
1378        // deposit_range is the range of typical deposits within minimum_delegation
1379        // minnow_range is under the minimum for cases where we test that
1380        // op_range is how we roll whether to deposit, withdraw, or reward
1381        // std_range is a standard probability
1382        let deposit_range = Uniform::from(LAMPORTS_PER_SOL..LAMPORTS_PER_SOL * 1000);
1383        let minnow_range = Uniform::from(1..LAMPORTS_PER_SOL);
1384        let op_range = Uniform::from(if with_rewards { 0.0..1.0 } else { 0.0..0.65 });
1385        let std_range = Uniform::from(0.0..1.0);
1386
1387        let deposit_amount = |prng: &mut StdRng| {
1388            if no_minimum && prng.gen_bool(0.2) {
1389                minnow_range.sample(prng)
1390            } else {
1391                deposit_range.sample(prng)
1392            }
1393        };
1394
1395        // run everything a number of times to get a good sample
1396        for _ in 0..100 {
1397            // PoolState tracks all outstanding tokens and the total combined stake
1398            // there is no reasonable way to track "deposited stake" because reward accrual
1399            // makes this concept incoherent a token corresponds to a
1400            // percentage, not a stake value
1401            let mut pool = PoolState::default();
1402
1403            // generate between 1 and 100 users and have ~half of them deposit
1404            // note for most of these tests we adhere to the minimum delegation
1405            // one of the thing we want to see is deposit size being many ooms larger than
1406            // reward size
1407            let mut users = vec![];
1408            let user_count: usize = prng.gen_range(1..=100);
1409            for _ in 0..user_count {
1410                let user = Pubkey::new_unique();
1411
1412                if prng.gen_bool(0.5) {
1413                    pool.deposit(&user, deposit_amount(&mut prng)).unwrap();
1414                }
1415
1416                users.push(user);
1417            }
1418
1419            // now we do a set of arbitrary operations and confirm invariants hold
1420            // we underweight withdraw a little bit to lessen the chances we random walk to
1421            // an empty pool
1422            for _ in 0..1000 {
1423                match op_range.sample(&mut prng) {
1424                    // deposit a random amount of stake for tokens with a random user
1425                    // check their stake, tokens, and share increase by the expected amount
1426                    n if n <= 0.35 => {
1427                        let user = users.choose(&mut prng).unwrap();
1428                        let prev_share = pool.share(user);
1429                        let prev_stake = pool.stake(user);
1430                        let prev_token_supply = pool.token_supply;
1431                        let prev_total_stake = pool.total_stake;
1432
1433                        let stake_deposited = deposit_amount(&mut prng);
1434                        let tokens_minted = pool.deposit(user, stake_deposited).unwrap();
1435
1436                        // stake increased by exactly the deposit amount
1437                        assert_eq!(pool.total_stake - prev_total_stake, stake_deposited);
1438
1439                        // calculated stake fraction is within 2 lamps of deposit amount
1440                        assert!(
1441                            (pool.stake(user) as i64 - prev_stake as i64 - stake_deposited as i64)
1442                                .abs()
1443                                <= 2
1444                        );
1445
1446                        // tokens increased by exactly the mint amount
1447                        assert_eq!(pool.token_supply - prev_token_supply, tokens_minted);
1448
1449                        // tokens per supply increased with stake per total
1450                        if prev_total_stake > 0 {
1451                            assert_relative_eq!(
1452                                pool.share(user) - prev_share,
1453                                pool.stake(user) as f64 / pool.total_stake as f64
1454                                    - prev_stake as f64 / prev_total_stake as f64,
1455                                epsilon = 1e-6
1456                            );
1457                        }
1458                    }
1459
1460                    // burn a random amount of tokens from a random user with outstanding deposits
1461                    // check their stake, tokens, and share decrease by the expected amount
1462                    n if n > 0.35 && n <= 0.65 => {
1463                        if let Some(user) = users
1464                            .iter()
1465                            .filter(|user| pool.tokens(user) > 0)
1466                            .choose(&mut prng)
1467                        {
1468                            let prev_tokens = pool.tokens(user);
1469                            let prev_share = pool.share(user);
1470                            let prev_stake = pool.stake(user);
1471                            let prev_token_supply = pool.token_supply;
1472                            let prev_total_stake = pool.total_stake;
1473
1474                            let tokens_burned = if std_range.sample(&mut prng) <= 0.1 {
1475                                prev_tokens
1476                            } else {
1477                                prng.gen_range(0..prev_tokens)
1478                            };
1479                            let stake_received = pool.withdraw(user, tokens_burned).unwrap();
1480
1481                            // stake decreased by exactly the withdraw amount
1482                            assert_eq!(prev_total_stake - pool.total_stake, stake_received);
1483
1484                            // calculated stake fraction is within 2 lamps of withdraw amount
1485                            assert!(
1486                                (prev_stake as i64
1487                                    - pool.stake(user) as i64
1488                                    - stake_received as i64)
1489                                    .abs()
1490                                    <= 2
1491                            );
1492
1493                            // tokens decreased by the burn amount
1494                            assert_eq!(prev_token_supply - pool.token_supply, tokens_burned);
1495
1496                            // tokens per supply decreased with stake per total
1497                            if pool.total_stake > 0 {
1498                                assert_relative_eq!(
1499                                    prev_share - pool.share(user),
1500                                    prev_stake as f64 / prev_total_stake as f64
1501                                        - pool.stake(user) as f64 / pool.total_stake as f64,
1502                                    epsilon = 1e-6
1503                                );
1504                            }
1505                        };
1506                    }
1507
1508                    // run a single epoch worth of rewards
1509                    // check all user shares stay the same and stakes increase by the expected
1510                    // amount
1511                    _ => {
1512                        assert!(with_rewards);
1513
1514                        let prev_shares_stakes = users
1515                            .iter()
1516                            .map(|user| (user, pool.share(user), pool.stake(user)))
1517                            .filter(|(_, _, stake)| stake > &0)
1518                            .collect::<Vec<_>>();
1519
1520                        pool.reward((pool.total_stake as f64 * INFLATION_BASE_RATE) as u64);
1521
1522                        for (user, prev_share, prev_stake) in prev_shares_stakes {
1523                            // shares are the same before and after
1524                            assert_eq!(pool.share(user), prev_share);
1525
1526                            let curr_stake = pool.stake(user);
1527                            let stake_share = prev_stake as f64 * INFLATION_BASE_RATE;
1528                            let stake_diff = (curr_stake - prev_stake) as f64;
1529
1530                            // stake increase is within 2 lamps when calculated as a difference or a
1531                            // percentage
1532                            assert!((stake_share - stake_diff).abs() <= 2.0);
1533                        }
1534                    }
1535                }
1536            }
1537        }
1538    }
1539}