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, PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH, POOL_MINT_AUTHORITY_PREFIX,
15 POOL_MINT_PREFIX, POOL_MPL_AUTHORITY_PREFIX, POOL_ONRAMP_PREFIX, POOL_PREFIX,
16 POOL_STAKE_AUTHORITY_PREFIX, POOL_STAKE_PREFIX, VOTE_STATE_AUTHORIZED_WITHDRAWER_END,
17 VOTE_STATE_AUTHORIZED_WITHDRAWER_START, VOTE_STATE_DISCRIMINATOR_END,
18 },
19 borsh::BorshDeserialize,
20 solana_account_info::{next_account_info, AccountInfo},
21 solana_borsh::v1::{get_packed_len, try_from_slice_unchecked},
22 solana_clock::Epoch,
23 solana_cpi::invoke_signed,
24 solana_msg::msg,
25 solana_native_token::LAMPORTS_PER_SOL,
26 solana_program_entrypoint::ProgramResult,
27 solana_program_error::ProgramError,
28 solana_program_pack::Pack,
29 solana_pubkey::Pubkey,
30 solana_rent::Rent,
31 solana_stake_interface::{
32 self as stake,
33 state::{Meta, Stake, StakeActivationStatus, StakeStateV2},
34 },
35 solana_system_interface::{instruction as system_instruction, program as system_program},
36 solana_sysvar::{clock::Clock, stake_history::StakeHistorySysvar, Sysvar},
37 solana_vote_interface::program as vote_program,
38 spl_token::state::Mint,
39};
40
41fn calculate_deposit_amount(
44 pre_token_supply: u64,
45 pre_pool_stake: u64,
46 user_stake_to_deposit: u64,
47) -> Option<u64> {
48 if pre_pool_stake == 0 || pre_token_supply == 0 {
49 Some(user_stake_to_deposit)
50 } else {
51 u64::try_from(
52 (user_stake_to_deposit as u128)
53 .checked_mul(pre_token_supply as u128)?
54 .checked_div(pre_pool_stake as u128)?,
55 )
56 .ok()
57 }
58}
59
60fn calculate_withdraw_amount(
63 pre_token_supply: u64,
64 pre_pool_stake: u64,
65 user_tokens_to_burn: u64,
66) -> Option<u64> {
67 let numerator = (user_tokens_to_burn as u128).checked_mul(pre_pool_stake as u128)?;
68 let denominator = pre_token_supply as u128;
69 if numerator < denominator || denominator == 0 {
70 Some(0)
71 } else {
72 u64::try_from(numerator.checked_div(denominator)?).ok()
73 }
74}
75
76fn get_stake_state(stake_account_info: &AccountInfo) -> Result<(Meta, Stake), ProgramError> {
78 let stake_state = try_from_slice_unchecked::<StakeStateV2>(&stake_account_info.data.borrow())?;
79
80 match stake_state {
81 StakeStateV2::Stake(meta, stake, _) => Ok((meta, stake)),
82 _ => Err(SinglePoolError::WrongStakeStake.into()),
83 }
84}
85
86fn get_stake_amount(stake_account_info: &AccountInfo) -> Result<u64, ProgramError> {
88 Ok(get_stake_state(stake_account_info)?.1.delegation.stake)
89}
90
91fn is_stake_active_without_history(stake: &Stake, current_epoch: Epoch) -> bool {
93 stake.delegation.activation_epoch < current_epoch
94 && stake.delegation.deactivation_epoch == Epoch::MAX
95}
96
97fn is_stake_fully_active(stake_activation_status: &StakeActivationStatus) -> bool {
99 matches!(stake_activation_status, StakeActivationStatus {
100 effective,
101 activating: 0,
102 deactivating: 0,
103 } if *effective > 0)
104}
105
106fn check_pool_address(
108 program_id: &Pubkey,
109 vote_account_address: &Pubkey,
110 check_address: &Pubkey,
111) -> Result<u8, ProgramError> {
112 check_pool_pda(
113 program_id,
114 vote_account_address,
115 check_address,
116 &crate::find_pool_address_and_bump,
117 "pool",
118 SinglePoolError::InvalidPoolAccount,
119 )
120}
121
122fn check_pool_stake_address(
124 program_id: &Pubkey,
125 pool_address: &Pubkey,
126 check_address: &Pubkey,
127) -> Result<u8, ProgramError> {
128 check_pool_pda(
129 program_id,
130 pool_address,
131 check_address,
132 &crate::find_pool_stake_address_and_bump,
133 "stake account",
134 SinglePoolError::InvalidPoolStakeAccount,
135 )
136}
137
138fn check_pool_onramp_address(
140 program_id: &Pubkey,
141 pool_address: &Pubkey,
142 check_address: &Pubkey,
143) -> Result<u8, ProgramError> {
144 check_pool_pda(
145 program_id,
146 pool_address,
147 check_address,
148 &crate::find_pool_onramp_address_and_bump,
149 "onramp account",
150 SinglePoolError::InvalidPoolOnRampAccount,
151 )
152}
153
154fn check_pool_mint_address(
156 program_id: &Pubkey,
157 pool_address: &Pubkey,
158 check_address: &Pubkey,
159) -> Result<u8, ProgramError> {
160 check_pool_pda(
161 program_id,
162 pool_address,
163 check_address,
164 &crate::find_pool_mint_address_and_bump,
165 "mint",
166 SinglePoolError::InvalidPoolMint,
167 )
168}
169
170fn check_pool_stake_authority_address(
172 program_id: &Pubkey,
173 pool_address: &Pubkey,
174 check_address: &Pubkey,
175) -> Result<u8, ProgramError> {
176 check_pool_pda(
177 program_id,
178 pool_address,
179 check_address,
180 &crate::find_pool_stake_authority_address_and_bump,
181 "stake authority",
182 SinglePoolError::InvalidPoolStakeAuthority,
183 )
184}
185
186fn check_pool_mint_authority_address(
188 program_id: &Pubkey,
189 pool_address: &Pubkey,
190 check_address: &Pubkey,
191) -> Result<u8, ProgramError> {
192 check_pool_pda(
193 program_id,
194 pool_address,
195 check_address,
196 &crate::find_pool_mint_authority_address_and_bump,
197 "mint authority",
198 SinglePoolError::InvalidPoolMintAuthority,
199 )
200}
201
202fn check_pool_mpl_authority_address(
204 program_id: &Pubkey,
205 pool_address: &Pubkey,
206 check_address: &Pubkey,
207) -> Result<u8, ProgramError> {
208 check_pool_pda(
209 program_id,
210 pool_address,
211 check_address,
212 &crate::find_pool_mpl_authority_address_and_bump,
213 "MPL authority",
214 SinglePoolError::InvalidPoolMplAuthority,
215 )
216}
217
218fn check_pool_pda(
219 program_id: &Pubkey,
220 base_address: &Pubkey,
221 check_address: &Pubkey,
222 pda_lookup_fn: &dyn Fn(&Pubkey, &Pubkey) -> (Pubkey, u8),
223 pda_name: &str,
224 pool_error: SinglePoolError,
225) -> Result<u8, ProgramError> {
226 let (derived_address, bump_seed) = pda_lookup_fn(program_id, base_address);
227 if *check_address != derived_address {
228 msg!(
229 "Incorrect {} address for base {}: expected {}, received {}",
230 pda_name,
231 base_address,
232 derived_address,
233 check_address,
234 );
235 Err(pool_error.into())
236 } else {
237 Ok(bump_seed)
238 }
239}
240
241fn check_vote_account(vote_account_info: &AccountInfo) -> Result<(), ProgramError> {
243 check_account_owner(vote_account_info, &vote_program::id())?;
244
245 let vote_account_data = &vote_account_info.try_borrow_data()?;
246 let state_variant = vote_account_data
247 .get(..VOTE_STATE_DISCRIMINATOR_END)
248 .and_then(|s| s.try_into().ok())
249 .ok_or(SinglePoolError::UnparseableVoteAccount)?;
250
251 match u32::from_le_bytes(state_variant) {
252 1 | 2 => Ok(()),
253 0 => Err(SinglePoolError::LegacyVoteAccount.into()),
254 _ => Err(SinglePoolError::UnparseableVoteAccount.into()),
255 }
256}
257
258fn check_mpl_metadata_account_address(
260 metadata_address: &Pubkey,
261 pool_mint: &Pubkey,
262) -> Result<(), ProgramError> {
263 let (metadata_account_pubkey, _) = find_metadata_account(pool_mint);
264 if metadata_account_pubkey != *metadata_address {
265 Err(SinglePoolError::InvalidMetadataAccount.into())
266 } else {
267 Ok(())
268 }
269}
270
271fn check_system_program(program_id: &Pubkey) -> Result<(), ProgramError> {
273 if *program_id != system_program::id() {
274 msg!(
275 "Expected system program {}, received {}",
276 system_program::id(),
277 program_id
278 );
279 Err(ProgramError::IncorrectProgramId)
280 } else {
281 Ok(())
282 }
283}
284
285fn check_token_program(address: &Pubkey) -> Result<(), ProgramError> {
287 if *address != spl_token::id() {
288 msg!(
289 "Incorrect token program, expected {}, received {}",
290 spl_token::id(),
291 address
292 );
293 Err(ProgramError::IncorrectProgramId)
294 } else {
295 Ok(())
296 }
297}
298
299fn check_stake_program(program_id: &Pubkey) -> Result<(), ProgramError> {
301 if *program_id != stake::program::id() {
302 msg!(
303 "Expected stake program {}, received {}",
304 stake::program::id(),
305 program_id
306 );
307 Err(ProgramError::IncorrectProgramId)
308 } else {
309 Ok(())
310 }
311}
312
313fn check_mpl_metadata_program(program_id: &Pubkey) -> Result<(), ProgramError> {
315 if *program_id != inline_mpl_token_metadata::id() {
316 msg!(
317 "Expected MPL metadata program {}, received {}",
318 inline_mpl_token_metadata::id(),
319 program_id
320 );
321 Err(ProgramError::IncorrectProgramId)
322 } else {
323 Ok(())
324 }
325}
326
327fn check_account_owner(
329 account_info: &AccountInfo,
330 program_id: &Pubkey,
331) -> Result<(), ProgramError> {
332 if *program_id != *account_info.owner {
333 msg!(
334 "Expected account to be owned by program {}, received {}",
335 program_id,
336 account_info.owner
337 );
338 Err(ProgramError::IncorrectProgramId)
339 } else {
340 Ok(())
341 }
342}
343
344fn minimum_pool_balance() -> Result<u64, ProgramError> {
348 Ok(std::cmp::max(
349 stake::tools::get_minimum_delegation()?,
350 LAMPORTS_PER_SOL,
351 ))
352}
353
354pub struct Processor {}
356impl Processor {
357 #[allow(clippy::too_many_arguments)]
358 fn stake_merge<'a>(
359 pool_account_key: &Pubkey,
360 source_account: AccountInfo<'a>,
361 authority: AccountInfo<'a>,
362 bump_seed: u8,
363 destination_account: AccountInfo<'a>,
364 clock: AccountInfo<'a>,
365 stake_history: AccountInfo<'a>,
366 ) -> Result<(), ProgramError> {
367 let authority_seeds = &[
368 POOL_STAKE_AUTHORITY_PREFIX,
369 pool_account_key.as_ref(),
370 &[bump_seed],
371 ];
372 let signers = &[&authority_seeds[..]];
373
374 invoke_signed(
375 &stake::instruction::merge(destination_account.key, source_account.key, authority.key)
376 [0],
377 &[
378 destination_account,
379 source_account,
380 clock,
381 stake_history,
382 authority,
383 ],
384 signers,
385 )
386 }
387
388 fn stake_split<'a>(
389 pool_account_key: &Pubkey,
390 stake_account: AccountInfo<'a>,
391 authority: AccountInfo<'a>,
392 bump_seed: u8,
393 amount: u64,
394 split_stake: AccountInfo<'a>,
395 ) -> Result<(), ProgramError> {
396 let authority_seeds = &[
397 POOL_STAKE_AUTHORITY_PREFIX,
398 pool_account_key.as_ref(),
399 &[bump_seed],
400 ];
401 let signers = &[&authority_seeds[..]];
402
403 let split_instruction =
404 stake::instruction::split(stake_account.key, authority.key, amount, split_stake.key);
405
406 invoke_signed(
407 split_instruction.last().unwrap(),
408 &[stake_account, split_stake, authority],
409 signers,
410 )
411 }
412
413 #[allow(clippy::too_many_arguments)]
414 fn stake_authorize<'a>(
415 pool_account_key: &Pubkey,
416 stake_account: AccountInfo<'a>,
417 stake_authority: AccountInfo<'a>,
418 bump_seed: u8,
419 new_stake_authority: &Pubkey,
420 clock: AccountInfo<'a>,
421 ) -> Result<(), ProgramError> {
422 let authority_seeds = &[
423 POOL_STAKE_AUTHORITY_PREFIX,
424 pool_account_key.as_ref(),
425 &[bump_seed],
426 ];
427 let signers = &[&authority_seeds[..]];
428
429 let authorize_instruction = stake::instruction::authorize(
430 stake_account.key,
431 stake_authority.key,
432 new_stake_authority,
433 stake::state::StakeAuthorize::Staker,
434 None,
435 );
436
437 invoke_signed(
438 &authorize_instruction,
439 &[
440 stake_account.clone(),
441 clock.clone(),
442 stake_authority.clone(),
443 ],
444 signers,
445 )?;
446
447 let authorize_instruction = stake::instruction::authorize(
448 stake_account.key,
449 stake_authority.key,
450 new_stake_authority,
451 stake::state::StakeAuthorize::Withdrawer,
452 None,
453 );
454 invoke_signed(
455 &authorize_instruction,
456 &[stake_account, clock, stake_authority],
457 signers,
458 )
459 }
460
461 #[allow(clippy::too_many_arguments)]
462 fn stake_withdraw<'a>(
463 pool_account_key: &Pubkey,
464 stake_account: AccountInfo<'a>,
465 stake_authority: AccountInfo<'a>,
466 bump_seed: u8,
467 destination_account: AccountInfo<'a>,
468 clock: AccountInfo<'a>,
469 stake_history: AccountInfo<'a>,
470 lamports: u64,
471 ) -> Result<(), ProgramError> {
472 let authority_seeds = &[
473 POOL_STAKE_AUTHORITY_PREFIX,
474 pool_account_key.as_ref(),
475 &[bump_seed],
476 ];
477 let signers = &[&authority_seeds[..]];
478
479 let withdraw_instruction = stake::instruction::withdraw(
480 stake_account.key,
481 stake_authority.key,
482 destination_account.key,
483 lamports,
484 None,
485 );
486
487 invoke_signed(
488 &withdraw_instruction,
489 &[
490 stake_account,
491 destination_account,
492 clock,
493 stake_history,
494 stake_authority,
495 ],
496 signers,
497 )
498 }
499
500 #[allow(clippy::too_many_arguments)]
501 fn token_mint_to<'a>(
502 pool_account_key: &Pubkey,
503 token_program: AccountInfo<'a>,
504 mint: AccountInfo<'a>,
505 destination: AccountInfo<'a>,
506 authority: AccountInfo<'a>,
507 bump_seed: u8,
508 amount: u64,
509 ) -> Result<(), ProgramError> {
510 let authority_seeds = &[
511 POOL_MINT_AUTHORITY_PREFIX,
512 pool_account_key.as_ref(),
513 &[bump_seed],
514 ];
515 let signers = &[&authority_seeds[..]];
516
517 let ix = spl_token::instruction::mint_to(
518 token_program.key,
519 mint.key,
520 destination.key,
521 authority.key,
522 &[],
523 amount,
524 )?;
525
526 invoke_signed(&ix, &[mint, destination, authority], signers)
527 }
528
529 #[allow(clippy::too_many_arguments)]
530 fn token_burn<'a>(
531 pool_account_key: &Pubkey,
532 token_program: AccountInfo<'a>,
533 burn_account: AccountInfo<'a>,
534 mint: AccountInfo<'a>,
535 authority: AccountInfo<'a>,
536 bump_seed: u8,
537 amount: u64,
538 ) -> Result<(), ProgramError> {
539 let authority_seeds = &[
540 POOL_MINT_AUTHORITY_PREFIX,
541 pool_account_key.as_ref(),
542 &[bump_seed],
543 ];
544 let signers = &[&authority_seeds[..]];
545
546 let ix = spl_token::instruction::burn(
547 token_program.key,
548 burn_account.key,
549 mint.key,
550 authority.key,
551 &[],
552 amount,
553 )?;
554
555 invoke_signed(&ix, &[burn_account, mint, authority], signers)
556 }
557
558 fn process_initialize_pool(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
559 let account_info_iter = &mut accounts.iter();
560 let vote_account_info = next_account_info(account_info_iter)?;
561 let pool_info = next_account_info(account_info_iter)?;
562 let pool_stake_info = next_account_info(account_info_iter)?;
563 let pool_mint_info = next_account_info(account_info_iter)?;
564 let pool_stake_authority_info = next_account_info(account_info_iter)?;
565 let pool_mint_authority_info = next_account_info(account_info_iter)?;
566 let rent_info = next_account_info(account_info_iter)?;
567 let rent = &Rent::from_account_info(rent_info)?;
568 let clock_info = next_account_info(account_info_iter)?;
569 let stake_history_info = next_account_info(account_info_iter)?;
570 let stake_config_info = next_account_info(account_info_iter)?;
571 let system_program_info = next_account_info(account_info_iter)?;
572 let token_program_info = next_account_info(account_info_iter)?;
573 let stake_program_info = next_account_info(account_info_iter)?;
574
575 check_vote_account(vote_account_info)?;
576 let pool_bump_seed = check_pool_address(program_id, vote_account_info.key, pool_info.key)?;
577 let stake_bump_seed =
578 check_pool_stake_address(program_id, pool_info.key, pool_stake_info.key)?;
579 let mint_bump_seed =
580 check_pool_mint_address(program_id, pool_info.key, pool_mint_info.key)?;
581 let stake_authority_bump_seed = check_pool_stake_authority_address(
582 program_id,
583 pool_info.key,
584 pool_stake_authority_info.key,
585 )?;
586 let mint_authority_bump_seed = check_pool_mint_authority_address(
587 program_id,
588 pool_info.key,
589 pool_mint_authority_info.key,
590 )?;
591 check_system_program(system_program_info.key)?;
592 check_token_program(token_program_info.key)?;
593 check_stake_program(stake_program_info.key)?;
594
595 let pool_seeds = &[
596 POOL_PREFIX,
597 vote_account_info.key.as_ref(),
598 &[pool_bump_seed],
599 ];
600 let pool_signers = &[&pool_seeds[..]];
601
602 let stake_seeds = &[
603 POOL_STAKE_PREFIX,
604 pool_info.key.as_ref(),
605 &[stake_bump_seed],
606 ];
607 let stake_signers = &[&stake_seeds[..]];
608
609 let mint_seeds = &[POOL_MINT_PREFIX, pool_info.key.as_ref(), &[mint_bump_seed]];
610 let mint_signers = &[&mint_seeds[..]];
611
612 let stake_authority_seeds = &[
613 POOL_STAKE_AUTHORITY_PREFIX,
614 pool_info.key.as_ref(),
615 &[stake_authority_bump_seed],
616 ];
617 let stake_authority_signers = &[&stake_authority_seeds[..]];
618
619 let mint_authority_seeds = &[
620 POOL_MINT_AUTHORITY_PREFIX,
621 pool_info.key.as_ref(),
622 &[mint_authority_bump_seed],
623 ];
624 let mint_authority_signers = &[&mint_authority_seeds[..]];
625
626 let pool_space = get_packed_len::<SinglePool>();
628 if !rent.is_exempt(pool_info.lamports(), pool_space) {
629 return Err(SinglePoolError::WrongRentAmount.into());
630 }
631 if pool_info.data_len() != 0 {
632 return Err(SinglePoolError::PoolAlreadyInitialized.into());
633 }
634
635 invoke_signed(
636 &system_instruction::allocate(pool_info.key, pool_space as u64),
637 &[pool_info.clone()],
638 pool_signers,
639 )?;
640
641 invoke_signed(
642 &system_instruction::assign(pool_info.key, program_id),
643 &[pool_info.clone()],
644 pool_signers,
645 )?;
646
647 let mut pool = try_from_slice_unchecked::<SinglePool>(&pool_info.data.borrow())?;
648 pool.account_type = SinglePoolAccountType::Pool;
649 pool.vote_account_address = *vote_account_info.key;
650 borsh::to_writer(&mut pool_info.data.borrow_mut()[..], &pool)?;
651
652 let mint_space = spl_token::state::Mint::LEN;
654
655 invoke_signed(
656 &system_instruction::allocate(pool_mint_info.key, mint_space as u64),
657 &[pool_mint_info.clone()],
658 mint_signers,
659 )?;
660
661 invoke_signed(
662 &system_instruction::assign(pool_mint_info.key, token_program_info.key),
663 &[pool_mint_info.clone()],
664 mint_signers,
665 )?;
666
667 invoke_signed(
668 &spl_token::instruction::initialize_mint2(
669 token_program_info.key,
670 pool_mint_info.key,
671 pool_mint_authority_info.key,
672 None,
673 MINT_DECIMALS,
674 )?,
675 &[pool_mint_info.clone()],
676 mint_authority_signers,
677 )?;
678
679 let minimum_pool_balance = minimum_pool_balance()?;
682 let stake_space = std::mem::size_of::<stake::state::StakeStateV2>();
683 let stake_rent_plus_initial = rent
684 .minimum_balance(stake_space)
685 .saturating_add(minimum_pool_balance);
686
687 if pool_stake_info.lamports() < stake_rent_plus_initial {
688 return Err(SinglePoolError::WrongRentAmount.into());
689 }
690
691 let authorized = stake::state::Authorized::auto(pool_stake_authority_info.key);
692
693 invoke_signed(
694 &system_instruction::allocate(pool_stake_info.key, stake_space as u64),
695 &[pool_stake_info.clone()],
696 stake_signers,
697 )?;
698
699 invoke_signed(
700 &system_instruction::assign(pool_stake_info.key, stake_program_info.key),
701 &[pool_stake_info.clone()],
702 stake_signers,
703 )?;
704
705 invoke_signed(
706 &stake::instruction::initialize_checked(pool_stake_info.key, &authorized),
707 &[
708 pool_stake_info.clone(),
709 rent_info.clone(),
710 pool_stake_authority_info.clone(),
711 pool_stake_authority_info.clone(),
712 ],
713 stake_authority_signers,
714 )?;
715
716 invoke_signed(
718 &stake::instruction::delegate_stake(
719 pool_stake_info.key,
720 pool_stake_authority_info.key,
721 vote_account_info.key,
722 ),
723 &[
724 pool_stake_info.clone(),
725 vote_account_info.clone(),
726 clock_info.clone(),
727 stake_history_info.clone(),
728 stake_config_info.clone(),
729 pool_stake_authority_info.clone(),
730 ],
731 stake_authority_signers,
732 )?;
733
734 Ok(())
735 }
736
737 fn process_replenish_pool(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
738 let account_info_iter = &mut accounts.iter();
739 let vote_account_info = next_account_info(account_info_iter)?;
740 let pool_info = next_account_info(account_info_iter)?;
741 let pool_stake_info = next_account_info(account_info_iter)?;
742 let pool_onramp_info = next_account_info(account_info_iter)?;
743 let pool_stake_authority_info = next_account_info(account_info_iter)?;
744 let clock_info = next_account_info(account_info_iter)?;
745 let clock = &Clock::from_account_info(clock_info)?;
746 let stake_history_info = next_account_info(account_info_iter)?;
747 let stake_config_info = next_account_info(account_info_iter)?;
748 let stake_program_info = next_account_info(account_info_iter)?;
749
750 let stake_history = &StakeHistorySysvar(clock.epoch);
751 let minimum_delegation = stake::tools::get_minimum_delegation()?;
752
753 check_vote_account(vote_account_info)?;
754 check_pool_address(program_id, vote_account_info.key, pool_info.key)?;
755
756 SinglePool::from_account_info(pool_info, program_id)?;
757
758 check_pool_stake_address(program_id, pool_info.key, pool_stake_info.key)?;
759 check_pool_onramp_address(program_id, pool_info.key, pool_onramp_info.key)?;
760 let stake_authority_bump_seed = check_pool_stake_authority_address(
761 program_id,
762 pool_info.key,
763 pool_stake_authority_info.key,
764 )?;
765 check_stake_program(stake_program_info.key)?;
766
767 let (pool_stake_meta, pool_stake_state) = get_stake_state(pool_stake_info)?;
769 let pool_stake_status = pool_stake_state
770 .delegation
771 .stake_activating_and_deactivating(
772 clock.epoch,
773 stake_history,
774 PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
775 );
776 let pool_stake_is_fully_active = is_stake_fully_active(&pool_stake_status);
777
778 let (option_onramp_status, onramp_deactivation_epoch, onramp_rent_exempt_reserve) =
781 match try_from_slice_unchecked::<StakeStateV2>(&pool_onramp_info.data.borrow()) {
782 Ok(StakeStateV2::Initialized(meta)) => (None, u64::MAX, meta.rent_exempt_reserve),
783 Ok(StakeStateV2::Stake(meta, stake, _)) => (
784 Some(stake.delegation.stake_activating_and_deactivating(
785 clock.epoch,
786 stake_history,
787 PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
788 )),
789 stake.delegation.deactivation_epoch,
790 meta.rent_exempt_reserve,
791 ),
792 _ => return Err(SinglePoolError::OnRampDoesntExist.into()),
793 };
794
795 let stake_authority_seeds = &[
796 POOL_STAKE_AUTHORITY_PREFIX,
797 pool_info.key.as_ref(),
798 &[stake_authority_bump_seed],
799 ];
800 let stake_authority_signers = &[&stake_authority_seeds[..]];
801
802 if pool_stake_state.delegation.deactivation_epoch == clock.epoch
805 || (pool_stake_state.delegation.deactivation_epoch < clock.epoch
806 && pool_stake_status.effective == 0)
807 {
808 invoke_signed(
809 &stake::instruction::delegate_stake(
810 pool_stake_info.key,
811 pool_stake_authority_info.key,
812 vote_account_info.key,
813 ),
814 &[
815 pool_stake_info.clone(),
816 vote_account_info.clone(),
817 clock_info.clone(),
818 stake_history_info.clone(),
819 stake_config_info.clone(),
820 pool_stake_authority_info.clone(),
821 ],
822 stake_authority_signers,
823 )?;
824 }
825
826 if pool_stake_is_fully_active {
828 let pool_excess_lamports = pool_stake_info
830 .lamports()
831 .saturating_sub(pool_stake_state.delegation.stake)
832 .saturating_sub(pool_stake_meta.rent_exempt_reserve);
833
834 if let Some(ref onramp_status) = option_onramp_status {
836 if is_stake_fully_active(onramp_status) {
837 invoke_signed(
838 &stake::instruction::move_stake(
839 pool_onramp_info.key,
840 pool_stake_info.key,
841 pool_stake_authority_info.key,
842 onramp_status.effective,
843 ),
844 &[
845 pool_onramp_info.clone(),
846 pool_stake_info.clone(),
847 pool_stake_authority_info.clone(),
848 ],
849 stake_authority_signers,
850 )?;
851 }
852 }
853
854 if pool_excess_lamports > 0 {
856 invoke_signed(
857 &stake::instruction::move_lamports(
858 pool_stake_info.key,
859 pool_onramp_info.key,
860 pool_stake_authority_info.key,
861 pool_excess_lamports,
862 ),
863 &[
864 pool_stake_info.clone(),
865 pool_onramp_info.clone(),
866 pool_stake_authority_info.clone(),
867 ],
868 stake_authority_signers,
869 )?;
870 }
871
872 let onramp_non_rent_lamports = pool_onramp_info
877 .lamports()
878 .saturating_sub(onramp_rent_exempt_reserve);
879 let must_delegate_onramp = match option_onramp_status.unwrap_or_default() {
880 StakeActivationStatus {
882 effective: 0,
883 activating,
884 deactivating: 0,
885 } if activating > 0 => {
886 onramp_non_rent_lamports >= minimum_delegation
887 && onramp_non_rent_lamports > activating
888 }
889 StakeActivationStatus {
892 effective: _,
893 activating: 0,
894 deactivating,
895 } if deactivating == 0 || onramp_deactivation_epoch == clock.epoch => {
896 onramp_non_rent_lamports >= minimum_delegation
897 }
898 _ => false,
900 };
901
902 if must_delegate_onramp {
903 invoke_signed(
904 &stake::instruction::delegate_stake(
905 pool_onramp_info.key,
906 pool_stake_authority_info.key,
907 vote_account_info.key,
908 ),
909 &[
910 pool_onramp_info.clone(),
911 vote_account_info.clone(),
912 clock_info.clone(),
913 stake_history_info.clone(),
914 stake_config_info.clone(),
915 pool_stake_authority_info.clone(),
916 ],
917 stake_authority_signers,
918 )?;
919 }
920 }
921
922 Ok(())
923 }
924
925 fn process_deposit_stake(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
926 let account_info_iter = &mut accounts.iter();
927 let pool_info = next_account_info(account_info_iter)?;
928 let pool_stake_info = next_account_info(account_info_iter)?;
929 let pool_mint_info = {
930 let account_info = next_account_info(account_info_iter)?;
931 if check_pool_onramp_address(program_id, pool_info.key, account_info.key).is_ok() {
934 next_account_info(account_info_iter)?
935 } else {
936 account_info
937 }
938 };
939 let pool_stake_authority_info = next_account_info(account_info_iter)?;
940 let pool_mint_authority_info = next_account_info(account_info_iter)?;
941 let user_stake_info = next_account_info(account_info_iter)?;
942 let user_token_account_info = next_account_info(account_info_iter)?;
943 let user_lamport_account_info = next_account_info(account_info_iter)?;
944 let clock_info = next_account_info(account_info_iter)?;
945 let clock = &Clock::from_account_info(clock_info)?;
946 let stake_history_info = next_account_info(account_info_iter)?;
947 let token_program_info = next_account_info(account_info_iter)?;
948 let stake_program_info = next_account_info(account_info_iter)?;
949
950 SinglePool::from_account_info(pool_info, program_id)?;
951
952 check_pool_stake_address(program_id, pool_info.key, pool_stake_info.key)?;
953 check_pool_mint_address(program_id, pool_info.key, pool_mint_info.key)?;
954 let stake_authority_bump_seed = check_pool_stake_authority_address(
955 program_id,
956 pool_info.key,
957 pool_stake_authority_info.key,
958 )?;
959 let mint_authority_bump_seed = check_pool_mint_authority_address(
960 program_id,
961 pool_info.key,
962 pool_mint_authority_info.key,
963 )?;
964 check_token_program(token_program_info.key)?;
965 check_stake_program(stake_program_info.key)?;
966
967 if pool_stake_info.key == user_stake_info.key {
968 return Err(SinglePoolError::InvalidPoolStakeAccountUsage.into());
969 }
970
971 let onramp_account_address = crate::find_pool_onramp_address(program_id, pool_info.key);
972 if onramp_account_address == *user_stake_info.key {
973 return Err(SinglePoolError::InvalidPoolStakeAccountUsage.into());
974 }
975
976 let minimum_pool_balance = minimum_pool_balance()?;
977
978 let (pool_stake_meta, pool_stake_state) = get_stake_state(pool_stake_info)?;
979 let pre_pool_stake = pool_stake_state
980 .delegation
981 .stake
982 .saturating_sub(minimum_pool_balance);
983 let pre_pool_excess_lamports = pool_stake_info
984 .lamports()
985 .checked_sub(pool_stake_state.delegation.stake)
986 .and_then(|amount| amount.checked_sub(pool_stake_meta.rent_exempt_reserve))
987 .ok_or(SinglePoolError::ArithmeticOverflow)?;
988 msg!("Available stake pre merge {}", pre_pool_stake);
989
990 let (user_stake_meta, user_stake_state) = get_stake_state(user_stake_info)?;
993 if user_stake_meta.authorized
994 != stake::state::Authorized::auto(pool_stake_authority_info.key)
995 || is_stake_active_without_history(&pool_stake_state, clock.epoch)
996 != is_stake_active_without_history(&user_stake_state, clock.epoch)
997 {
998 return Err(SinglePoolError::WrongStakeStake.into());
999 }
1000
1001 Self::stake_merge(
1005 pool_info.key,
1006 user_stake_info.clone(),
1007 pool_stake_authority_info.clone(),
1008 stake_authority_bump_seed,
1009 pool_stake_info.clone(),
1010 clock_info.clone(),
1011 stake_history_info.clone(),
1012 )?;
1013
1014 let (pool_stake_meta, pool_stake_state) = get_stake_state(pool_stake_info)?;
1015 let post_pool_stake = pool_stake_state
1016 .delegation
1017 .stake
1018 .saturating_sub(minimum_pool_balance);
1019 let post_pool_lamports = pool_stake_info.lamports();
1020 msg!("Available stake post merge {}", post_pool_stake);
1021
1022 let stake_added = post_pool_stake
1024 .checked_sub(pre_pool_stake)
1025 .ok_or(SinglePoolError::ArithmeticOverflow)?;
1026
1027 let user_excess_lamports = post_pool_lamports
1030 .checked_sub(pool_stake_state.delegation.stake)
1031 .and_then(|amount| amount.checked_sub(pool_stake_meta.rent_exempt_reserve))
1032 .and_then(|amount| amount.checked_sub(pre_pool_excess_lamports))
1033 .ok_or(SinglePoolError::ArithmeticOverflow)?;
1034
1035 if user_stake_info.lamports() != 0 {
1037 return Err(SinglePoolError::UnexpectedMathError.into());
1038 }
1039
1040 let token_supply = {
1041 let pool_mint_data = pool_mint_info.try_borrow_data()?;
1042 let pool_mint = Mint::unpack_from_slice(&pool_mint_data)?;
1043 pool_mint.supply
1044 };
1045
1046 let new_pool_tokens = calculate_deposit_amount(token_supply, pre_pool_stake, stake_added)
1048 .ok_or(SinglePoolError::UnexpectedMathError)?;
1049
1050 if new_pool_tokens == 0 {
1051 return Err(SinglePoolError::DepositTooSmall.into());
1052 }
1053
1054 Self::token_mint_to(
1056 pool_info.key,
1057 token_program_info.clone(),
1058 pool_mint_info.clone(),
1059 user_token_account_info.clone(),
1060 pool_mint_authority_info.clone(),
1061 mint_authority_bump_seed,
1062 new_pool_tokens,
1063 )?;
1064
1065 if user_excess_lamports > 0 {
1067 Self::stake_withdraw(
1068 pool_info.key,
1069 pool_stake_info.clone(),
1070 pool_stake_authority_info.clone(),
1071 stake_authority_bump_seed,
1072 user_lamport_account_info.clone(),
1073 clock_info.clone(),
1074 stake_history_info.clone(),
1075 user_excess_lamports,
1076 )?;
1077 }
1078
1079 Ok(())
1080 }
1081
1082 fn process_withdraw_stake(
1083 program_id: &Pubkey,
1084 accounts: &[AccountInfo],
1085 user_stake_authority: &Pubkey,
1086 token_amount: u64,
1087 ) -> ProgramResult {
1088 let account_info_iter = &mut accounts.iter();
1089 let pool_info = next_account_info(account_info_iter)?;
1090 let pool_stake_info = next_account_info(account_info_iter)?;
1091 let pool_mint_info = {
1092 let account_info = next_account_info(account_info_iter)?;
1093 if check_pool_onramp_address(program_id, pool_info.key, account_info.key).is_ok() {
1096 next_account_info(account_info_iter)?
1097 } else {
1098 account_info
1099 }
1100 };
1101 let pool_stake_authority_info = next_account_info(account_info_iter)?;
1102 let pool_mint_authority_info = next_account_info(account_info_iter)?;
1103 let user_stake_info = next_account_info(account_info_iter)?;
1104 let user_token_account_info = next_account_info(account_info_iter)?;
1105 let clock_info = next_account_info(account_info_iter)?;
1106 let token_program_info = next_account_info(account_info_iter)?;
1107 let stake_program_info = next_account_info(account_info_iter)?;
1108
1109 SinglePool::from_account_info(pool_info, program_id)?;
1110
1111 check_pool_stake_address(program_id, pool_info.key, pool_stake_info.key)?;
1112 check_pool_mint_address(program_id, pool_info.key, pool_mint_info.key)?;
1113 let stake_authority_bump_seed = check_pool_stake_authority_address(
1114 program_id,
1115 pool_info.key,
1116 pool_stake_authority_info.key,
1117 )?;
1118 let mint_authority_bump_seed = check_pool_mint_authority_address(
1119 program_id,
1120 pool_info.key,
1121 pool_mint_authority_info.key,
1122 )?;
1123 check_token_program(token_program_info.key)?;
1124 check_stake_program(stake_program_info.key)?;
1125
1126 if pool_stake_info.key == user_stake_info.key {
1127 return Err(SinglePoolError::InvalidPoolStakeAccountUsage.into());
1128 }
1129
1130 let onramp_account_address = crate::find_pool_onramp_address(program_id, pool_info.key);
1131 if onramp_account_address == *user_stake_info.key {
1132 return Err(SinglePoolError::InvalidPoolStakeAccountUsage.into());
1133 }
1134
1135 let minimum_pool_balance = minimum_pool_balance()?;
1136
1137 let pre_pool_stake =
1138 get_stake_amount(pool_stake_info)?.saturating_sub(minimum_pool_balance);
1139 msg!("Available stake pre split {}", pre_pool_stake);
1140
1141 let token_supply = {
1142 let pool_mint_data = pool_mint_info.try_borrow_data()?;
1143 let pool_mint = Mint::unpack_from_slice(&pool_mint_data)?;
1144 pool_mint.supply
1145 };
1146
1147 let withdraw_stake = calculate_withdraw_amount(token_supply, pre_pool_stake, token_amount)
1149 .ok_or(SinglePoolError::UnexpectedMathError)?;
1150
1151 if withdraw_stake == 0 {
1152 return Err(SinglePoolError::WithdrawalTooSmall.into());
1153 }
1154
1155 if withdraw_stake > pre_pool_stake || withdraw_stake == pool_stake_info.lamports() {
1157 return Err(SinglePoolError::WithdrawalTooLarge.into());
1158 }
1159
1160 Self::token_burn(
1162 pool_info.key,
1163 token_program_info.clone(),
1164 user_token_account_info.clone(),
1165 pool_mint_info.clone(),
1166 pool_mint_authority_info.clone(),
1167 mint_authority_bump_seed,
1168 token_amount,
1169 )?;
1170
1171 Self::stake_split(
1173 pool_info.key,
1174 pool_stake_info.clone(),
1175 pool_stake_authority_info.clone(),
1176 stake_authority_bump_seed,
1177 withdraw_stake,
1178 user_stake_info.clone(),
1179 )?;
1180
1181 Self::stake_authorize(
1183 pool_info.key,
1184 user_stake_info.clone(),
1185 pool_stake_authority_info.clone(),
1186 stake_authority_bump_seed,
1187 user_stake_authority,
1188 clock_info.clone(),
1189 )?;
1190
1191 let post_pool_stake =
1192 get_stake_amount(pool_stake_info)?.saturating_sub(minimum_pool_balance);
1193 msg!("Available stake post split {}", post_pool_stake);
1194
1195 Ok(())
1196 }
1197
1198 fn process_create_pool_token_metadata(
1199 program_id: &Pubkey,
1200 accounts: &[AccountInfo],
1201 ) -> ProgramResult {
1202 let account_info_iter = &mut accounts.iter();
1203 let pool_info = next_account_info(account_info_iter)?;
1204 let pool_mint_info = next_account_info(account_info_iter)?;
1205 let pool_mint_authority_info = next_account_info(account_info_iter)?;
1206 let pool_mpl_authority_info = next_account_info(account_info_iter)?;
1207 let payer_info = next_account_info(account_info_iter)?;
1208 let metadata_info = next_account_info(account_info_iter)?;
1209 let mpl_token_metadata_program_info = next_account_info(account_info_iter)?;
1210 let system_program_info = next_account_info(account_info_iter)?;
1211
1212 let pool = SinglePool::from_account_info(pool_info, program_id)?;
1213
1214 let mint_authority_bump_seed = check_pool_mint_authority_address(
1215 program_id,
1216 pool_info.key,
1217 pool_mint_authority_info.key,
1218 )?;
1219 let mpl_authority_bump_seed = check_pool_mpl_authority_address(
1220 program_id,
1221 pool_info.key,
1222 pool_mpl_authority_info.key,
1223 )?;
1224 check_pool_mint_address(program_id, pool_info.key, pool_mint_info.key)?;
1225 check_system_program(system_program_info.key)?;
1226 check_account_owner(payer_info, &system_program::id())?;
1227 check_mpl_metadata_program(mpl_token_metadata_program_info.key)?;
1228 check_mpl_metadata_account_address(metadata_info.key, pool_mint_info.key)?;
1229
1230 if !payer_info.is_signer {
1231 msg!("Payer did not sign metadata creation");
1232 return Err(SinglePoolError::SignatureMissing.into());
1233 }
1234
1235 let vote_address_str = pool.vote_account_address.to_string();
1236 let token_name = format!("SPL Single Pool {}", &vote_address_str[0..15]);
1237 let token_symbol = format!("st{}", &vote_address_str[0..7]);
1238
1239 let new_metadata_instruction = create_metadata_accounts_v3(
1240 *mpl_token_metadata_program_info.key,
1241 *metadata_info.key,
1242 *pool_mint_info.key,
1243 *pool_mint_authority_info.key,
1244 *payer_info.key,
1245 *pool_mpl_authority_info.key,
1246 token_name,
1247 token_symbol,
1248 "".to_string(),
1249 );
1250
1251 let mint_authority_seeds = &[
1252 POOL_MINT_AUTHORITY_PREFIX,
1253 pool_info.key.as_ref(),
1254 &[mint_authority_bump_seed],
1255 ];
1256 let mpl_authority_seeds = &[
1257 POOL_MPL_AUTHORITY_PREFIX,
1258 pool_info.key.as_ref(),
1259 &[mpl_authority_bump_seed],
1260 ];
1261 let signers = &[&mint_authority_seeds[..], &mpl_authority_seeds[..]];
1262
1263 invoke_signed(
1264 &new_metadata_instruction,
1265 &[
1266 metadata_info.clone(),
1267 pool_mint_info.clone(),
1268 pool_mint_authority_info.clone(),
1269 payer_info.clone(),
1270 pool_mpl_authority_info.clone(),
1271 system_program_info.clone(),
1272 ],
1273 signers,
1274 )?;
1275
1276 Ok(())
1277 }
1278
1279 fn process_update_pool_token_metadata(
1280 program_id: &Pubkey,
1281 accounts: &[AccountInfo],
1282 name: String,
1283 symbol: String,
1284 uri: String,
1285 ) -> ProgramResult {
1286 let account_info_iter = &mut accounts.iter();
1287 let vote_account_info = next_account_info(account_info_iter)?;
1288 let pool_info = next_account_info(account_info_iter)?;
1289 let pool_mpl_authority_info = next_account_info(account_info_iter)?;
1290 let authorized_withdrawer_info = next_account_info(account_info_iter)?;
1291 let metadata_info = next_account_info(account_info_iter)?;
1292 let mpl_token_metadata_program_info = next_account_info(account_info_iter)?;
1293
1294 check_vote_account(vote_account_info)?;
1295 check_pool_address(program_id, vote_account_info.key, pool_info.key)?;
1296
1297 let pool = SinglePool::from_account_info(pool_info, program_id)?;
1298 if pool.vote_account_address != *vote_account_info.key {
1299 return Err(SinglePoolError::InvalidPoolAccount.into());
1300 }
1301
1302 let mpl_authority_bump_seed = check_pool_mpl_authority_address(
1303 program_id,
1304 pool_info.key,
1305 pool_mpl_authority_info.key,
1306 )?;
1307 let pool_mint_address = crate::find_pool_mint_address(program_id, pool_info.key);
1308 check_mpl_metadata_program(mpl_token_metadata_program_info.key)?;
1309 check_mpl_metadata_account_address(metadata_info.key, &pool_mint_address)?;
1310
1311 let vote_account_data = &vote_account_info.try_borrow_data()?;
1316 let vote_account_withdrawer = vote_account_data
1317 .get(VOTE_STATE_AUTHORIZED_WITHDRAWER_START..VOTE_STATE_AUTHORIZED_WITHDRAWER_END)
1318 .and_then(|x| Pubkey::try_from(x).ok())
1319 .ok_or(SinglePoolError::UnparseableVoteAccount)?;
1320
1321 if *authorized_withdrawer_info.key != vote_account_withdrawer {
1322 msg!("Vote account authorized withdrawer does not match the account provided.");
1323 return Err(SinglePoolError::InvalidMetadataSigner.into());
1324 }
1325
1326 if !authorized_withdrawer_info.is_signer {
1327 msg!("Vote account authorized withdrawer did not sign metadata update.");
1328 return Err(SinglePoolError::SignatureMissing.into());
1329 }
1330
1331 let update_metadata_accounts_instruction = update_metadata_accounts_v2(
1332 *mpl_token_metadata_program_info.key,
1333 *metadata_info.key,
1334 *pool_mpl_authority_info.key,
1335 None,
1336 Some(DataV2 {
1337 name,
1338 symbol,
1339 uri,
1340 seller_fee_basis_points: 0,
1341 creators: None,
1342 collection: None,
1343 uses: None,
1344 }),
1345 None,
1346 Some(true),
1347 );
1348
1349 let mpl_authority_seeds = &[
1350 POOL_MPL_AUTHORITY_PREFIX,
1351 pool_info.key.as_ref(),
1352 &[mpl_authority_bump_seed],
1353 ];
1354 let signers = &[&mpl_authority_seeds[..]];
1355
1356 invoke_signed(
1357 &update_metadata_accounts_instruction,
1358 &[metadata_info.clone(), pool_mpl_authority_info.clone()],
1359 signers,
1360 )?;
1361
1362 Ok(())
1363 }
1364
1365 fn process_initialize_pool_onramp(
1366 program_id: &Pubkey,
1367 accounts: &[AccountInfo],
1368 ) -> ProgramResult {
1369 let account_info_iter = &mut accounts.iter();
1370 let pool_info = next_account_info(account_info_iter)?;
1371 let pool_onramp_info = next_account_info(account_info_iter)?;
1372 let pool_stake_authority_info = next_account_info(account_info_iter)?;
1373 let rent_info = next_account_info(account_info_iter)?;
1374 let rent = &Rent::from_account_info(rent_info)?;
1375 let system_program_info = next_account_info(account_info_iter)?;
1376 let stake_program_info = next_account_info(account_info_iter)?;
1377
1378 SinglePool::from_account_info(pool_info, program_id)?;
1379
1380 let onramp_bump_seed =
1381 check_pool_onramp_address(program_id, pool_info.key, pool_onramp_info.key)?;
1382 let stake_authority_bump_seed = check_pool_stake_authority_address(
1383 program_id,
1384 pool_info.key,
1385 pool_stake_authority_info.key,
1386 )?;
1387 check_system_program(system_program_info.key)?;
1388 check_stake_program(stake_program_info.key)?;
1389
1390 let onramp_seeds = &[
1391 POOL_ONRAMP_PREFIX,
1392 pool_info.key.as_ref(),
1393 &[onramp_bump_seed],
1394 ];
1395 let onramp_signers = &[&onramp_seeds[..]];
1396
1397 let stake_authority_seeds = &[
1398 POOL_STAKE_AUTHORITY_PREFIX,
1399 pool_info.key.as_ref(),
1400 &[stake_authority_bump_seed],
1401 ];
1402 let stake_authority_signers = &[&stake_authority_seeds[..]];
1403
1404 let stake_space = std::mem::size_of::<stake::state::StakeStateV2>();
1406 let stake_rent = rent.minimum_balance(stake_space);
1407
1408 if pool_onramp_info.lamports() < stake_rent {
1409 return Err(SinglePoolError::WrongRentAmount.into());
1410 }
1411
1412 let authorized = stake::state::Authorized::auto(pool_stake_authority_info.key);
1413
1414 invoke_signed(
1415 &system_instruction::allocate(pool_onramp_info.key, stake_space as u64),
1416 &[pool_onramp_info.clone()],
1417 onramp_signers,
1418 )?;
1419
1420 invoke_signed(
1421 &system_instruction::assign(pool_onramp_info.key, stake_program_info.key),
1422 &[pool_onramp_info.clone()],
1423 onramp_signers,
1424 )?;
1425
1426 invoke_signed(
1427 &stake::instruction::initialize_checked(pool_onramp_info.key, &authorized),
1428 &[
1429 pool_onramp_info.clone(),
1430 rent_info.clone(),
1431 pool_stake_authority_info.clone(),
1432 pool_stake_authority_info.clone(),
1433 ],
1434 stake_authority_signers,
1435 )?;
1436
1437 Ok(())
1438 }
1439
1440 pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
1442 let instruction = SinglePoolInstruction::try_from_slice(input)?;
1443 match instruction {
1444 SinglePoolInstruction::InitializePool => {
1445 msg!("Instruction: InitializePool");
1446 Self::process_initialize_pool(program_id, accounts)
1447 }
1448 SinglePoolInstruction::ReplenishPool => {
1449 msg!("Instruction: ReplenishPool");
1450 Self::process_replenish_pool(program_id, accounts)
1451 }
1452 SinglePoolInstruction::DepositStake => {
1453 msg!("Instruction: DepositStake");
1454 Self::process_deposit_stake(program_id, accounts)
1455 }
1456 SinglePoolInstruction::WithdrawStake {
1457 user_stake_authority,
1458 token_amount,
1459 } => {
1460 msg!("Instruction: WithdrawStake");
1461 Self::process_withdraw_stake(
1462 program_id,
1463 accounts,
1464 &user_stake_authority,
1465 token_amount,
1466 )
1467 }
1468 SinglePoolInstruction::CreateTokenMetadata => {
1469 msg!("Instruction: CreateTokenMetadata");
1470 Self::process_create_pool_token_metadata(program_id, accounts)
1471 }
1472 SinglePoolInstruction::UpdateTokenMetadata { name, symbol, uri } => {
1473 msg!("Instruction: UpdateTokenMetadata");
1474 Self::process_update_pool_token_metadata(program_id, accounts, name, symbol, uri)
1475 }
1476 SinglePoolInstruction::InitializePoolOnRamp => {
1477 msg!("Instruction: InitializePoolOnRamp");
1478 Self::process_initialize_pool_onramp(program_id, accounts)
1479 }
1480 }
1481 }
1482}
1483
1484#[cfg(test)]
1485#[allow(clippy::arithmetic_side_effects)]
1486mod tests {
1487 use {
1488 super::*,
1489 approx::assert_relative_eq,
1490 rand::{
1491 distr::{Distribution, Uniform},
1492 rngs::StdRng,
1493 seq::IteratorRandom,
1494 Rng, SeedableRng,
1495 },
1496 std::collections::BTreeMap,
1497 test_case::test_case,
1498 };
1499
1500 const INFLATION_BASE_RATE: f64 = 0.0004;
1502
1503 #[derive(Clone, Debug, Default)]
1504 struct PoolState {
1505 pub token_supply: u64,
1506 pub total_stake: u64,
1507 pub user_token_balances: BTreeMap<Pubkey, u64>,
1508 }
1509 impl PoolState {
1510 #[rustfmt::skip]
1514 pub fn deposit(&mut self, user_pubkey: &Pubkey, stake_to_deposit: u64) -> Option<u64> {
1515 calculate_deposit_amount(self.token_supply, self.total_stake, stake_to_deposit)
1516 .and_then(|tokens_to_mint| self.token_supply.checked_add(tokens_to_mint)
1517 .and_then(|new_token_supply| self.total_stake.checked_add(stake_to_deposit)
1518 .and_then(|new_total_stake| self.user_token_balances.remove(user_pubkey).or(Some(0))
1519 .and_then(|old_user_token_balance| old_user_token_balance.checked_add(tokens_to_mint)
1520 .map(|new_user_token_balance| {
1521 self.token_supply = new_token_supply;
1522 self.total_stake = new_total_stake;
1523 let _ = self.user_token_balances.insert(*user_pubkey, new_user_token_balance);
1524 tokens_to_mint
1525 })))))
1526 }
1527
1528 #[rustfmt::skip]
1532 pub fn withdraw(&mut self, user_pubkey: &Pubkey, tokens_to_burn: u64) -> Option<u64> {
1533 calculate_withdraw_amount(self.token_supply, self.total_stake, tokens_to_burn)
1534 .and_then(|stake_to_withdraw| self.token_supply.checked_sub(tokens_to_burn)
1535 .and_then(|new_token_supply| self.total_stake.checked_sub(stake_to_withdraw)
1536 .and_then(|new_total_stake| self.user_token_balances.remove(user_pubkey)
1537 .and_then(|old_user_token_balance| old_user_token_balance.checked_sub(tokens_to_burn)
1538 .map(|new_user_token_balance| {
1539 self.token_supply = new_token_supply;
1540 self.total_stake = new_total_stake;
1541 let _ = self.user_token_balances.insert(*user_pubkey, new_user_token_balance);
1542 stake_to_withdraw
1543 })))))
1544 }
1545
1546 pub fn reward(&mut self, reward_amount: u64) {
1548 self.total_stake = self.total_stake.checked_add(reward_amount).unwrap();
1549 }
1550
1551 pub fn tokens(&self, user_pubkey: &Pubkey) -> u64 {
1553 *self.user_token_balances.get(user_pubkey).unwrap_or(&0)
1554 }
1555
1556 pub fn stake(&self, user_pubkey: &Pubkey) -> u64 {
1558 let tokens = self.tokens(user_pubkey);
1559 if tokens > 0 {
1560 u64::try_from(tokens as u128 * self.total_stake as u128 / self.token_supply as u128)
1561 .unwrap()
1562 } else {
1563 0
1564 }
1565 }
1566
1567 pub fn share(&self, user_pubkey: &Pubkey) -> f64 {
1569 let tokens = self.tokens(user_pubkey);
1570 if tokens > 0 {
1571 tokens as f64 / self.token_supply as f64
1572 } else {
1573 0.0
1574 }
1575 }
1576 }
1577
1578 #[test]
1581 fn simple_deposit_withdraw() {
1582 let mut pool = PoolState::default();
1583 let alice = Pubkey::new_unique();
1584 let bob = Pubkey::new_unique();
1585 let chad = Pubkey::new_unique();
1586
1587 pool.deposit(&alice, 250).unwrap();
1589 assert_eq!(pool.tokens(&alice), 250);
1590 assert_eq!(pool.token_supply, 250);
1591 assert_eq!(pool.total_stake, 250);
1592
1593 pool.deposit(&bob, 750).unwrap();
1595 assert_eq!(pool.tokens(&bob), 750);
1596 assert_eq!(pool.token_supply, 1000);
1597 assert_eq!(pool.total_stake, 1000);
1598
1599 assert_relative_eq!(pool.share(&alice), 0.25);
1603 assert_relative_eq!(pool.share(&bob), 0.75);
1604 pool.reward(1000);
1605 assert_eq!(pool.stake(&alice), pool.tokens(&alice) * 2);
1606 assert_eq!(pool.stake(&bob), pool.tokens(&bob) * 2);
1607 assert_relative_eq!(pool.share(&alice), 0.25);
1608 assert_relative_eq!(pool.share(&bob), 0.75);
1609
1610 let stake_removed = pool.withdraw(&alice, 125).unwrap();
1615 pool.deposit(&chad, 250).unwrap();
1616 assert_eq!(stake_removed, 250);
1617 assert_relative_eq!(pool.share(&alice), 0.125);
1618 assert_relative_eq!(pool.share(&bob), 0.75);
1619
1620 let stake_removed = pool.withdraw(&bob, 750).unwrap();
1622 assert_eq!(stake_removed, 1500);
1623 assert_relative_eq!(pool.share(&bob), 0.0);
1624 pool.withdraw(&chad, 125).unwrap();
1625 assert_relative_eq!(pool.share(&alice), 1.0);
1626 }
1627
1628 #[test_case(rand::random(), false, false; "no_rewards")]
1634 #[test_case(rand::random(), true, false; "with_rewards")]
1635 #[test_case(rand::random(), true, true; "no_minimum")]
1636 fn random_deposit_withdraw(seed: u64, with_rewards: bool, no_minimum: bool) {
1637 println!(
1638 "TEST SEED: {}. edit the test case to pass this value if needed to debug failures",
1639 seed
1640 );
1641 let mut prng = rand::rngs::StdRng::seed_from_u64(seed);
1642
1643 let deposit_range = Uniform::try_from(LAMPORTS_PER_SOL..LAMPORTS_PER_SOL * 1000).unwrap();
1648 let minnow_range = Uniform::try_from(1..LAMPORTS_PER_SOL).unwrap();
1649 let op_range = Uniform::try_from(if with_rewards { 0.0..1.0 } else { 0.0..0.65 }).unwrap();
1650 let std_range = Uniform::try_from(0.0..1.0).unwrap();
1651
1652 let deposit_amount = |prng: &mut StdRng| {
1653 if no_minimum && prng.random_bool(0.2) {
1654 minnow_range.sample(prng)
1655 } else {
1656 deposit_range.sample(prng)
1657 }
1658 };
1659
1660 for _ in 0..100 {
1662 let mut pool = PoolState::default();
1667
1668 let mut users = vec![];
1673 let user_count: usize = prng.random_range(1..=100);
1674 for _ in 0..user_count {
1675 let user = Pubkey::new_unique();
1676
1677 if prng.random_bool(0.5) {
1678 pool.deposit(&user, deposit_amount(&mut prng)).unwrap();
1679 }
1680
1681 users.push(user);
1682 }
1683
1684 for _ in 0..1000 {
1688 match op_range.sample(&mut prng) {
1689 n if n <= 0.35 => {
1692 let user = users.iter().choose(&mut prng).unwrap();
1693 let prev_share = pool.share(user);
1694 let prev_stake = pool.stake(user);
1695 let prev_token_supply = pool.token_supply;
1696 let prev_total_stake = pool.total_stake;
1697
1698 let stake_deposited = deposit_amount(&mut prng);
1699 let tokens_minted = pool.deposit(user, stake_deposited).unwrap();
1700
1701 assert_eq!(pool.total_stake - prev_total_stake, stake_deposited);
1703
1704 assert!(
1706 (pool.stake(user) as i64 - prev_stake as i64 - stake_deposited as i64)
1707 .abs()
1708 <= 2
1709 );
1710
1711 assert_eq!(pool.token_supply - prev_token_supply, tokens_minted);
1713
1714 if prev_total_stake > 0 {
1716 assert_relative_eq!(
1717 pool.share(user) - prev_share,
1718 pool.stake(user) as f64 / pool.total_stake as f64
1719 - prev_stake as f64 / prev_total_stake as f64,
1720 epsilon = 1e-6
1721 );
1722 }
1723 }
1724
1725 n if n > 0.35 && n <= 0.65 => {
1728 if let Some(user) = users
1729 .iter()
1730 .filter(|user| pool.tokens(user) > 0)
1731 .choose(&mut prng)
1732 {
1733 let prev_tokens = pool.tokens(user);
1734 let prev_share = pool.share(user);
1735 let prev_stake = pool.stake(user);
1736 let prev_token_supply = pool.token_supply;
1737 let prev_total_stake = pool.total_stake;
1738
1739 let tokens_burned = if std_range.sample(&mut prng) <= 0.1 {
1740 prev_tokens
1741 } else {
1742 prng.random_range(0..prev_tokens)
1743 };
1744 let stake_received = pool.withdraw(user, tokens_burned).unwrap();
1745
1746 assert_eq!(prev_total_stake - pool.total_stake, stake_received);
1748
1749 assert!(
1751 (prev_stake as i64
1752 - pool.stake(user) as i64
1753 - stake_received as i64)
1754 .abs()
1755 <= 2
1756 );
1757
1758 assert_eq!(prev_token_supply - pool.token_supply, tokens_burned);
1760
1761 if pool.total_stake > 0 {
1763 assert_relative_eq!(
1764 prev_share - pool.share(user),
1765 prev_stake as f64 / prev_total_stake as f64
1766 - pool.stake(user) as f64 / pool.total_stake as f64,
1767 epsilon = 1e-6
1768 );
1769 }
1770 };
1771 }
1772
1773 _ => {
1777 assert!(with_rewards);
1778
1779 let prev_shares_stakes = users
1780 .iter()
1781 .map(|user| (user, pool.share(user), pool.stake(user)))
1782 .filter(|(_, _, stake)| stake > &0)
1783 .collect::<Vec<_>>();
1784
1785 pool.reward((pool.total_stake as f64 * INFLATION_BASE_RATE) as u64);
1786
1787 for (user, prev_share, prev_stake) in prev_shares_stakes {
1788 assert_eq!(pool.share(user), prev_share);
1790
1791 let curr_stake = pool.stake(user);
1792 let stake_share = prev_stake as f64 * INFLATION_BASE_RATE;
1793 let stake_diff = (curr_stake - prev_stake) as f64;
1794
1795 assert!((stake_share - stake_diff).abs() <= 2.0);
1798 }
1799 }
1800 }
1801 }
1802 }
1803 }
1804}