Skip to main content

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, PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH, PHANTOM_TOKEN_AMOUNT,
15        POOL_MINT_AUTHORITY_PREFIX, POOL_MINT_PREFIX, POOL_MPL_AUTHORITY_PREFIX,
16        POOL_ONRAMP_PREFIX, POOL_PREFIX, POOL_STAKE_AUTHORITY_PREFIX, POOL_STAKE_PREFIX,
17        VOTE_STATE_AUTHORIZED_WITHDRAWER_END, VOTE_STATE_AUTHORIZED_WITHDRAWER_START,
18        VOTE_STATE_DISCRIMINATOR_END,
19    },
20    borsh::BorshDeserialize,
21    solana_account_info::{next_account_info, AccountInfo},
22    solana_borsh::v1::try_from_slice_unchecked,
23    solana_clock::Clock,
24    solana_cpi::invoke_signed,
25    solana_msg::msg,
26    solana_native_token::LAMPORTS_PER_SOL,
27    solana_program_entrypoint::ProgramResult,
28    solana_program_error::ProgramError,
29    solana_program_pack::Pack,
30    solana_pubkey::Pubkey,
31    solana_rent::Rent,
32    solana_stake_interface::{
33        self as stake,
34        state::{Meta, Stake, StakeActivationStatus, StakeStateV2},
35        sysvar::stake_history::StakeHistorySysvar,
36    },
37    solana_system_interface::{instruction as system_instruction, program as system_program},
38    solana_sysvar::{Sysvar, SysvarSerialize},
39    solana_vote_interface::program as vote_program,
40    spl_token_interface::{self as spl_token, state::Mint},
41};
42
43/// Determine the canonical value of the pool from its staked and stake-able lamports
44fn pool_net_asset_value(
45    pool_stake_info: &AccountInfo,
46    pool_onramp_info: &AccountInfo,
47    rent: &Rent,
48) -> u64 {
49    // these numbers should typically be equal, but might differ during StakeState upgrades
50    let pool_rent_exempt_reserve = rent.minimum_balance(pool_stake_info.data_len());
51    let onramp_rent_exempt_reserve = rent.minimum_balance(pool_onramp_info.data_len());
52
53    // NEV is all lamports in both accounts less rent
54    pool_stake_info
55        .lamports()
56        .saturating_add(pool_onramp_info.lamports())
57        .saturating_sub(pool_rent_exempt_reserve)
58        .saturating_sub(onramp_rent_exempt_reserve)
59}
60
61/// Calculate pool tokens to mint, given outstanding token supply, pool NEV, and deposit amount
62fn calculate_deposit_amount(
63    pre_token_supply: u64,
64    pre_pool_nev: u64,
65    user_deposit_amount: u64,
66) -> Option<u64> {
67    if pre_pool_nev == 0 || pre_token_supply == 0 {
68        Some(user_deposit_amount)
69    } else {
70        u64::try_from(
71            (user_deposit_amount as u128)
72                .checked_mul(pre_token_supply as u128)?
73                .checked_div(pre_pool_nev as u128)?,
74        )
75        .ok()
76    }
77}
78
79/// Calculate pool value to return, given outstanding token supply, pool NEV, and tokens to redeem
80fn calculate_withdraw_amount(
81    pre_token_supply: u64,
82    pre_pool_nev: u64,
83    user_tokens_to_burn: u64,
84) -> Option<u64> {
85    let numerator = (user_tokens_to_burn as u128).checked_mul(pre_pool_nev as u128)?;
86    let denominator = pre_token_supply as u128;
87    if numerator < denominator || denominator == 0 {
88        Some(0)
89    } else {
90        u64::try_from(numerator.checked_div(denominator)?).ok()
91    }
92}
93
94/// Deserialize the stake state from `AccountInfo`
95fn get_stake_state(stake_account_info: &AccountInfo) -> Result<(Meta, Stake), ProgramError> {
96    match deserialize_stake(stake_account_info) {
97        Ok(StakeStateV2::Stake(meta, stake, _)) => Ok((meta, stake)),
98        _ => Err(SinglePoolError::WrongStakeState.into()),
99    }
100}
101
102/// Deserialize the stake amount from `AccountInfo`
103fn get_stake_amount(stake_account_info: &AccountInfo) -> Result<u64, ProgramError> {
104    Ok(get_stake_state(stake_account_info)?.1.delegation.stake)
105}
106
107/// Wrapper so if we ever change stake deserialization we have it in one place
108fn deserialize_stake(stake_account_info: &AccountInfo) -> Result<StakeStateV2, ProgramError> {
109    Ok(try_from_slice_unchecked::<StakeStateV2>(
110        &stake_account_info.data.borrow(),
111    )?)
112}
113
114/// Determine if stake is fully active with history
115fn is_stake_fully_active(stake_activation_status: &StakeActivationStatus) -> bool {
116    matches!(stake_activation_status, StakeActivationStatus {
117            effective,
118            activating: 0,
119            deactivating: 0,
120        } if *effective > 0)
121}
122
123/// Determine if stake is newly activating with history
124fn is_stake_newly_activating(stake_activation_status: &StakeActivationStatus) -> bool {
125    matches!(stake_activation_status, StakeActivationStatus {
126            effective: 0,
127            activating,
128            deactivating: 0,
129        } if *activating > 0)
130}
131
132/// Check pool account address for the validator vote account
133fn check_pool_address(
134    program_id: &Pubkey,
135    vote_account_address: &Pubkey,
136    check_address: &Pubkey,
137) -> Result<u8, ProgramError> {
138    check_pool_pda(
139        program_id,
140        vote_account_address,
141        check_address,
142        &crate::find_pool_address_and_bump,
143        "pool",
144        SinglePoolError::InvalidPoolAccount,
145    )
146}
147
148/// Check pool stake account address for the pool account
149fn check_pool_stake_address(
150    program_id: &Pubkey,
151    pool_address: &Pubkey,
152    check_address: &Pubkey,
153) -> Result<u8, ProgramError> {
154    check_pool_pda(
155        program_id,
156        pool_address,
157        check_address,
158        &crate::find_pool_stake_address_and_bump,
159        "stake account",
160        SinglePoolError::InvalidPoolStakeAccount,
161    )
162}
163
164/// Check pool on-ramp account address for the pool account
165fn check_pool_onramp_address(
166    program_id: &Pubkey,
167    pool_address: &Pubkey,
168    check_address: &Pubkey,
169) -> Result<u8, ProgramError> {
170    check_pool_pda(
171        program_id,
172        pool_address,
173        check_address,
174        &crate::find_pool_onramp_address_and_bump,
175        "onramp account",
176        SinglePoolError::InvalidPoolOnRampAccount,
177    )
178}
179
180/// Check pool mint address for the pool account
181fn check_pool_mint_address(
182    program_id: &Pubkey,
183    pool_address: &Pubkey,
184    check_address: &Pubkey,
185) -> Result<u8, ProgramError> {
186    check_pool_pda(
187        program_id,
188        pool_address,
189        check_address,
190        &crate::find_pool_mint_address_and_bump,
191        "mint",
192        SinglePoolError::InvalidPoolMint,
193    )
194}
195
196/// Check pool stake authority address for the pool account
197fn check_pool_stake_authority_address(
198    program_id: &Pubkey,
199    pool_address: &Pubkey,
200    check_address: &Pubkey,
201) -> Result<u8, ProgramError> {
202    check_pool_pda(
203        program_id,
204        pool_address,
205        check_address,
206        &crate::find_pool_stake_authority_address_and_bump,
207        "stake authority",
208        SinglePoolError::InvalidPoolStakeAuthority,
209    )
210}
211
212/// Check pool mint authority address for the pool account
213fn check_pool_mint_authority_address(
214    program_id: &Pubkey,
215    pool_address: &Pubkey,
216    check_address: &Pubkey,
217) -> Result<u8, ProgramError> {
218    check_pool_pda(
219        program_id,
220        pool_address,
221        check_address,
222        &crate::find_pool_mint_authority_address_and_bump,
223        "mint authority",
224        SinglePoolError::InvalidPoolMintAuthority,
225    )
226}
227
228/// Check pool MPL authority address for the pool account
229fn check_pool_mpl_authority_address(
230    program_id: &Pubkey,
231    pool_address: &Pubkey,
232    check_address: &Pubkey,
233) -> Result<u8, ProgramError> {
234    check_pool_pda(
235        program_id,
236        pool_address,
237        check_address,
238        &crate::find_pool_mpl_authority_address_and_bump,
239        "MPL authority",
240        SinglePoolError::InvalidPoolMplAuthority,
241    )
242}
243
244fn check_pool_pda(
245    program_id: &Pubkey,
246    base_address: &Pubkey,
247    check_address: &Pubkey,
248    pda_lookup_fn: &dyn Fn(&Pubkey, &Pubkey) -> (Pubkey, u8),
249    pda_name: &str,
250    pool_error: SinglePoolError,
251) -> Result<u8, ProgramError> {
252    let (derived_address, bump_seed) = pda_lookup_fn(program_id, base_address);
253    if *check_address != derived_address {
254        msg!(
255            "Incorrect {} address for base {}: expected {}, received {}",
256            pda_name,
257            base_address,
258            derived_address,
259            check_address,
260        );
261        Err(pool_error.into())
262    } else {
263        Ok(bump_seed)
264    }
265}
266
267/// Check vote account is owned by the vote program and not a legacy variant
268fn check_vote_account(vote_account_info: &AccountInfo) -> Result<(), ProgramError> {
269    check_account_owner(vote_account_info, &vote_program::id())?;
270
271    let vote_account_data = &vote_account_info.try_borrow_data()?;
272    let state_variant = vote_account_data
273        .get(..VOTE_STATE_DISCRIMINATOR_END)
274        .and_then(|s| s.try_into().ok())
275        .ok_or(SinglePoolError::UnparseableVoteAccount)?;
276
277    #[allow(clippy::manual_range_patterns)]
278    match u32::from_le_bytes(state_variant) {
279        1 | 2 | 3 => Ok(()),
280        0 => Err(SinglePoolError::LegacyVoteAccount.into()),
281        _ => Err(SinglePoolError::UnparseableVoteAccount.into()),
282    }
283}
284
285/// Check pool mint address and return notional token supply.
286/// Phantom tokens exist to represent pool-locked stake in calculations.
287fn check_pool_mint_with_supply(
288    program_id: &Pubkey,
289    pool_address: &Pubkey,
290    pool_mint_info: &AccountInfo,
291) -> Result<u64, ProgramError> {
292    check_pool_mint_address(program_id, pool_address, pool_mint_info.key)?;
293    let pool_mint_data = pool_mint_info.try_borrow_data()?;
294    let pool_mint = Mint::unpack_from_slice(&pool_mint_data)?;
295    Ok(pool_mint.supply.saturating_add(PHANTOM_TOKEN_AMOUNT))
296}
297
298/// Check MPL metadata account address for the pool mint
299fn check_mpl_metadata_account_address(
300    metadata_address: &Pubkey,
301    pool_mint: &Pubkey,
302) -> Result<(), ProgramError> {
303    let (metadata_account_pubkey, _) = find_metadata_account(pool_mint);
304    if metadata_account_pubkey != *metadata_address {
305        Err(SinglePoolError::InvalidMetadataAccount.into())
306    } else {
307        Ok(())
308    }
309}
310
311/// Check system program address
312fn check_system_program(program_id: &Pubkey) -> Result<(), ProgramError> {
313    if *program_id != system_program::id() {
314        msg!(
315            "Expected system program {}, received {}",
316            system_program::id(),
317            program_id
318        );
319        Err(ProgramError::IncorrectProgramId)
320    } else {
321        Ok(())
322    }
323}
324
325/// Check token program address
326fn check_token_program(address: &Pubkey) -> Result<(), ProgramError> {
327    if *address != spl_token::id() {
328        msg!(
329            "Incorrect token program, expected {}, received {}",
330            spl_token::id(),
331            address
332        );
333        Err(ProgramError::IncorrectProgramId)
334    } else {
335        Ok(())
336    }
337}
338
339/// Check stake program address
340fn check_stake_program(program_id: &Pubkey) -> Result<(), ProgramError> {
341    if *program_id != stake::program::id() {
342        msg!(
343            "Expected stake program {}, received {}",
344            stake::program::id(),
345            program_id
346        );
347        Err(ProgramError::IncorrectProgramId)
348    } else {
349        Ok(())
350    }
351}
352
353/// Check MPL metadata program
354fn check_mpl_metadata_program(program_id: &Pubkey) -> Result<(), ProgramError> {
355    if *program_id != inline_mpl_token_metadata::id() {
356        msg!(
357            "Expected MPL metadata program {}, received {}",
358            inline_mpl_token_metadata::id(),
359            program_id
360        );
361        Err(ProgramError::IncorrectProgramId)
362    } else {
363        Ok(())
364    }
365}
366
367/// Check account owner is the given program
368fn check_account_owner(
369    account_info: &AccountInfo,
370    program_id: &Pubkey,
371) -> Result<(), ProgramError> {
372    if *program_id != *account_info.owner {
373        msg!(
374            "Expected account to be owned by program {}, received {}",
375            program_id,
376            account_info.owner
377        );
378        Err(ProgramError::IncorrectProgramId)
379    } else {
380        Ok(())
381    }
382}
383
384/// Minimum balance of delegated stake required to create a pool. We require at least
385/// 1 sol to avoid minting tokens for these lamports (locking them in the pool) since
386/// they will *become* locked after the BPF Stake 5.0.0 upgrade.
387///
388/// We also track any future (currently unplanned) minimum delegation increase, to ensure
389/// a new pool is always valid for `DelegateStake`.
390fn minimum_pool_balance() -> Result<u64, ProgramError> {
391    Ok(std::cmp::max(
392        stake::tools::get_minimum_delegation()?,
393        LAMPORTS_PER_SOL,
394    ))
395}
396
397/// Program state handler.
398pub struct Processor {}
399impl Processor {
400    #[allow(clippy::too_many_arguments)]
401    fn stake_merge<'a>(
402        pool_account_key: &Pubkey,
403        source_account: AccountInfo<'a>,
404        authority: AccountInfo<'a>,
405        bump_seed: u8,
406        destination_account: AccountInfo<'a>,
407        clock: AccountInfo<'a>,
408        stake_history: AccountInfo<'a>,
409    ) -> Result<(), ProgramError> {
410        let authority_seeds = &[
411            POOL_STAKE_AUTHORITY_PREFIX,
412            pool_account_key.as_ref(),
413            &[bump_seed],
414        ];
415        let signers = &[&authority_seeds[..]];
416
417        invoke_signed(
418            &stake::instruction::merge(destination_account.key, source_account.key, authority.key)
419                [0],
420            &[
421                destination_account,
422                source_account,
423                clock,
424                stake_history,
425                authority,
426            ],
427            signers,
428        )
429    }
430
431    fn stake_split<'a>(
432        pool_account_key: &Pubkey,
433        stake_account: AccountInfo<'a>,
434        authority: AccountInfo<'a>,
435        bump_seed: u8,
436        amount: u64,
437        split_stake: AccountInfo<'a>,
438    ) -> Result<(), ProgramError> {
439        let authority_seeds = &[
440            POOL_STAKE_AUTHORITY_PREFIX,
441            pool_account_key.as_ref(),
442            &[bump_seed],
443        ];
444        let signers = &[&authority_seeds[..]];
445
446        let split_instruction =
447            stake::instruction::split(stake_account.key, authority.key, amount, split_stake.key);
448
449        invoke_signed(
450            split_instruction.last().unwrap(),
451            &[stake_account, split_stake, authority],
452            signers,
453        )
454    }
455
456    #[allow(clippy::too_many_arguments)]
457    fn stake_authorize<'a>(
458        pool_account_key: &Pubkey,
459        stake_account: AccountInfo<'a>,
460        stake_authority: AccountInfo<'a>,
461        bump_seed: u8,
462        new_stake_authority: &Pubkey,
463        clock: AccountInfo<'a>,
464    ) -> Result<(), ProgramError> {
465        let authority_seeds = &[
466            POOL_STAKE_AUTHORITY_PREFIX,
467            pool_account_key.as_ref(),
468            &[bump_seed],
469        ];
470        let signers = &[&authority_seeds[..]];
471
472        let authorize_instruction = stake::instruction::authorize(
473            stake_account.key,
474            stake_authority.key,
475            new_stake_authority,
476            stake::state::StakeAuthorize::Staker,
477            None,
478        );
479
480        invoke_signed(
481            &authorize_instruction,
482            &[
483                stake_account.clone(),
484                clock.clone(),
485                stake_authority.clone(),
486            ],
487            signers,
488        )?;
489
490        let authorize_instruction = stake::instruction::authorize(
491            stake_account.key,
492            stake_authority.key,
493            new_stake_authority,
494            stake::state::StakeAuthorize::Withdrawer,
495            None,
496        );
497        invoke_signed(
498            &authorize_instruction,
499            &[stake_account, clock, stake_authority],
500            signers,
501        )
502    }
503
504    #[allow(clippy::too_many_arguments)]
505    fn stake_withdraw<'a>(
506        pool_account_key: &Pubkey,
507        stake_account: AccountInfo<'a>,
508        stake_authority: AccountInfo<'a>,
509        bump_seed: u8,
510        destination_account: AccountInfo<'a>,
511        clock: AccountInfo<'a>,
512        stake_history: AccountInfo<'a>,
513        lamports: u64,
514    ) -> Result<(), ProgramError> {
515        let authority_seeds = &[
516            POOL_STAKE_AUTHORITY_PREFIX,
517            pool_account_key.as_ref(),
518            &[bump_seed],
519        ];
520        let signers = &[&authority_seeds[..]];
521
522        let withdraw_instruction = stake::instruction::withdraw(
523            stake_account.key,
524            stake_authority.key,
525            destination_account.key,
526            lamports,
527            None,
528        );
529
530        invoke_signed(
531            &withdraw_instruction,
532            &[
533                stake_account,
534                destination_account,
535                clock,
536                stake_history,
537                stake_authority,
538            ],
539            signers,
540        )
541    }
542
543    #[allow(clippy::too_many_arguments)]
544    fn token_mint_to<'a>(
545        pool_account_key: &Pubkey,
546        token_program: AccountInfo<'a>,
547        mint: AccountInfo<'a>,
548        destination: AccountInfo<'a>,
549        authority: AccountInfo<'a>,
550        bump_seed: u8,
551        amount: u64,
552    ) -> Result<(), ProgramError> {
553        let authority_seeds = &[
554            POOL_MINT_AUTHORITY_PREFIX,
555            pool_account_key.as_ref(),
556            &[bump_seed],
557        ];
558        let signers = &[&authority_seeds[..]];
559
560        let ix = spl_token::instruction::mint_to(
561            token_program.key,
562            mint.key,
563            destination.key,
564            authority.key,
565            &[],
566            amount,
567        )?;
568
569        invoke_signed(&ix, &[mint, destination, authority], signers)
570    }
571
572    #[allow(clippy::too_many_arguments)]
573    fn token_burn<'a>(
574        pool_account_key: &Pubkey,
575        token_program: AccountInfo<'a>,
576        burn_account: AccountInfo<'a>,
577        mint: AccountInfo<'a>,
578        authority: AccountInfo<'a>,
579        bump_seed: u8,
580        amount: u64,
581    ) -> Result<(), ProgramError> {
582        let authority_seeds = &[
583            POOL_MINT_AUTHORITY_PREFIX,
584            pool_account_key.as_ref(),
585            &[bump_seed],
586        ];
587        let signers = &[&authority_seeds[..]];
588
589        let ix = spl_token::instruction::burn(
590            token_program.key,
591            burn_account.key,
592            mint.key,
593            authority.key,
594            &[],
595            amount,
596        )?;
597
598        invoke_signed(&ix, &[burn_account, mint, authority], signers)
599    }
600
601    fn process_initialize_pool(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
602        let account_info_iter = &mut accounts.iter();
603        let vote_account_info = next_account_info(account_info_iter)?;
604        let pool_info = next_account_info(account_info_iter)?;
605        let pool_stake_info = next_account_info(account_info_iter)?;
606        let pool_mint_info = next_account_info(account_info_iter)?;
607        let pool_stake_authority_info = next_account_info(account_info_iter)?;
608        let pool_mint_authority_info = next_account_info(account_info_iter)?;
609        let rent_info = next_account_info(account_info_iter)?;
610        let rent = &Rent::from_account_info(rent_info)?;
611        let clock_info = next_account_info(account_info_iter)?;
612        let stake_history_info = next_account_info(account_info_iter)?;
613        let stake_config_info = next_account_info(account_info_iter)?;
614        let system_program_info = next_account_info(account_info_iter)?;
615        let token_program_info = next_account_info(account_info_iter)?;
616        let stake_program_info = next_account_info(account_info_iter)?;
617
618        check_vote_account(vote_account_info)?;
619        let pool_bump_seed = check_pool_address(program_id, vote_account_info.key, pool_info.key)?;
620        let stake_bump_seed =
621            check_pool_stake_address(program_id, pool_info.key, pool_stake_info.key)?;
622        let mint_bump_seed =
623            check_pool_mint_address(program_id, pool_info.key, pool_mint_info.key)?;
624        let stake_authority_bump_seed = check_pool_stake_authority_address(
625            program_id,
626            pool_info.key,
627            pool_stake_authority_info.key,
628        )?;
629        let mint_authority_bump_seed = check_pool_mint_authority_address(
630            program_id,
631            pool_info.key,
632            pool_mint_authority_info.key,
633        )?;
634        check_system_program(system_program_info.key)?;
635        check_token_program(token_program_info.key)?;
636        check_stake_program(stake_program_info.key)?;
637
638        let pool_seeds = &[
639            POOL_PREFIX,
640            vote_account_info.key.as_ref(),
641            &[pool_bump_seed],
642        ];
643        let pool_signers = &[&pool_seeds[..]];
644
645        let stake_seeds = &[
646            POOL_STAKE_PREFIX,
647            pool_info.key.as_ref(),
648            &[stake_bump_seed],
649        ];
650        let stake_signers = &[&stake_seeds[..]];
651
652        let mint_seeds = &[POOL_MINT_PREFIX, pool_info.key.as_ref(), &[mint_bump_seed]];
653        let mint_signers = &[&mint_seeds[..]];
654
655        let stake_authority_seeds = &[
656            POOL_STAKE_AUTHORITY_PREFIX,
657            pool_info.key.as_ref(),
658            &[stake_authority_bump_seed],
659        ];
660        let stake_authority_signers = &[&stake_authority_seeds[..]];
661
662        let mint_authority_seeds = &[
663            POOL_MINT_AUTHORITY_PREFIX,
664            pool_info.key.as_ref(),
665            &[mint_authority_bump_seed],
666        ];
667        let mint_authority_signers = &[&mint_authority_seeds[..]];
668
669        // create the pool. user has already transferred in rent
670        let pool_space = SinglePool::size_of();
671        if !rent.is_exempt(pool_info.lamports(), pool_space) {
672            return Err(SinglePoolError::WrongRentAmount.into());
673        }
674        if pool_info.data_len() != 0 {
675            return Err(SinglePoolError::PoolAlreadyInitialized.into());
676        }
677
678        invoke_signed(
679            &system_instruction::allocate(pool_info.key, pool_space as u64),
680            &[pool_info.clone()],
681            pool_signers,
682        )?;
683
684        invoke_signed(
685            &system_instruction::assign(pool_info.key, program_id),
686            &[pool_info.clone()],
687            pool_signers,
688        )?;
689
690        let mut pool = try_from_slice_unchecked::<SinglePool>(&pool_info.data.borrow())?;
691        pool.account_type = SinglePoolAccountType::Pool;
692        pool.vote_account_address = *vote_account_info.key;
693        borsh::to_writer(&mut pool_info.data.borrow_mut()[..], &pool)?;
694
695        // create the pool mint. user has already transferred in rent
696        let mint_space = spl_token::state::Mint::LEN;
697
698        invoke_signed(
699            &system_instruction::allocate(pool_mint_info.key, mint_space as u64),
700            &[pool_mint_info.clone()],
701            mint_signers,
702        )?;
703
704        invoke_signed(
705            &system_instruction::assign(pool_mint_info.key, token_program_info.key),
706            &[pool_mint_info.clone()],
707            mint_signers,
708        )?;
709
710        invoke_signed(
711            &spl_token::instruction::initialize_mint2(
712                token_program_info.key,
713                pool_mint_info.key,
714                pool_mint_authority_info.key,
715                None,
716                MINT_DECIMALS,
717            )?,
718            &[pool_mint_info.clone()],
719            mint_authority_signers,
720        )?;
721
722        // create the pool stake account. user has already transferred in rent plus at
723        // least the minimum
724        let minimum_pool_balance = minimum_pool_balance()?;
725        let stake_space = StakeStateV2::size_of();
726        let stake_rent_plus_initial = rent
727            .minimum_balance(stake_space)
728            .saturating_add(minimum_pool_balance);
729
730        if pool_stake_info.lamports() < stake_rent_plus_initial {
731            return Err(SinglePoolError::WrongRentAmount.into());
732        }
733
734        let authorized = stake::state::Authorized::auto(pool_stake_authority_info.key);
735
736        invoke_signed(
737            &system_instruction::allocate(pool_stake_info.key, stake_space as u64),
738            &[pool_stake_info.clone()],
739            stake_signers,
740        )?;
741
742        invoke_signed(
743            &system_instruction::assign(pool_stake_info.key, stake_program_info.key),
744            &[pool_stake_info.clone()],
745            stake_signers,
746        )?;
747
748        invoke_signed(
749            &stake::instruction::initialize_checked(pool_stake_info.key, &authorized),
750            &[
751                pool_stake_info.clone(),
752                rent_info.clone(),
753                pool_stake_authority_info.clone(),
754                pool_stake_authority_info.clone(),
755            ],
756            stake_authority_signers,
757        )?;
758
759        // delegate stake so it activates
760        invoke_signed(
761            &stake::instruction::delegate_stake(
762                pool_stake_info.key,
763                pool_stake_authority_info.key,
764                vote_account_info.key,
765            ),
766            &[
767                pool_stake_info.clone(),
768                vote_account_info.clone(),
769                clock_info.clone(),
770                stake_history_info.clone(),
771                stake_config_info.clone(),
772                pool_stake_authority_info.clone(),
773            ],
774            stake_authority_signers,
775        )?;
776
777        Ok(())
778    }
779
780    fn process_replenish_pool(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
781        let account_info_iter = &mut accounts.iter();
782        let vote_account_info = next_account_info(account_info_iter)?;
783        let pool_info = next_account_info(account_info_iter)?;
784        let pool_stake_info = next_account_info(account_info_iter)?;
785        let pool_onramp_info = next_account_info(account_info_iter)?;
786        let pool_stake_authority_info = next_account_info(account_info_iter)?;
787        let clock_info = next_account_info(account_info_iter)?;
788        let clock = &Clock::from_account_info(clock_info)?;
789        let stake_history_info = next_account_info(account_info_iter)?;
790        let stake_config_info = next_account_info(account_info_iter)?;
791        let stake_program_info = next_account_info(account_info_iter)?;
792
793        let rent = Rent::get()?;
794        let stake_history = &StakeHistorySysvar(clock.epoch);
795
796        check_vote_account(vote_account_info)?;
797        check_pool_address(program_id, vote_account_info.key, pool_info.key)?;
798
799        SinglePool::from_account_info(pool_info, program_id)?;
800
801        check_pool_stake_address(program_id, pool_info.key, pool_stake_info.key)?;
802        check_pool_onramp_address(program_id, pool_info.key, pool_onramp_info.key)?;
803        let stake_authority_bump_seed = check_pool_stake_authority_address(
804            program_id,
805            pool_info.key,
806            pool_stake_authority_info.key,
807        )?;
808        check_stake_program(stake_program_info.key)?;
809
810        let minimum_delegation = stake::tools::get_minimum_delegation()?;
811
812        // we expect these numbers to be equal but get them separately in case of future changes
813        let pool_rent_exempt_reserve = rent.minimum_balance(pool_stake_info.data_len());
814        let onramp_rent_exempt_reserve = rent.minimum_balance(pool_onramp_info.data_len());
815
816        // get main pool account, we require it to be fully active for most operations
817        let (_, pool_stake_state) = get_stake_state(pool_stake_info)?;
818        let pool_stake_status = pool_stake_state
819            .delegation
820            .stake_activating_and_deactivating(
821                clock.epoch,
822                stake_history,
823                PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
824            );
825        let pool_stake_is_fully_active = is_stake_fully_active(&pool_stake_status);
826
827        // get on-ramp and its status. we have to match because unlike the main account it could be Initialized
828        // if it doesnt exist, it must first be created with InitializePoolOnRamp
829        let (option_onramp_status, onramp_deactivation_epoch) =
830            match deserialize_stake(pool_onramp_info) {
831                Ok(StakeStateV2::Initialized(_)) => (None, u64::MAX),
832                Ok(StakeStateV2::Stake(_, stake, _)) => (
833                    Some(stake.delegation.stake_activating_and_deactivating(
834                        clock.epoch,
835                        stake_history,
836                        PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
837                    )),
838                    stake.delegation.deactivation_epoch,
839                ),
840                _ => return Err(SinglePoolError::OnRampDoesntExist.into()),
841            };
842
843        let stake_authority_seeds = &[
844            POOL_STAKE_AUTHORITY_PREFIX,
845            pool_info.key.as_ref(),
846            &[stake_authority_bump_seed],
847        ];
848        let stake_authority_signers = &[&stake_authority_seeds[..]];
849
850        // if pool stake is deactivating this epoch, or has fully deactivated, delegate it
851        // this may happen as a result of `DeactivateDelinquent`
852        if pool_stake_state.delegation.deactivation_epoch == clock.epoch
853            || (pool_stake_state.delegation.deactivation_epoch < clock.epoch
854                && pool_stake_status.effective == 0)
855        {
856            invoke_signed(
857                &stake::instruction::delegate_stake(
858                    pool_stake_info.key,
859                    pool_stake_authority_info.key,
860                    vote_account_info.key,
861                ),
862                &[
863                    pool_stake_info.clone(),
864                    vote_account_info.clone(),
865                    clock_info.clone(),
866                    stake_history_info.clone(),
867                    stake_config_info.clone(),
868                    pool_stake_authority_info.clone(),
869                ],
870                stake_authority_signers,
871            )?;
872        }
873
874        // if pool is fully active, we can move stake to the main account and lamports to the on-ramp
875        if pool_stake_is_fully_active {
876            // determine excess lamports in the main account before we touch either of them
877            let pool_excess_lamports = pool_stake_info
878                .lamports()
879                .saturating_sub(pool_stake_state.delegation.stake)
880                .saturating_sub(pool_rent_exempt_reserve);
881
882            // if the on-ramp is fully active, move its stake to the main pool account
883            if let Some(ref onramp_status) = option_onramp_status {
884                if is_stake_fully_active(onramp_status) {
885                    invoke_signed(
886                        &stake::instruction::move_stake(
887                            pool_onramp_info.key,
888                            pool_stake_info.key,
889                            pool_stake_authority_info.key,
890                            onramp_status.effective,
891                        ),
892                        &[
893                            pool_onramp_info.clone(),
894                            pool_stake_info.clone(),
895                            pool_stake_authority_info.clone(),
896                        ],
897                        stake_authority_signers,
898                    )?;
899                }
900            }
901
902            // if there are any excess lamports to move to the on-ramp, move them
903            if pool_excess_lamports > 0 {
904                invoke_signed(
905                    &stake::instruction::move_lamports(
906                        pool_stake_info.key,
907                        pool_onramp_info.key,
908                        pool_stake_authority_info.key,
909                        pool_excess_lamports,
910                    ),
911                    &[
912                        pool_stake_info.clone(),
913                        pool_onramp_info.clone(),
914                        pool_stake_authority_info.clone(),
915                    ],
916                    stake_authority_signers,
917                )?;
918            }
919
920            // finally, delegate the on-ramp account if it has sufficient undelegated lamports
921            // if activating, this means more lamports than the current activating delegation
922            // in all cases, this means having enough to cover the minimum delegation
923            // we do nothing if partially active. we know it cannot be fully active because of MoveStake
924            let onramp_non_rent_lamports = pool_onramp_info
925                .lamports()
926                .saturating_sub(onramp_rent_exempt_reserve);
927            let must_delegate_onramp = match option_onramp_status.unwrap_or_default() {
928                // activating
929                StakeActivationStatus {
930                    effective: 0,
931                    activating,
932                    deactivating: 0,
933                } if activating > 0 => {
934                    onramp_non_rent_lamports >= minimum_delegation
935                        && onramp_non_rent_lamports > activating
936                }
937                // inactive, or deactivating this epoch due to DeactivateDelinquent
938                // effective may be nonzero here because we are using the status prior to MoveStake
939                StakeActivationStatus {
940                    effective: _,
941                    activating: 0,
942                    deactivating,
943                } if deactivating == 0 || onramp_deactivation_epoch == clock.epoch => {
944                    onramp_non_rent_lamports >= minimum_delegation
945                }
946                // partially active, partially inactive, or some state beyond mortal reckoning
947                _ => false,
948            };
949
950            if must_delegate_onramp {
951                invoke_signed(
952                    &stake::instruction::delegate_stake(
953                        pool_onramp_info.key,
954                        pool_stake_authority_info.key,
955                        vote_account_info.key,
956                    ),
957                    &[
958                        pool_onramp_info.clone(),
959                        vote_account_info.clone(),
960                        clock_info.clone(),
961                        stake_history_info.clone(),
962                        stake_config_info.clone(),
963                        pool_stake_authority_info.clone(),
964                    ],
965                    stake_authority_signers,
966                )?;
967            }
968        }
969
970        Ok(())
971    }
972
973    fn process_deposit_stake(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
974        let account_info_iter = &mut accounts.iter();
975        let pool_info = next_account_info(account_info_iter)?;
976        let pool_stake_info = next_account_info(account_info_iter)?;
977        let pool_onramp_info = next_account_info(account_info_iter)?;
978        let pool_mint_info = next_account_info(account_info_iter)?;
979        let pool_stake_authority_info = next_account_info(account_info_iter)?;
980        let pool_mint_authority_info = next_account_info(account_info_iter)?;
981        let user_stake_info = next_account_info(account_info_iter)?;
982        let user_token_account_info = next_account_info(account_info_iter)?;
983        let user_lamport_account_info = next_account_info(account_info_iter)?;
984        let clock_info = next_account_info(account_info_iter)?;
985        let clock = &Clock::from_account_info(clock_info)?;
986        let stake_history_info = next_account_info(account_info_iter)?;
987        let token_program_info = next_account_info(account_info_iter)?;
988        let stake_program_info = next_account_info(account_info_iter)?;
989
990        let rent = &Rent::get()?;
991        let stake_history = &StakeHistorySysvar(clock.epoch);
992
993        SinglePool::from_account_info(pool_info, program_id)?;
994
995        check_pool_stake_address(program_id, pool_info.key, pool_stake_info.key)?;
996        check_pool_onramp_address(program_id, pool_info.key, pool_onramp_info.key)?;
997        let token_supply = check_pool_mint_with_supply(program_id, pool_info.key, pool_mint_info)?;
998        let stake_authority_bump_seed = check_pool_stake_authority_address(
999            program_id,
1000            pool_info.key,
1001            pool_stake_authority_info.key,
1002        )?;
1003        let mint_authority_bump_seed = check_pool_mint_authority_address(
1004            program_id,
1005            pool_info.key,
1006            pool_mint_authority_info.key,
1007        )?;
1008        check_token_program(token_program_info.key)?;
1009        check_stake_program(stake_program_info.key)?;
1010
1011        if pool_stake_info.key == user_stake_info.key {
1012            return Err(SinglePoolError::InvalidPoolStakeAccountUsage.into());
1013        }
1014
1015        if pool_onramp_info.key == user_stake_info.key {
1016            return Err(SinglePoolError::InvalidPoolStakeAccountUsage.into());
1017        }
1018
1019        let (pre_pool_stake, pool_is_active, pool_is_activating) = {
1020            let (_, pool_stake_state) = get_stake_state(pool_stake_info)?;
1021            let pool_stake_status = pool_stake_state
1022                .delegation
1023                .stake_activating_and_deactivating(
1024                    clock.epoch,
1025                    stake_history,
1026                    PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
1027                );
1028
1029            (
1030                pool_stake_state.delegation.stake,
1031                is_stake_fully_active(&pool_stake_status),
1032                is_stake_newly_activating(&pool_stake_status),
1033            )
1034        };
1035
1036        // if pool is inactive or deactivating, it does not accept deposits.
1037        // a user must call `ReplenishPool` to reactivate it. this condition is exceptional:
1038        // in practice, it can only happen if the vote account is delinquent. under normal operation,
1039        // a new pool is activating for one epoch then active forevermore
1040        //
1041        // this branch would also be hit for a pool in warmup/cooldown, but this should never happen,
1042        // because it would require cluster warmup/cooldown *and* a first-epoch pool or delinquent validator.
1043        // a `ReplenishRequired` error in that case is misleading, but it is still properly an error
1044        if !pool_is_active && !pool_is_activating {
1045            return Err(SinglePoolError::ReplenishRequired.into());
1046        } else if pool_is_active && pool_is_activating {
1047            // this is impossible, but assert since we assume `pool_is_active == !pool_is_activating` later
1048            unreachable!();
1049        };
1050
1051        // tokens for deposit are determined off the total stakeable value of both pool-owned accounts
1052        let pre_total_nev = pool_net_asset_value(pool_stake_info, pool_onramp_info, rent);
1053
1054        let pre_user_lamports = user_stake_info.lamports();
1055        let (user_stake_meta, user_stake_status) = match deserialize_stake(user_stake_info) {
1056            Ok(StakeStateV2::Stake(meta, stake, _)) => (
1057                meta,
1058                stake.delegation.stake_activating_and_deactivating(
1059                    clock.epoch,
1060                    stake_history,
1061                    PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
1062                ),
1063            ),
1064            Ok(StakeStateV2::Initialized(meta)) => (meta, StakeActivationStatus::default()),
1065            _ => return Err(SinglePoolError::WrongStakeState.into()),
1066        };
1067
1068        // user must have set authority to pool and have no lockup for merge to succeed
1069        if user_stake_meta.authorized
1070            != stake::state::Authorized::auto(pool_stake_authority_info.key)
1071            || user_stake_meta.lockup.is_in_force(clock, None)
1072        {
1073            return Err(SinglePoolError::WrongStakeState.into());
1074        }
1075
1076        // user can deposit active stake into an active pool, or activating or inactive stake into an activating pool
1077        if pool_is_active && is_stake_fully_active(&user_stake_status) {
1078            // ok: active <- active
1079        } else if pool_is_activating && is_stake_newly_activating(&user_stake_status) {
1080            // ok: activating <- activating
1081        } else if pool_is_activating && user_stake_status == StakeActivationStatus::default() {
1082            // ok: activating <- inactive
1083        } else {
1084            // all other transitions are disallowed
1085            return Err(SinglePoolError::WrongStakeState.into());
1086        }
1087
1088        // merge the user stake account, which is preauthed to us, into the pool stake account
1089        Self::stake_merge(
1090            pool_info.key,
1091            user_stake_info.clone(),
1092            pool_stake_authority_info.clone(),
1093            stake_authority_bump_seed,
1094            pool_stake_info.clone(),
1095            clock_info.clone(),
1096            stake_history_info.clone(),
1097        )?;
1098
1099        // determine new stake lamports added by merge
1100        let post_pool_stake = get_stake_amount(pool_stake_info)?;
1101        let new_stake_added = post_pool_stake
1102            .checked_sub(pre_pool_stake)
1103            .ok_or(SinglePoolError::ArithmeticOverflow)?;
1104
1105        // return user lamports that were not added to stake
1106        let user_excess_lamports = pre_user_lamports
1107            .checked_sub(new_stake_added)
1108            .ok_or(SinglePoolError::ArithmeticOverflow)?;
1109
1110        // sanity check: the user stake account is empty
1111        if user_stake_info.lamports() != 0 {
1112            return Err(SinglePoolError::UnexpectedMathError.into());
1113        }
1114
1115        // deposit amount is determined off stake added because we return excess lamports
1116        let new_pool_tokens =
1117            calculate_deposit_amount(token_supply, pre_total_nev, new_stake_added)
1118                .ok_or(SinglePoolError::UnexpectedMathError)?;
1119
1120        if new_pool_tokens == 0 {
1121            return Err(SinglePoolError::DepositTooSmall.into());
1122        }
1123
1124        // mint tokens to the user corresponding to their stake deposit
1125        Self::token_mint_to(
1126            pool_info.key,
1127            token_program_info.clone(),
1128            pool_mint_info.clone(),
1129            user_token_account_info.clone(),
1130            pool_mint_authority_info.clone(),
1131            mint_authority_bump_seed,
1132            new_pool_tokens,
1133        )?;
1134
1135        // return any unstaked lamports the user stake account merged in
1136        if user_excess_lamports > 0 {
1137            Self::stake_withdraw(
1138                pool_info.key,
1139                pool_stake_info.clone(),
1140                pool_stake_authority_info.clone(),
1141                stake_authority_bump_seed,
1142                user_lamport_account_info.clone(),
1143                clock_info.clone(),
1144                stake_history_info.clone(),
1145                user_excess_lamports,
1146            )?;
1147        }
1148
1149        Ok(())
1150    }
1151
1152    fn process_withdraw_stake(
1153        program_id: &Pubkey,
1154        accounts: &[AccountInfo],
1155        user_stake_authority: &Pubkey,
1156        token_amount: u64,
1157    ) -> ProgramResult {
1158        let account_info_iter = &mut accounts.iter();
1159        let pool_info = next_account_info(account_info_iter)?;
1160        let pool_stake_info = next_account_info(account_info_iter)?;
1161        let pool_onramp_info = next_account_info(account_info_iter)?;
1162        let pool_mint_info = next_account_info(account_info_iter)?;
1163        let pool_stake_authority_info = next_account_info(account_info_iter)?;
1164        let pool_mint_authority_info = next_account_info(account_info_iter)?;
1165        let user_stake_info = next_account_info(account_info_iter)?;
1166        let user_token_account_info = next_account_info(account_info_iter)?;
1167        let clock_info = next_account_info(account_info_iter)?;
1168        let clock = &Clock::from_account_info(clock_info)?;
1169        let token_program_info = next_account_info(account_info_iter)?;
1170        let stake_program_info = next_account_info(account_info_iter)?;
1171
1172        let rent = &Rent::get()?;
1173        let stake_history = &StakeHistorySysvar(clock.epoch);
1174
1175        SinglePool::from_account_info(pool_info, program_id)?;
1176
1177        check_pool_stake_address(program_id, pool_info.key, pool_stake_info.key)?;
1178        check_pool_onramp_address(program_id, pool_info.key, pool_onramp_info.key)?;
1179        let token_supply = check_pool_mint_with_supply(program_id, pool_info.key, pool_mint_info)?;
1180        let stake_authority_bump_seed = check_pool_stake_authority_address(
1181            program_id,
1182            pool_info.key,
1183            pool_stake_authority_info.key,
1184        )?;
1185        let mint_authority_bump_seed = check_pool_mint_authority_address(
1186            program_id,
1187            pool_info.key,
1188            pool_mint_authority_info.key,
1189        )?;
1190        check_token_program(token_program_info.key)?;
1191        check_stake_program(stake_program_info.key)?;
1192
1193        if pool_stake_info.key == user_stake_info.key {
1194            return Err(SinglePoolError::InvalidPoolStakeAccountUsage.into());
1195        }
1196
1197        if pool_onramp_info.key == user_stake_info.key {
1198            return Err(SinglePoolError::InvalidPoolStakeAccountUsage.into());
1199        }
1200
1201        if token_amount == 0 {
1202            return Err(SinglePoolError::WithdrawalTooSmall.into());
1203        }
1204
1205        let minimum_delegation = stake::tools::get_minimum_delegation()?;
1206
1207        // tokens for withdraw are determined off the total stakeable value of both pool-owned accounts
1208        let pre_total_nev = pool_net_asset_value(pool_stake_info, pool_onramp_info, rent);
1209
1210        // note we deliberately do NOT validate the activation status of the pool account.
1211        // neither warmup/cooldown nor validator delinquency prevent a user withdrawal.
1212        // however, because we calculate NEV from all lamports in both pool accounts,
1213        // but can only split stake from the main account (unless inactive), we must determine whether this is possible
1214        let (withdrawable_value, pool_is_fully_inactive) = {
1215            let (_, pool_stake_state) = get_stake_state(pool_stake_info)?;
1216            let pool_stake_status = pool_stake_state
1217                .delegation
1218                .stake_activating_and_deactivating(
1219                    clock.epoch,
1220                    stake_history,
1221                    PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
1222                );
1223
1224            // if fully inactive, we split on lamports; otherwise, on all delegation.
1225            // the stake program works off delegation in this way *even* for a partially deactivated stake
1226            if pool_stake_status == StakeActivationStatus::default() {
1227                (
1228                    pool_stake_info
1229                        .lamports()
1230                        .saturating_sub(rent.minimum_balance(pool_stake_info.data_len())),
1231                    true,
1232                )
1233            } else {
1234                (pool_stake_state.delegation.stake, false)
1235            }
1236        };
1237
1238        // withdraw amount is determined off pool NEV just like deposit amount
1239        let stake_to_withdraw =
1240            calculate_withdraw_amount(token_supply, pre_total_nev, token_amount)
1241                .ok_or(SinglePoolError::UnexpectedMathError)?;
1242
1243        // self-explanatory. we catch 0 deposit above so we only hit this if we rounded to 0
1244        if stake_to_withdraw == 0 {
1245            return Err(SinglePoolError::WithdrawalTooSmall.into());
1246        }
1247
1248        // the pool must *always* meet minimum delegation, even if it is inactive.
1249        // this error is currently impossible to hit and exists to protect pools if minimum delegation rises above 1sol
1250        if withdrawable_value.saturating_sub(stake_to_withdraw) < minimum_delegation {
1251            return Err(SinglePoolError::WithdrawalViolatesPoolRequirements.into());
1252        }
1253
1254        // this is impossible but we guard explicitly because it would put the pool in an unrecoverable state
1255        if stake_to_withdraw == pool_stake_info.lamports() {
1256            return Err(SinglePoolError::WithdrawalViolatesPoolRequirements.into());
1257        }
1258
1259        // if the destination would be in any non-inactive state it must meet minimum delegation
1260        if !pool_is_fully_inactive && stake_to_withdraw < minimum_delegation {
1261            return Err(SinglePoolError::WithdrawalTooSmall.into());
1262        }
1263
1264        // if we do not have enough value to service this withdrawal, the user must wait a `ReplenishPool` cycle.
1265        // this does *not* mean the value isnt in the pool, merely that it is not duly splittable.
1266        // this check should always come last to avoid returning it if the withdrawal is actually invalid
1267        if stake_to_withdraw > withdrawable_value {
1268            return Err(SinglePoolError::WithdrawalTooLarge.into());
1269        }
1270
1271        // burn user tokens corresponding to the amount of stake they wish to withdraw
1272        Self::token_burn(
1273            pool_info.key,
1274            token_program_info.clone(),
1275            user_token_account_info.clone(),
1276            pool_mint_info.clone(),
1277            pool_mint_authority_info.clone(),
1278            mint_authority_bump_seed,
1279            token_amount,
1280        )?;
1281
1282        // split stake into a blank stake account the user has created for this purpose
1283        Self::stake_split(
1284            pool_info.key,
1285            pool_stake_info.clone(),
1286            pool_stake_authority_info.clone(),
1287            stake_authority_bump_seed,
1288            stake_to_withdraw,
1289            user_stake_info.clone(),
1290        )?;
1291
1292        // assign both authorities on the new stake account to the user
1293        Self::stake_authorize(
1294            pool_info.key,
1295            user_stake_info.clone(),
1296            pool_stake_authority_info.clone(),
1297            stake_authority_bump_seed,
1298            user_stake_authority,
1299            clock_info.clone(),
1300        )?;
1301
1302        Ok(())
1303    }
1304
1305    fn process_create_pool_token_metadata(
1306        program_id: &Pubkey,
1307        accounts: &[AccountInfo],
1308    ) -> ProgramResult {
1309        let account_info_iter = &mut accounts.iter();
1310        let pool_info = next_account_info(account_info_iter)?;
1311        let pool_mint_info = next_account_info(account_info_iter)?;
1312        let pool_mint_authority_info = next_account_info(account_info_iter)?;
1313        let pool_mpl_authority_info = next_account_info(account_info_iter)?;
1314        let payer_info = next_account_info(account_info_iter)?;
1315        let metadata_info = next_account_info(account_info_iter)?;
1316        let mpl_token_metadata_program_info = next_account_info(account_info_iter)?;
1317        let system_program_info = next_account_info(account_info_iter)?;
1318
1319        let pool = SinglePool::from_account_info(pool_info, program_id)?;
1320
1321        let mint_authority_bump_seed = check_pool_mint_authority_address(
1322            program_id,
1323            pool_info.key,
1324            pool_mint_authority_info.key,
1325        )?;
1326        let mpl_authority_bump_seed = check_pool_mpl_authority_address(
1327            program_id,
1328            pool_info.key,
1329            pool_mpl_authority_info.key,
1330        )?;
1331        check_pool_mint_address(program_id, pool_info.key, pool_mint_info.key)?;
1332        check_system_program(system_program_info.key)?;
1333        check_account_owner(payer_info, &system_program::id())?;
1334        check_mpl_metadata_program(mpl_token_metadata_program_info.key)?;
1335        check_mpl_metadata_account_address(metadata_info.key, pool_mint_info.key)?;
1336
1337        if !payer_info.is_signer {
1338            msg!("Payer did not sign metadata creation");
1339            return Err(SinglePoolError::SignatureMissing.into());
1340        }
1341
1342        let vote_address_str = pool.vote_account_address.to_string();
1343        let token_name = format!("SPL Single Pool {}", &vote_address_str[0..15]);
1344        let token_symbol = format!("st{}", &vote_address_str[0..7]);
1345
1346        let new_metadata_instruction = create_metadata_accounts_v3(
1347            *mpl_token_metadata_program_info.key,
1348            *metadata_info.key,
1349            *pool_mint_info.key,
1350            *pool_mint_authority_info.key,
1351            *payer_info.key,
1352            *pool_mpl_authority_info.key,
1353            token_name,
1354            token_symbol,
1355            "".to_string(),
1356        );
1357
1358        let mint_authority_seeds = &[
1359            POOL_MINT_AUTHORITY_PREFIX,
1360            pool_info.key.as_ref(),
1361            &[mint_authority_bump_seed],
1362        ];
1363        let mpl_authority_seeds = &[
1364            POOL_MPL_AUTHORITY_PREFIX,
1365            pool_info.key.as_ref(),
1366            &[mpl_authority_bump_seed],
1367        ];
1368        let signers = &[&mint_authority_seeds[..], &mpl_authority_seeds[..]];
1369
1370        invoke_signed(
1371            &new_metadata_instruction,
1372            &[
1373                metadata_info.clone(),
1374                pool_mint_info.clone(),
1375                pool_mint_authority_info.clone(),
1376                payer_info.clone(),
1377                pool_mpl_authority_info.clone(),
1378                system_program_info.clone(),
1379            ],
1380            signers,
1381        )?;
1382
1383        Ok(())
1384    }
1385
1386    fn process_update_pool_token_metadata(
1387        program_id: &Pubkey,
1388        accounts: &[AccountInfo],
1389        name: String,
1390        symbol: String,
1391        uri: String,
1392    ) -> ProgramResult {
1393        let account_info_iter = &mut accounts.iter();
1394        let vote_account_info = next_account_info(account_info_iter)?;
1395        let pool_info = next_account_info(account_info_iter)?;
1396        let pool_mpl_authority_info = next_account_info(account_info_iter)?;
1397        let authorized_withdrawer_info = next_account_info(account_info_iter)?;
1398        let metadata_info = next_account_info(account_info_iter)?;
1399        let mpl_token_metadata_program_info = next_account_info(account_info_iter)?;
1400
1401        check_vote_account(vote_account_info)?;
1402        check_pool_address(program_id, vote_account_info.key, pool_info.key)?;
1403
1404        let pool = SinglePool::from_account_info(pool_info, program_id)?;
1405        if pool.vote_account_address != *vote_account_info.key {
1406            return Err(SinglePoolError::InvalidPoolAccount.into());
1407        }
1408
1409        let mpl_authority_bump_seed = check_pool_mpl_authority_address(
1410            program_id,
1411            pool_info.key,
1412            pool_mpl_authority_info.key,
1413        )?;
1414        let pool_mint_address = crate::find_pool_mint_address(program_id, pool_info.key);
1415        check_mpl_metadata_program(mpl_token_metadata_program_info.key)?;
1416        check_mpl_metadata_account_address(metadata_info.key, &pool_mint_address)?;
1417
1418        // we use authorized_withdrawer to authenticate the caller controls the vote
1419        // account this is safer than using an authorized_voter since those keys
1420        // live hot and validator-operators we spoke with indicated this would
1421        // be their preference as well
1422        let vote_account_data = &vote_account_info.try_borrow_data()?;
1423        let vote_account_withdrawer = vote_account_data
1424            .get(VOTE_STATE_AUTHORIZED_WITHDRAWER_START..VOTE_STATE_AUTHORIZED_WITHDRAWER_END)
1425            .and_then(|x| Pubkey::try_from(x).ok())
1426            .ok_or(SinglePoolError::UnparseableVoteAccount)?;
1427
1428        if *authorized_withdrawer_info.key != vote_account_withdrawer {
1429            msg!("Vote account authorized withdrawer does not match the account provided.");
1430            return Err(SinglePoolError::InvalidMetadataSigner.into());
1431        }
1432
1433        if !authorized_withdrawer_info.is_signer {
1434            msg!("Vote account authorized withdrawer did not sign metadata update.");
1435            return Err(SinglePoolError::SignatureMissing.into());
1436        }
1437
1438        let update_metadata_accounts_instruction = update_metadata_accounts_v2(
1439            *mpl_token_metadata_program_info.key,
1440            *metadata_info.key,
1441            *pool_mpl_authority_info.key,
1442            None,
1443            Some(DataV2 {
1444                name,
1445                symbol,
1446                uri,
1447                seller_fee_basis_points: 0,
1448                creators: None,
1449                collection: None,
1450                uses: None,
1451            }),
1452            None,
1453            Some(true),
1454        );
1455
1456        let mpl_authority_seeds = &[
1457            POOL_MPL_AUTHORITY_PREFIX,
1458            pool_info.key.as_ref(),
1459            &[mpl_authority_bump_seed],
1460        ];
1461        let signers = &[&mpl_authority_seeds[..]];
1462
1463        invoke_signed(
1464            &update_metadata_accounts_instruction,
1465            &[metadata_info.clone(), pool_mpl_authority_info.clone()],
1466            signers,
1467        )?;
1468
1469        Ok(())
1470    }
1471
1472    fn process_initialize_pool_onramp(
1473        program_id: &Pubkey,
1474        accounts: &[AccountInfo],
1475    ) -> ProgramResult {
1476        let account_info_iter = &mut accounts.iter();
1477        let pool_info = next_account_info(account_info_iter)?;
1478        let pool_onramp_info = next_account_info(account_info_iter)?;
1479        let pool_stake_authority_info = next_account_info(account_info_iter)?;
1480        let rent_info = next_account_info(account_info_iter)?;
1481        let rent = &Rent::from_account_info(rent_info)?;
1482        let system_program_info = next_account_info(account_info_iter)?;
1483        let stake_program_info = next_account_info(account_info_iter)?;
1484
1485        SinglePool::from_account_info(pool_info, program_id)?;
1486
1487        let onramp_bump_seed =
1488            check_pool_onramp_address(program_id, pool_info.key, pool_onramp_info.key)?;
1489        let stake_authority_bump_seed = check_pool_stake_authority_address(
1490            program_id,
1491            pool_info.key,
1492            pool_stake_authority_info.key,
1493        )?;
1494        check_system_program(system_program_info.key)?;
1495        check_stake_program(stake_program_info.key)?;
1496
1497        let onramp_seeds = &[
1498            POOL_ONRAMP_PREFIX,
1499            pool_info.key.as_ref(),
1500            &[onramp_bump_seed],
1501        ];
1502        let onramp_signers = &[&onramp_seeds[..]];
1503
1504        let stake_authority_seeds = &[
1505            POOL_STAKE_AUTHORITY_PREFIX,
1506            pool_info.key.as_ref(),
1507            &[stake_authority_bump_seed],
1508        ];
1509        let stake_authority_signers = &[&stake_authority_seeds[..]];
1510
1511        // create the pool on-ramp account. user has already transferred in rent
1512        let stake_space = StakeStateV2::size_of();
1513        let stake_rent = rent.minimum_balance(stake_space);
1514
1515        if pool_onramp_info.lamports() < stake_rent {
1516            return Err(SinglePoolError::WrongRentAmount.into());
1517        }
1518
1519        let authorized = stake::state::Authorized::auto(pool_stake_authority_info.key);
1520
1521        invoke_signed(
1522            &system_instruction::allocate(pool_onramp_info.key, stake_space as u64),
1523            &[pool_onramp_info.clone()],
1524            onramp_signers,
1525        )?;
1526
1527        invoke_signed(
1528            &system_instruction::assign(pool_onramp_info.key, stake_program_info.key),
1529            &[pool_onramp_info.clone()],
1530            onramp_signers,
1531        )?;
1532
1533        invoke_signed(
1534            &stake::instruction::initialize_checked(pool_onramp_info.key, &authorized),
1535            &[
1536                pool_onramp_info.clone(),
1537                rent_info.clone(),
1538                pool_stake_authority_info.clone(),
1539                pool_stake_authority_info.clone(),
1540            ],
1541            stake_authority_signers,
1542        )?;
1543
1544        Ok(())
1545    }
1546
1547    /// Processes [Instruction](enum.Instruction.html).
1548    pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
1549        let instruction = SinglePoolInstruction::try_from_slice(input)?;
1550        match instruction {
1551            SinglePoolInstruction::InitializePool => {
1552                msg!("Instruction: InitializePool");
1553                Self::process_initialize_pool(program_id, accounts)
1554            }
1555            SinglePoolInstruction::ReplenishPool => {
1556                msg!("Instruction: ReplenishPool");
1557                Self::process_replenish_pool(program_id, accounts)
1558            }
1559            SinglePoolInstruction::DepositStake => {
1560                msg!("Instruction: DepositStake");
1561                Self::process_deposit_stake(program_id, accounts)
1562            }
1563            SinglePoolInstruction::WithdrawStake {
1564                user_stake_authority,
1565                token_amount,
1566            } => {
1567                msg!("Instruction: WithdrawStake");
1568                Self::process_withdraw_stake(
1569                    program_id,
1570                    accounts,
1571                    &user_stake_authority,
1572                    token_amount,
1573                )
1574            }
1575            SinglePoolInstruction::CreateTokenMetadata => {
1576                msg!("Instruction: CreateTokenMetadata");
1577                Self::process_create_pool_token_metadata(program_id, accounts)
1578            }
1579            SinglePoolInstruction::UpdateTokenMetadata { name, symbol, uri } => {
1580                msg!("Instruction: UpdateTokenMetadata");
1581                Self::process_update_pool_token_metadata(program_id, accounts, name, symbol, uri)
1582            }
1583            SinglePoolInstruction::InitializePoolOnRamp => {
1584                msg!("Instruction: InitializePoolOnRamp");
1585                Self::process_initialize_pool_onramp(program_id, accounts)
1586            }
1587            SinglePoolInstruction::DepositSol { lamports: _ } => {
1588                msg!("Instruction: DepositSol (NOT IMPLEMENTED)");
1589                Err(ProgramError::InvalidInstructionData)
1590            }
1591        }
1592    }
1593}
1594
1595#[cfg(test)]
1596#[allow(clippy::arithmetic_side_effects)]
1597mod tests {
1598    use {
1599        super::*,
1600        approx::assert_relative_eq,
1601        rand::{
1602            distr::{Distribution, Uniform},
1603            rngs::StdRng,
1604            seq::IteratorRandom,
1605            Rng, SeedableRng,
1606        },
1607        std::collections::BTreeMap,
1608        test_case::test_case,
1609    };
1610
1611    // approximately 6%/yr assuming 146 epochs
1612    const INFLATION_BASE_RATE: f64 = 0.0004;
1613
1614    #[derive(Clone, Debug, Default)]
1615    struct PoolState {
1616        pub token_supply: u64,
1617        pub total_stake: u64,
1618        pub user_token_balances: BTreeMap<Pubkey, u64>,
1619    }
1620    impl PoolState {
1621        // deposits a given amount of stake and returns the equivalent tokens on success
1622        // note this is written as unsugared do-notation, so *any* failure returns None
1623        // otherwise returns the value produced by its respective calculate function
1624        #[rustfmt::skip]
1625        pub fn deposit(&mut self, user_pubkey: &Pubkey, stake_to_deposit: u64) -> Option<u64> {
1626            calculate_deposit_amount(self.token_supply, self.total_stake, stake_to_deposit)
1627                .and_then(|tokens_to_mint| self.token_supply.checked_add(tokens_to_mint)
1628                .and_then(|new_token_supply| self.total_stake.checked_add(stake_to_deposit)
1629                .and_then(|new_total_stake| self.user_token_balances.remove(user_pubkey).or(Some(0))
1630                .and_then(|old_user_token_balance| old_user_token_balance.checked_add(tokens_to_mint)
1631                .map(|new_user_token_balance| {
1632                    self.token_supply = new_token_supply;
1633                    self.total_stake = new_total_stake;
1634                    let _ = self.user_token_balances.insert(*user_pubkey, new_user_token_balance);
1635                    tokens_to_mint
1636            })))))
1637        }
1638
1639        // burns a given amount of tokens and returns the equivalent stake on success
1640        // note this is written as unsugared do-notation, so *any* failure returns None
1641        // otherwise returns the value produced by its respective calculate function
1642        #[rustfmt::skip]
1643        pub fn withdraw(&mut self, user_pubkey: &Pubkey, tokens_to_burn: u64) -> Option<u64> {
1644            calculate_withdraw_amount(self.token_supply, self.total_stake, tokens_to_burn)
1645                .and_then(|stake_to_withdraw| self.token_supply.checked_sub(tokens_to_burn)
1646                .and_then(|new_token_supply| self.total_stake.checked_sub(stake_to_withdraw)
1647                .and_then(|new_total_stake| self.user_token_balances.remove(user_pubkey)
1648                .and_then(|old_user_token_balance| old_user_token_balance.checked_sub(tokens_to_burn)
1649                .map(|new_user_token_balance| {
1650                    self.token_supply = new_token_supply;
1651                    self.total_stake = new_total_stake;
1652                    let _ = self.user_token_balances.insert(*user_pubkey, new_user_token_balance);
1653                    stake_to_withdraw
1654            })))))
1655        }
1656
1657        // adds an arbitrary amount of stake, as if inflation rewards were granted
1658        pub fn reward(&mut self, reward_amount: u64) {
1659            self.total_stake = self.total_stake.checked_add(reward_amount).unwrap();
1660        }
1661
1662        // get the token balance for a user
1663        pub fn tokens(&self, user_pubkey: &Pubkey) -> u64 {
1664            *self.user_token_balances.get(user_pubkey).unwrap_or(&0)
1665        }
1666
1667        // get the amount of stake that belongs to a user
1668        pub fn stake(&self, user_pubkey: &Pubkey) -> u64 {
1669            let tokens = self.tokens(user_pubkey);
1670            if tokens > 0 {
1671                u64::try_from(tokens as u128 * self.total_stake as u128 / self.token_supply as u128)
1672                    .unwrap()
1673            } else {
1674                0
1675            }
1676        }
1677
1678        // get the share of the pool that belongs to a user, as a float between 0 and 1
1679        pub fn share(&self, user_pubkey: &Pubkey) -> f64 {
1680            let tokens = self.tokens(user_pubkey);
1681            if tokens > 0 {
1682                tokens as f64 / self.token_supply as f64
1683            } else {
1684                0.0
1685            }
1686        }
1687    }
1688
1689    // this deterministically tests basic behavior of calculate_deposit_amount and
1690    // calculate_withdraw_amount
1691    #[test]
1692    fn simple_deposit_withdraw() {
1693        let mut pool = PoolState::default();
1694        let alice = Pubkey::new_unique();
1695        let bob = Pubkey::new_unique();
1696        let chad = Pubkey::new_unique();
1697
1698        // first deposit. alice now has 250
1699        pool.deposit(&alice, 250).unwrap();
1700        assert_eq!(pool.tokens(&alice), 250);
1701        assert_eq!(pool.token_supply, 250);
1702        assert_eq!(pool.total_stake, 250);
1703
1704        // second deposit. bob now has 750
1705        pool.deposit(&bob, 750).unwrap();
1706        assert_eq!(pool.tokens(&bob), 750);
1707        assert_eq!(pool.token_supply, 1000);
1708        assert_eq!(pool.total_stake, 1000);
1709
1710        // alice controls 25% of the pool and bob controls 75%. rewards should accrue
1711        // likewise use nice even numbers, we can test fiddly stuff in the
1712        // stochastic cases
1713        assert_relative_eq!(pool.share(&alice), 0.25);
1714        assert_relative_eq!(pool.share(&bob), 0.75);
1715        pool.reward(1000);
1716        assert_eq!(pool.stake(&alice), pool.tokens(&alice) * 2);
1717        assert_eq!(pool.stake(&bob), pool.tokens(&bob) * 2);
1718        assert_relative_eq!(pool.share(&alice), 0.25);
1719        assert_relative_eq!(pool.share(&bob), 0.75);
1720
1721        // alice harvests rewards, reducing her share of the *previous* pool size to
1722        // 12.5% but because the pool itself has shrunk to 87.5%, its actually
1723        // more like 14.3% luckily chad deposits immediately after to make our
1724        // math easier
1725        let stake_removed = pool.withdraw(&alice, 125).unwrap();
1726        pool.deposit(&chad, 250).unwrap();
1727        assert_eq!(stake_removed, 250);
1728        assert_relative_eq!(pool.share(&alice), 0.125);
1729        assert_relative_eq!(pool.share(&bob), 0.75);
1730
1731        // bob and chad exit the pool
1732        let stake_removed = pool.withdraw(&bob, 750).unwrap();
1733        assert_eq!(stake_removed, 1500);
1734        assert_relative_eq!(pool.share(&bob), 0.0);
1735        pool.withdraw(&chad, 125).unwrap();
1736        assert_relative_eq!(pool.share(&alice), 1.0);
1737    }
1738
1739    // this stochastically tests calculate_deposit_amount and
1740    // calculate_withdraw_amount the objective is specifically to ensure that
1741    // the math does not fail on any combination of state changes the no_minimum
1742    // case is to account for a future where small deposits are possible through
1743    // multistake
1744    #[test_case(rand::random(), false, false; "no_rewards")]
1745    #[test_case(rand::random(), true, false; "with_rewards")]
1746    #[test_case(rand::random(), true, true; "no_minimum")]
1747    fn random_deposit_withdraw(seed: u64, with_rewards: bool, no_minimum: bool) {
1748        println!(
1749            "TEST SEED: {seed}. edit the test case to pass this value if needed to debug failures",
1750        );
1751        let mut prng = rand::rngs::StdRng::seed_from_u64(seed);
1752
1753        // deposit_range is the range of typical deposits within minimum_delegation
1754        // minnow_range is under the minimum for cases where we test that
1755        // op_range is how we roll whether to deposit, withdraw, or reward
1756        // std_range is a standard probability
1757        let deposit_range = Uniform::try_from(LAMPORTS_PER_SOL..LAMPORTS_PER_SOL * 1000).unwrap();
1758        let minnow_range = Uniform::try_from(1..LAMPORTS_PER_SOL).unwrap();
1759        let op_range = Uniform::try_from(if with_rewards { 0.0..1.0 } else { 0.0..0.65 }).unwrap();
1760        let std_range = Uniform::try_from(0.0..1.0).unwrap();
1761
1762        let deposit_amount = |prng: &mut StdRng| {
1763            if no_minimum && prng.random_bool(0.2) {
1764                minnow_range.sample(prng)
1765            } else {
1766                deposit_range.sample(prng)
1767            }
1768        };
1769
1770        // run everything a number of times to get a good sample
1771        for _ in 0..100 {
1772            // PoolState tracks all outstanding tokens and the total combined stake
1773            // there is no reasonable way to track "deposited stake" because reward accrual
1774            // makes this concept incoherent a token corresponds to a
1775            // percentage, not a stake value
1776            let mut pool = PoolState::default();
1777
1778            // generate between 1 and 100 users and have ~half of them deposit
1779            // note for most of these tests we adhere to the minimum delegation
1780            // one of the thing we want to see is deposit size being many ooms larger than
1781            // reward size
1782            let mut users = vec![];
1783            let user_count: usize = prng.random_range(1..=100);
1784            for _ in 0..user_count {
1785                let user = Pubkey::new_unique();
1786
1787                if prng.random_bool(0.5) {
1788                    pool.deposit(&user, deposit_amount(&mut prng)).unwrap();
1789                }
1790
1791                users.push(user);
1792            }
1793
1794            // now we do a set of arbitrary operations and confirm invariants hold
1795            // we underweight withdraw a little bit to lessen the chances we random walk to
1796            // an empty pool
1797            for _ in 0..1000 {
1798                match op_range.sample(&mut prng) {
1799                    // deposit a random amount of stake for tokens with a random user
1800                    // check their stake, tokens, and share increase by the expected amount
1801                    n if n <= 0.35 => {
1802                        let user = users.iter().choose(&mut prng).unwrap();
1803                        let prev_share = pool.share(user);
1804                        let prev_stake = pool.stake(user);
1805                        let prev_token_supply = pool.token_supply;
1806                        let prev_total_stake = pool.total_stake;
1807
1808                        let stake_deposited = deposit_amount(&mut prng);
1809                        let tokens_minted = pool.deposit(user, stake_deposited).unwrap();
1810
1811                        // stake increased by exactly the deposit amount
1812                        assert_eq!(pool.total_stake - prev_total_stake, stake_deposited);
1813
1814                        // calculated stake fraction is within 2 lamps of deposit amount
1815                        assert!(
1816                            (pool.stake(user) as i64 - prev_stake as i64 - stake_deposited as i64)
1817                                .abs()
1818                                <= 2
1819                        );
1820
1821                        // tokens increased by exactly the mint amount
1822                        assert_eq!(pool.token_supply - prev_token_supply, tokens_minted);
1823
1824                        // tokens per supply increased with stake per total
1825                        if prev_total_stake > 0 {
1826                            assert_relative_eq!(
1827                                pool.share(user) - prev_share,
1828                                pool.stake(user) as f64 / pool.total_stake as f64
1829                                    - prev_stake as f64 / prev_total_stake as f64,
1830                                epsilon = 1e-6
1831                            );
1832                        }
1833                    }
1834
1835                    // burn a random amount of tokens from a random user with outstanding deposits
1836                    // check their stake, tokens, and share decrease by the expected amount
1837                    n if n > 0.35 && n <= 0.65 => {
1838                        if let Some(user) = users
1839                            .iter()
1840                            .filter(|user| pool.tokens(user) > 0)
1841                            .choose(&mut prng)
1842                        {
1843                            let prev_tokens = pool.tokens(user);
1844                            let prev_share = pool.share(user);
1845                            let prev_stake = pool.stake(user);
1846                            let prev_token_supply = pool.token_supply;
1847                            let prev_total_stake = pool.total_stake;
1848
1849                            let tokens_burned = if std_range.sample(&mut prng) <= 0.1 {
1850                                prev_tokens
1851                            } else {
1852                                prng.random_range(0..prev_tokens)
1853                            };
1854                            let stake_received = pool.withdraw(user, tokens_burned).unwrap();
1855
1856                            // stake decreased by exactly the withdraw amount
1857                            assert_eq!(prev_total_stake - pool.total_stake, stake_received);
1858
1859                            // calculated stake fraction is within 2 lamps of withdraw amount
1860                            assert!(
1861                                (prev_stake as i64
1862                                    - pool.stake(user) as i64
1863                                    - stake_received as i64)
1864                                    .abs()
1865                                    <= 2
1866                            );
1867
1868                            // tokens decreased by the burn amount
1869                            assert_eq!(prev_token_supply - pool.token_supply, tokens_burned);
1870
1871                            // tokens per supply decreased with stake per total
1872                            if pool.total_stake > 0 {
1873                                assert_relative_eq!(
1874                                    prev_share - pool.share(user),
1875                                    prev_stake as f64 / prev_total_stake as f64
1876                                        - pool.stake(user) as f64 / pool.total_stake as f64,
1877                                    epsilon = 1e-6
1878                                );
1879                            }
1880                        };
1881                    }
1882
1883                    // run a single epoch worth of rewards
1884                    // check all user shares stay the same and stakes increase by the expected
1885                    // amount
1886                    _ => {
1887                        assert!(with_rewards);
1888
1889                        let prev_shares_stakes = users
1890                            .iter()
1891                            .map(|user| (user, pool.share(user), pool.stake(user)))
1892                            .filter(|(_, _, stake)| stake > &0)
1893                            .collect::<Vec<_>>();
1894
1895                        pool.reward((pool.total_stake as f64 * INFLATION_BASE_RATE) as u64);
1896
1897                        for (user, prev_share, prev_stake) in prev_shares_stakes {
1898                            // shares are the same before and after
1899                            assert_eq!(pool.share(user), prev_share);
1900
1901                            let curr_stake = pool.stake(user);
1902                            let stake_share = prev_stake as f64 * INFLATION_BASE_RATE;
1903                            let stake_diff = (curr_stake - prev_stake) as f64;
1904
1905                            // stake increase is within 2 lamps when calculated as a difference or a
1906                            // percentage
1907                            assert!((stake_share - stake_diff).abs() <= 2.0);
1908                        }
1909                    }
1910                }
1911            }
1912        }
1913    }
1914}