1use {
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
43fn 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
62fn 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
78fn 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
88fn get_stake_amount(stake_account_info: &AccountInfo) -> Result<u64, ProgramError> {
90 Ok(get_stake_state(stake_account_info)?.1.delegation.stake)
91}
92
93fn 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
99fn 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
115fn 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
131fn 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
147fn 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
163fn 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
179fn 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
218fn 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
235fn 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
248fn 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
262fn 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
276fn 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
290fn 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
304fn 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
321fn minimum_delegation() -> Result<u64, ProgramError> {
325 Ok(std::cmp::max(
326 stake::tools::get_minimum_delegation()?,
327 LAMPORTS_PER_SOL,
328 ))
329}
330
331pub 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 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 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 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 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 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 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 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 let stake_added = post_pool_stake
855 .checked_sub(pre_pool_stake)
856 .ok_or(SinglePoolError::ArithmeticOverflow)?;
857
858 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 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 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 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 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 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 if withdraw_stake > pre_pool_stake || withdraw_stake == pool_stake_info.lamports() {
972 return Err(SinglePoolError::WithdrawalTooLarge.into());
973 }
974
975 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 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 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 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 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 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 #[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 #[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 pub fn reward(&mut self, reward_amount: u64) {
1283 self.total_stake = self.total_stake.checked_add(reward_amount).unwrap();
1284 }
1285
1286 pub fn tokens(&self, user_pubkey: &Pubkey) -> u64 {
1288 *self.user_token_balances.get(user_pubkey).unwrap_or(&0)
1289 }
1290
1291 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 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 #[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 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 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 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 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 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 #[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 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 for _ in 0..100 {
1397 let mut pool = PoolState::default();
1402
1403 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 for _ in 0..1000 {
1423 match op_range.sample(&mut prng) {
1424 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 assert_eq!(pool.total_stake - prev_total_stake, stake_deposited);
1438
1439 assert!(
1441 (pool.stake(user) as i64 - prev_stake as i64 - stake_deposited as i64)
1442 .abs()
1443 <= 2
1444 );
1445
1446 assert_eq!(pool.token_supply - prev_token_supply, tokens_minted);
1448
1449 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 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 assert_eq!(prev_total_stake - pool.total_stake, stake_received);
1483
1484 assert!(
1486 (prev_stake as i64
1487 - pool.stake(user) as i64
1488 - stake_received as i64)
1489 .abs()
1490 <= 2
1491 );
1492
1493 assert_eq!(prev_token_supply - pool.token_supply, tokens_burned);
1495
1496 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 _ => {
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 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 assert!((stake_share - stake_diff).abs() <= 2.0);
1533 }
1534 }
1535 }
1536 }
1537 }
1538 }
1539}