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::{self as svsp_instruction, SinglePoolInstruction},
13 state::{SinglePool, SinglePoolAccountType},
14 DEPOSIT_SOL_FEE_BPS, MAX_BPS, MINT_DECIMALS, PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
15 PHANTOM_TOKEN_AMOUNT, POOL_MINT_AUTHORITY_PREFIX, POOL_MINT_PREFIX,
16 POOL_MPL_AUTHORITY_PREFIX, POOL_ONRAMP_PREFIX, POOL_PREFIX, POOL_STAKE_AUTHORITY_PREFIX,
17 POOL_STAKE_PREFIX, VOTE_STATE_AUTHORIZED_WITHDRAWER_END,
18 VOTE_STATE_AUTHORIZED_WITHDRAWER_START, VOTE_STATE_DISCRIMINATOR_END,
19 },
20 borsh::BorshDeserialize,
21 solana_account_info::{next_account_info, AccountInfo},
22 solana_borsh::v1::try_from_slice_unchecked,
23 solana_clock::Clock,
24 solana_cpi::{invoke, invoke_signed},
25 solana_msg::msg,
26 solana_native_token::LAMPORTS_PER_SOL,
27 solana_program_entrypoint::ProgramResult,
28 solana_program_error::ProgramError,
29 solana_program_pack::Pack,
30 solana_pubkey::Pubkey,
31 solana_rent::Rent,
32 solana_stake_interface::{
33 self as stake,
34 state::{Meta, Stake, StakeActivationStatus, StakeStateV2},
35 sysvar::stake_history::StakeHistorySysvar,
36 },
37 solana_system_interface::{instruction as system_instruction, program as system_program},
38 solana_sysvar::{Sysvar, SysvarSerialize},
39 solana_vote_interface::program as vote_program,
40 spl_token_interface::{self as spl_token, state::Mint},
41};
42
43fn pool_net_asset_value(
45 pool_stake_info: &AccountInfo,
46 pool_onramp_info: &AccountInfo,
47 rent: &Rent,
48) -> u64 {
49 let pool_rent_exempt_reserve = rent.minimum_balance(pool_stake_info.data_len());
51 let onramp_rent_exempt_reserve = rent.minimum_balance(pool_onramp_info.data_len());
52
53 let main_stake_value = pool_stake_info
56 .lamports()
57 .saturating_sub(pool_rent_exempt_reserve);
58
59 let onramp_value = pool_onramp_info
60 .lamports()
61 .saturating_sub(onramp_rent_exempt_reserve);
62
63 main_stake_value.saturating_add(onramp_value)
64}
65
66fn calculate_deposit_amount(
68 pre_token_supply: u64,
69 pre_pool_nav: u64,
70 user_deposit_amount: u64,
71) -> Option<u64> {
72 if pre_pool_nav == 0 || pre_token_supply == 0 {
73 Some(user_deposit_amount)
74 } else {
75 u64::try_from(
76 (user_deposit_amount as u128)
77 .checked_mul(pre_token_supply as u128)?
78 .checked_div(pre_pool_nav as u128)?,
79 )
80 .ok()
81 }
82}
83
84fn calculate_withdraw_amount(
86 pre_token_supply: u64,
87 pre_pool_nav: u64,
88 user_tokens_to_burn: u64,
89) -> Option<u64> {
90 let numerator = (user_tokens_to_burn as u128).checked_mul(pre_pool_nav as u128)?;
91 let denominator = pre_token_supply as u128;
92 if numerator < denominator || denominator == 0 {
93 Some(0)
94 } else {
95 u64::try_from(numerator.checked_div(denominator)?).ok()
96 }
97}
98
99fn get_stake_state(stake_account_info: &AccountInfo) -> Result<(Meta, Stake), ProgramError> {
101 match deserialize_stake(stake_account_info) {
102 Ok(StakeStateV2::Stake(meta, stake, _)) => Ok((meta, stake)),
103 _ => Err(SinglePoolError::WrongStakeState.into()),
104 }
105}
106
107fn get_stake_amount(stake_account_info: &AccountInfo) -> Result<u64, ProgramError> {
109 Ok(get_stake_state(stake_account_info)?.1.delegation.stake)
110}
111
112fn deserialize_stake(stake_account_info: &AccountInfo) -> Result<StakeStateV2, ProgramError> {
114 Ok(try_from_slice_unchecked::<StakeStateV2>(
115 &stake_account_info.data.borrow(),
116 )?)
117}
118
119fn is_stake_fully_active(stake_activation_status: &StakeActivationStatus) -> bool {
121 matches!(stake_activation_status, StakeActivationStatus {
122 effective,
123 activating: 0,
124 deactivating: 0,
125 } if *effective > 0)
126}
127
128fn is_stake_newly_activating(stake_activation_status: &StakeActivationStatus) -> bool {
130 matches!(stake_activation_status, StakeActivationStatus {
131 effective: 0,
132 activating,
133 deactivating: 0,
134 } if *activating > 0)
135}
136
137fn check_pool_address(
139 program_id: &Pubkey,
140 vote_account_address: &Pubkey,
141 check_address: &Pubkey,
142) -> Result<u8, ProgramError> {
143 check_pool_pda(
144 program_id,
145 vote_account_address,
146 check_address,
147 &crate::find_pool_address_and_bump,
148 "pool",
149 SinglePoolError::InvalidPoolAccount,
150 )
151}
152
153fn check_pool_stake_address(
155 program_id: &Pubkey,
156 pool_address: &Pubkey,
157 check_address: &Pubkey,
158) -> Result<u8, ProgramError> {
159 check_pool_pda(
160 program_id,
161 pool_address,
162 check_address,
163 &crate::find_pool_stake_address_and_bump,
164 "stake account",
165 SinglePoolError::InvalidPoolStakeAccount,
166 )
167}
168
169fn check_pool_onramp_address(
171 program_id: &Pubkey,
172 pool_address: &Pubkey,
173 check_address: &Pubkey,
174) -> Result<u8, ProgramError> {
175 check_pool_pda(
176 program_id,
177 pool_address,
178 check_address,
179 &crate::find_pool_onramp_address_and_bump,
180 "onramp account",
181 SinglePoolError::InvalidPoolOnRampAccount,
182 )
183}
184
185fn check_pool_mint_address(
187 program_id: &Pubkey,
188 pool_address: &Pubkey,
189 check_address: &Pubkey,
190) -> Result<u8, ProgramError> {
191 check_pool_pda(
192 program_id,
193 pool_address,
194 check_address,
195 &crate::find_pool_mint_address_and_bump,
196 "mint",
197 SinglePoolError::InvalidPoolMint,
198 )
199}
200
201fn check_pool_stake_authority_address(
203 program_id: &Pubkey,
204 pool_address: &Pubkey,
205 check_address: &Pubkey,
206) -> Result<u8, ProgramError> {
207 check_pool_pda(
208 program_id,
209 pool_address,
210 check_address,
211 &crate::find_pool_stake_authority_address_and_bump,
212 "stake authority",
213 SinglePoolError::InvalidPoolStakeAuthority,
214 )
215}
216
217fn check_pool_mint_authority_address(
219 program_id: &Pubkey,
220 pool_address: &Pubkey,
221 check_address: &Pubkey,
222) -> Result<u8, ProgramError> {
223 check_pool_pda(
224 program_id,
225 pool_address,
226 check_address,
227 &crate::find_pool_mint_authority_address_and_bump,
228 "mint authority",
229 SinglePoolError::InvalidPoolMintAuthority,
230 )
231}
232
233fn check_pool_mpl_authority_address(
235 program_id: &Pubkey,
236 pool_address: &Pubkey,
237 check_address: &Pubkey,
238) -> Result<u8, ProgramError> {
239 check_pool_pda(
240 program_id,
241 pool_address,
242 check_address,
243 &crate::find_pool_mpl_authority_address_and_bump,
244 "MPL authority",
245 SinglePoolError::InvalidPoolMplAuthority,
246 )
247}
248
249fn check_pool_pda(
250 program_id: &Pubkey,
251 base_address: &Pubkey,
252 check_address: &Pubkey,
253 pda_lookup_fn: &dyn Fn(&Pubkey, &Pubkey) -> (Pubkey, u8),
254 pda_name: &str,
255 pool_error: SinglePoolError,
256) -> Result<u8, ProgramError> {
257 let (derived_address, bump_seed) = pda_lookup_fn(program_id, base_address);
258 if *check_address != derived_address {
259 msg!(
260 "Incorrect {} address for base {}: expected {}, received {}",
261 pda_name,
262 base_address,
263 derived_address,
264 check_address,
265 );
266 Err(pool_error.into())
267 } else {
268 Ok(bump_seed)
269 }
270}
271
272fn check_vote_account(vote_account_info: &AccountInfo) -> Result<(), ProgramError> {
274 check_account_owner(vote_account_info, &vote_program::id())?;
275
276 let vote_account_data = &vote_account_info.try_borrow_data()?;
277 let state_variant = vote_account_data
278 .get(..VOTE_STATE_DISCRIMINATOR_END)
279 .and_then(|s| s.try_into().ok())
280 .ok_or(SinglePoolError::UnparseableVoteAccount)?;
281
282 #[allow(clippy::manual_range_patterns)]
283 match u32::from_le_bytes(state_variant) {
284 1 | 2 | 3 => Ok(()),
285 0 => Err(SinglePoolError::LegacyVoteAccount.into()),
286 _ => Err(SinglePoolError::UnparseableVoteAccount.into()),
287 }
288}
289
290fn check_pool_mint_with_supply(
293 program_id: &Pubkey,
294 pool_address: &Pubkey,
295 pool_mint_info: &AccountInfo,
296) -> Result<u64, ProgramError> {
297 check_pool_mint_address(program_id, pool_address, pool_mint_info.key)?;
298 let pool_mint_data = pool_mint_info.try_borrow_data()?;
299 let pool_mint = Mint::unpack_from_slice(&pool_mint_data)?;
300 Ok(pool_mint.supply.saturating_add(PHANTOM_TOKEN_AMOUNT))
301}
302
303fn check_mpl_metadata_account_address(
305 metadata_address: &Pubkey,
306 pool_mint: &Pubkey,
307) -> Result<(), ProgramError> {
308 let (metadata_account_pubkey, _) = find_metadata_account(pool_mint);
309 if metadata_account_pubkey != *metadata_address {
310 Err(SinglePoolError::InvalidMetadataAccount.into())
311 } else {
312 Ok(())
313 }
314}
315
316fn check_system_program(program_id: &Pubkey) -> Result<(), ProgramError> {
318 if *program_id != system_program::id() {
319 msg!(
320 "Expected system program {}, received {}",
321 system_program::id(),
322 program_id
323 );
324 Err(ProgramError::IncorrectProgramId)
325 } else {
326 Ok(())
327 }
328}
329
330fn check_token_program(address: &Pubkey) -> Result<(), ProgramError> {
332 if *address != spl_token::id() {
333 msg!(
334 "Incorrect token program, expected {}, received {}",
335 spl_token::id(),
336 address
337 );
338 Err(ProgramError::IncorrectProgramId)
339 } else {
340 Ok(())
341 }
342}
343
344fn check_stake_program(program_id: &Pubkey) -> Result<(), ProgramError> {
346 if *program_id != stake::program::id() {
347 msg!(
348 "Expected stake program {}, received {}",
349 stake::program::id(),
350 program_id
351 );
352 Err(ProgramError::IncorrectProgramId)
353 } else {
354 Ok(())
355 }
356}
357
358fn check_mpl_metadata_program(program_id: &Pubkey) -> Result<(), ProgramError> {
360 if *program_id != inline_mpl_token_metadata::id() {
361 msg!(
362 "Expected MPL metadata program {}, received {}",
363 inline_mpl_token_metadata::id(),
364 program_id
365 );
366 Err(ProgramError::IncorrectProgramId)
367 } else {
368 Ok(())
369 }
370}
371
372fn check_account_owner(
374 account_info: &AccountInfo,
375 program_id: &Pubkey,
376) -> Result<(), ProgramError> {
377 if *program_id != *account_info.owner {
378 msg!(
379 "Expected account to be owned by program {}, received {}",
380 program_id,
381 account_info.owner
382 );
383 Err(ProgramError::IncorrectProgramId)
384 } else {
385 Ok(())
386 }
387}
388
389fn minimum_pool_balance() -> Result<u64, ProgramError> {
396 Ok(std::cmp::max(
397 stake::tools::get_minimum_delegation()?,
398 LAMPORTS_PER_SOL,
399 ))
400}
401
402pub struct Processor {}
404impl Processor {
405 #[allow(clippy::too_many_arguments)]
406 fn stake_merge<'a>(
407 pool_account_key: &Pubkey,
408 source_account: AccountInfo<'a>,
409 authority: AccountInfo<'a>,
410 bump_seed: u8,
411 destination_account: AccountInfo<'a>,
412 clock: AccountInfo<'a>,
413 stake_history: AccountInfo<'a>,
414 ) -> Result<(), ProgramError> {
415 let authority_seeds = &[
416 POOL_STAKE_AUTHORITY_PREFIX,
417 pool_account_key.as_ref(),
418 &[bump_seed],
419 ];
420 let signers = &[&authority_seeds[..]];
421
422 invoke_signed(
423 &stake::instruction::merge(destination_account.key, source_account.key, authority.key)
424 [0],
425 &[
426 destination_account,
427 source_account,
428 clock,
429 stake_history,
430 authority,
431 ],
432 signers,
433 )
434 }
435
436 fn stake_split<'a>(
437 pool_account_key: &Pubkey,
438 stake_account: AccountInfo<'a>,
439 authority: AccountInfo<'a>,
440 bump_seed: u8,
441 amount: u64,
442 split_stake: AccountInfo<'a>,
443 ) -> Result<(), ProgramError> {
444 let authority_seeds = &[
445 POOL_STAKE_AUTHORITY_PREFIX,
446 pool_account_key.as_ref(),
447 &[bump_seed],
448 ];
449 let signers = &[&authority_seeds[..]];
450
451 let split_instruction =
452 stake::instruction::split(stake_account.key, authority.key, amount, split_stake.key);
453
454 invoke_signed(
455 split_instruction.last().unwrap(),
456 &[stake_account, split_stake, authority],
457 signers,
458 )
459 }
460
461 #[allow(clippy::too_many_arguments)]
462 fn stake_authorize<'a>(
463 pool_account_key: &Pubkey,
464 stake_account: AccountInfo<'a>,
465 stake_authority: AccountInfo<'a>,
466 bump_seed: u8,
467 new_stake_authority: &Pubkey,
468 clock: AccountInfo<'a>,
469 ) -> Result<(), ProgramError> {
470 let authority_seeds = &[
471 POOL_STAKE_AUTHORITY_PREFIX,
472 pool_account_key.as_ref(),
473 &[bump_seed],
474 ];
475 let signers = &[&authority_seeds[..]];
476
477 let authorize_instruction = stake::instruction::authorize(
478 stake_account.key,
479 stake_authority.key,
480 new_stake_authority,
481 stake::state::StakeAuthorize::Staker,
482 None,
483 );
484
485 invoke_signed(
486 &authorize_instruction,
487 &[
488 stake_account.clone(),
489 clock.clone(),
490 stake_authority.clone(),
491 ],
492 signers,
493 )?;
494
495 let authorize_instruction = stake::instruction::authorize(
496 stake_account.key,
497 stake_authority.key,
498 new_stake_authority,
499 stake::state::StakeAuthorize::Withdrawer,
500 None,
501 );
502 invoke_signed(
503 &authorize_instruction,
504 &[stake_account, clock, stake_authority],
505 signers,
506 )
507 }
508
509 #[allow(clippy::too_many_arguments)]
510 fn stake_withdraw<'a>(
511 pool_account_key: &Pubkey,
512 stake_account: AccountInfo<'a>,
513 stake_authority: AccountInfo<'a>,
514 bump_seed: u8,
515 destination_account: AccountInfo<'a>,
516 clock: AccountInfo<'a>,
517 stake_history: AccountInfo<'a>,
518 lamports: u64,
519 ) -> Result<(), ProgramError> {
520 let authority_seeds = &[
521 POOL_STAKE_AUTHORITY_PREFIX,
522 pool_account_key.as_ref(),
523 &[bump_seed],
524 ];
525 let signers = &[&authority_seeds[..]];
526
527 let withdraw_instruction = stake::instruction::withdraw(
528 stake_account.key,
529 stake_authority.key,
530 destination_account.key,
531 lamports,
532 None,
533 );
534
535 invoke_signed(
536 &withdraw_instruction,
537 &[
538 stake_account,
539 destination_account,
540 clock,
541 stake_history,
542 stake_authority,
543 ],
544 signers,
545 )
546 }
547
548 #[allow(clippy::too_many_arguments)]
549 fn token_mint_to<'a>(
550 pool_account_key: &Pubkey,
551 token_program: AccountInfo<'a>,
552 mint: AccountInfo<'a>,
553 destination: AccountInfo<'a>,
554 authority: AccountInfo<'a>,
555 bump_seed: u8,
556 amount: u64,
557 ) -> Result<(), ProgramError> {
558 let authority_seeds = &[
559 POOL_MINT_AUTHORITY_PREFIX,
560 pool_account_key.as_ref(),
561 &[bump_seed],
562 ];
563 let signers = &[&authority_seeds[..]];
564
565 let ix = spl_token::instruction::mint_to(
566 token_program.key,
567 mint.key,
568 destination.key,
569 authority.key,
570 &[],
571 amount,
572 )?;
573
574 invoke_signed(&ix, &[mint, destination, authority], signers)
575 }
576
577 #[allow(clippy::too_many_arguments)]
578 fn token_burn<'a>(
579 pool_account_key: &Pubkey,
580 token_program: AccountInfo<'a>,
581 burn_account: AccountInfo<'a>,
582 mint: AccountInfo<'a>,
583 authority: AccountInfo<'a>,
584 bump_seed: u8,
585 amount: u64,
586 ) -> Result<(), ProgramError> {
587 let authority_seeds = &[
588 POOL_MINT_AUTHORITY_PREFIX,
589 pool_account_key.as_ref(),
590 &[bump_seed],
591 ];
592 let signers = &[&authority_seeds[..]];
593
594 let ix = spl_token::instruction::burn(
595 token_program.key,
596 burn_account.key,
597 mint.key,
598 authority.key,
599 &[],
600 amount,
601 )?;
602
603 invoke_signed(&ix, &[burn_account, mint, authority], signers)
604 }
605
606 fn process_initialize_pool(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
607 let account_info_iter = &mut accounts.iter();
608 let vote_account_info = next_account_info(account_info_iter)?;
609 let pool_info = next_account_info(account_info_iter)?;
610 let pool_stake_info = next_account_info(account_info_iter)?;
611 let pool_mint_info = next_account_info(account_info_iter)?;
612 let pool_stake_authority_info = next_account_info(account_info_iter)?;
613 let pool_mint_authority_info = next_account_info(account_info_iter)?;
614 let rent_info = next_account_info(account_info_iter)?;
615 let rent = &Rent::from_account_info(rent_info)?;
616 let clock_info = next_account_info(account_info_iter)?;
617 let stake_history_info = next_account_info(account_info_iter)?;
618 let stake_config_info = next_account_info(account_info_iter)?;
619 let system_program_info = next_account_info(account_info_iter)?;
620 let token_program_info = next_account_info(account_info_iter)?;
621 let stake_program_info = next_account_info(account_info_iter)?;
622
623 check_vote_account(vote_account_info)?;
624 let pool_bump_seed = check_pool_address(program_id, vote_account_info.key, pool_info.key)?;
625 let stake_bump_seed =
626 check_pool_stake_address(program_id, pool_info.key, pool_stake_info.key)?;
627 let mint_bump_seed =
628 check_pool_mint_address(program_id, pool_info.key, pool_mint_info.key)?;
629 let stake_authority_bump_seed = check_pool_stake_authority_address(
630 program_id,
631 pool_info.key,
632 pool_stake_authority_info.key,
633 )?;
634 let mint_authority_bump_seed = check_pool_mint_authority_address(
635 program_id,
636 pool_info.key,
637 pool_mint_authority_info.key,
638 )?;
639 check_system_program(system_program_info.key)?;
640 check_token_program(token_program_info.key)?;
641 check_stake_program(stake_program_info.key)?;
642
643 let pool_seeds = &[
644 POOL_PREFIX,
645 vote_account_info.key.as_ref(),
646 &[pool_bump_seed],
647 ];
648 let pool_signers = &[&pool_seeds[..]];
649
650 let stake_seeds = &[
651 POOL_STAKE_PREFIX,
652 pool_info.key.as_ref(),
653 &[stake_bump_seed],
654 ];
655 let stake_signers = &[&stake_seeds[..]];
656
657 let mint_seeds = &[POOL_MINT_PREFIX, pool_info.key.as_ref(), &[mint_bump_seed]];
658 let mint_signers = &[&mint_seeds[..]];
659
660 let stake_authority_seeds = &[
661 POOL_STAKE_AUTHORITY_PREFIX,
662 pool_info.key.as_ref(),
663 &[stake_authority_bump_seed],
664 ];
665 let stake_authority_signers = &[&stake_authority_seeds[..]];
666
667 let mint_authority_seeds = &[
668 POOL_MINT_AUTHORITY_PREFIX,
669 pool_info.key.as_ref(),
670 &[mint_authority_bump_seed],
671 ];
672 let mint_authority_signers = &[&mint_authority_seeds[..]];
673
674 let pool_space = SinglePool::size_of();
676 if !rent.is_exempt(pool_info.lamports(), pool_space) {
677 return Err(SinglePoolError::WrongRentAmount.into());
678 }
679 if pool_info.data_len() != 0 {
680 return Err(SinglePoolError::PoolAlreadyInitialized.into());
681 }
682
683 invoke_signed(
684 &system_instruction::allocate(pool_info.key, pool_space as u64),
685 core::slice::from_ref(pool_info),
686 pool_signers,
687 )?;
688
689 invoke_signed(
690 &system_instruction::assign(pool_info.key, program_id),
691 core::slice::from_ref(pool_info),
692 pool_signers,
693 )?;
694
695 let mut pool = try_from_slice_unchecked::<SinglePool>(&pool_info.data.borrow())?;
696 pool.account_type = SinglePoolAccountType::Pool;
697 pool.vote_account_address = *vote_account_info.key;
698 borsh::to_writer(&mut pool_info.data.borrow_mut()[..], &pool)?;
699
700 let mint_space = spl_token::state::Mint::LEN;
702
703 invoke_signed(
704 &system_instruction::allocate(pool_mint_info.key, mint_space as u64),
705 core::slice::from_ref(pool_mint_info),
706 mint_signers,
707 )?;
708
709 invoke_signed(
710 &system_instruction::assign(pool_mint_info.key, token_program_info.key),
711 core::slice::from_ref(pool_mint_info),
712 mint_signers,
713 )?;
714
715 invoke_signed(
716 &spl_token::instruction::initialize_mint2(
717 token_program_info.key,
718 pool_mint_info.key,
719 pool_mint_authority_info.key,
720 None,
721 MINT_DECIMALS,
722 )?,
723 core::slice::from_ref(pool_mint_info),
724 mint_authority_signers,
725 )?;
726
727 let minimum_pool_balance = minimum_pool_balance()?;
730 let stake_space = StakeStateV2::size_of();
731 let stake_rent_plus_initial = rent
732 .minimum_balance(stake_space)
733 .saturating_add(minimum_pool_balance);
734
735 if pool_stake_info.lamports() < stake_rent_plus_initial {
736 return Err(SinglePoolError::WrongRentAmount.into());
737 }
738
739 let authorized = stake::state::Authorized::auto(pool_stake_authority_info.key);
740
741 invoke_signed(
742 &system_instruction::allocate(pool_stake_info.key, stake_space as u64),
743 core::slice::from_ref(pool_stake_info),
744 stake_signers,
745 )?;
746
747 invoke_signed(
748 &system_instruction::assign(pool_stake_info.key, stake_program_info.key),
749 core::slice::from_ref(pool_stake_info),
750 stake_signers,
751 )?;
752
753 invoke_signed(
754 &stake::instruction::initialize_checked(pool_stake_info.key, &authorized),
755 &[
756 pool_stake_info.clone(),
757 rent_info.clone(),
758 pool_stake_authority_info.clone(),
759 pool_stake_authority_info.clone(),
760 ],
761 stake_authority_signers,
762 )?;
763
764 invoke_signed(
766 &stake::instruction::delegate_stake(
767 pool_stake_info.key,
768 pool_stake_authority_info.key,
769 vote_account_info.key,
770 ),
771 &[
772 pool_stake_info.clone(),
773 vote_account_info.clone(),
774 clock_info.clone(),
775 stake_history_info.clone(),
776 stake_config_info.clone(),
777 pool_stake_authority_info.clone(),
778 ],
779 stake_authority_signers,
780 )?;
781
782 Ok(())
783 }
784
785 fn process_replenish_pool(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
786 let account_info_iter = &mut accounts.iter();
787 let vote_account_info = next_account_info(account_info_iter)?;
788 let pool_info = next_account_info(account_info_iter)?;
789 let pool_stake_info = next_account_info(account_info_iter)?;
790 let pool_onramp_info = next_account_info(account_info_iter)?;
791 let pool_stake_authority_info = next_account_info(account_info_iter)?;
792 let clock_info = next_account_info(account_info_iter)?;
793 let clock = &Clock::from_account_info(clock_info)?;
794 let stake_history_info = next_account_info(account_info_iter)?;
795 let stake_config_info = next_account_info(account_info_iter)?;
796 let stake_program_info = next_account_info(account_info_iter)?;
797
798 let rent = Rent::get()?;
799 let stake_history = &StakeHistorySysvar(clock.epoch);
800
801 check_vote_account(vote_account_info)?;
802 check_pool_address(program_id, vote_account_info.key, pool_info.key)?;
803
804 SinglePool::from_account_info(pool_info, program_id)?;
805
806 check_pool_stake_address(program_id, pool_info.key, pool_stake_info.key)?;
807 check_pool_onramp_address(program_id, pool_info.key, pool_onramp_info.key)?;
808 let stake_authority_bump_seed = check_pool_stake_authority_address(
809 program_id,
810 pool_info.key,
811 pool_stake_authority_info.key,
812 )?;
813 check_stake_program(stake_program_info.key)?;
814
815 let minimum_delegation = stake::tools::get_minimum_delegation()?;
816
817 let pool_rent_exempt_reserve = rent.minimum_balance(pool_stake_info.data_len());
819 let onramp_rent_exempt_reserve = rent.minimum_balance(pool_onramp_info.data_len());
820
821 let (_, pool_stake_state) = get_stake_state(pool_stake_info)?;
823 let pool_stake_status = pool_stake_state
824 .delegation
825 .stake_activating_and_deactivating(
826 clock.epoch,
827 stake_history,
828 PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
829 );
830 let pool_stake_is_fully_active = is_stake_fully_active(&pool_stake_status);
831
832 let (option_onramp_status, onramp_deactivation_epoch) =
835 match deserialize_stake(pool_onramp_info) {
836 Ok(StakeStateV2::Initialized(_)) => (None, u64::MAX),
837 Ok(StakeStateV2::Stake(_, stake, _)) => (
838 Some(stake.delegation.stake_activating_and_deactivating(
839 clock.epoch,
840 stake_history,
841 PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
842 )),
843 stake.delegation.deactivation_epoch,
844 ),
845 _ => return Err(SinglePoolError::OnRampDoesntExist.into()),
846 };
847
848 let stake_authority_seeds = &[
849 POOL_STAKE_AUTHORITY_PREFIX,
850 pool_info.key.as_ref(),
851 &[stake_authority_bump_seed],
852 ];
853 let stake_authority_signers = &[&stake_authority_seeds[..]];
854
855 if pool_stake_state.delegation.deactivation_epoch == clock.epoch
858 || (pool_stake_state.delegation.deactivation_epoch < clock.epoch
859 && pool_stake_status.effective == 0)
860 {
861 invoke_signed(
862 &stake::instruction::delegate_stake(
863 pool_stake_info.key,
864 pool_stake_authority_info.key,
865 vote_account_info.key,
866 ),
867 &[
868 pool_stake_info.clone(),
869 vote_account_info.clone(),
870 clock_info.clone(),
871 stake_history_info.clone(),
872 stake_config_info.clone(),
873 pool_stake_authority_info.clone(),
874 ],
875 stake_authority_signers,
876 )?;
877 }
878
879 if pool_stake_is_fully_active {
881 let pool_excess_lamports = pool_stake_info
883 .lamports()
884 .saturating_sub(pool_stake_state.delegation.stake)
885 .saturating_sub(pool_rent_exempt_reserve);
886
887 if let Some(ref onramp_status) = option_onramp_status {
889 if is_stake_fully_active(onramp_status) {
890 invoke_signed(
891 &stake::instruction::move_stake(
892 pool_onramp_info.key,
893 pool_stake_info.key,
894 pool_stake_authority_info.key,
895 onramp_status.effective,
896 ),
897 &[
898 pool_onramp_info.clone(),
899 pool_stake_info.clone(),
900 pool_stake_authority_info.clone(),
901 ],
902 stake_authority_signers,
903 )?;
904 }
905 }
906
907 if pool_excess_lamports > 0 {
909 invoke_signed(
910 &stake::instruction::move_lamports(
911 pool_stake_info.key,
912 pool_onramp_info.key,
913 pool_stake_authority_info.key,
914 pool_excess_lamports,
915 ),
916 &[
917 pool_stake_info.clone(),
918 pool_onramp_info.clone(),
919 pool_stake_authority_info.clone(),
920 ],
921 stake_authority_signers,
922 )?;
923 }
924
925 let onramp_non_rent_lamports = pool_onramp_info
930 .lamports()
931 .saturating_sub(onramp_rent_exempt_reserve);
932 let must_delegate_onramp = match option_onramp_status.unwrap_or_default() {
933 StakeActivationStatus {
935 effective: 0,
936 activating,
937 deactivating: 0,
938 } if activating > 0 => {
939 onramp_non_rent_lamports >= minimum_delegation
940 && onramp_non_rent_lamports > activating
941 }
942 StakeActivationStatus {
945 effective: _,
946 activating: 0,
947 deactivating,
948 } if deactivating == 0 || onramp_deactivation_epoch == clock.epoch => {
949 onramp_non_rent_lamports >= minimum_delegation
950 }
951 _ => false,
953 };
954
955 if must_delegate_onramp {
956 invoke_signed(
957 &stake::instruction::delegate_stake(
958 pool_onramp_info.key,
959 pool_stake_authority_info.key,
960 vote_account_info.key,
961 ),
962 &[
963 pool_onramp_info.clone(),
964 vote_account_info.clone(),
965 clock_info.clone(),
966 stake_history_info.clone(),
967 stake_config_info.clone(),
968 pool_stake_authority_info.clone(),
969 ],
970 stake_authority_signers,
971 )?;
972 }
973 }
974
975 Ok(())
976 }
977
978 fn process_deposit_stake(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
979 let account_info_iter = &mut accounts.iter();
980 let pool_info = next_account_info(account_info_iter)?;
981 let pool_stake_info = next_account_info(account_info_iter)?;
982 let pool_onramp_info = next_account_info(account_info_iter)?;
983 let pool_mint_info = next_account_info(account_info_iter)?;
984 let pool_stake_authority_info = next_account_info(account_info_iter)?;
985 let pool_mint_authority_info = next_account_info(account_info_iter)?;
986 let user_stake_info = next_account_info(account_info_iter)?;
987 let user_token_account_info = next_account_info(account_info_iter)?;
988 let user_lamport_account_info = next_account_info(account_info_iter)?;
989 let clock_info = next_account_info(account_info_iter)?;
990 let clock = &Clock::from_account_info(clock_info)?;
991 let stake_history_info = next_account_info(account_info_iter)?;
992 let token_program_info = next_account_info(account_info_iter)?;
993 let stake_program_info = next_account_info(account_info_iter)?;
994
995 let rent = &Rent::get()?;
996 let stake_history = &StakeHistorySysvar(clock.epoch);
997
998 SinglePool::from_account_info(pool_info, program_id)?;
999
1000 check_pool_stake_address(program_id, pool_info.key, pool_stake_info.key)?;
1001 check_pool_onramp_address(program_id, pool_info.key, pool_onramp_info.key)?;
1002 let token_supply = check_pool_mint_with_supply(program_id, pool_info.key, pool_mint_info)?;
1003 let stake_authority_bump_seed = check_pool_stake_authority_address(
1004 program_id,
1005 pool_info.key,
1006 pool_stake_authority_info.key,
1007 )?;
1008 let mint_authority_bump_seed = check_pool_mint_authority_address(
1009 program_id,
1010 pool_info.key,
1011 pool_mint_authority_info.key,
1012 )?;
1013 check_token_program(token_program_info.key)?;
1014 check_stake_program(stake_program_info.key)?;
1015
1016 if pool_stake_info.key == user_stake_info.key {
1017 return Err(SinglePoolError::InvalidPoolStakeAccountUsage.into());
1018 }
1019
1020 if pool_onramp_info.key == user_stake_info.key {
1021 return Err(SinglePoolError::InvalidPoolStakeAccountUsage.into());
1022 }
1023
1024 let (pre_pool_stake, pool_is_active, pool_is_activating) = {
1025 let (_, pool_stake_state) = get_stake_state(pool_stake_info)?;
1026 let pool_stake_status = pool_stake_state
1027 .delegation
1028 .stake_activating_and_deactivating(
1029 clock.epoch,
1030 stake_history,
1031 PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
1032 );
1033
1034 (
1035 pool_stake_state.delegation.stake,
1036 is_stake_fully_active(&pool_stake_status),
1037 is_stake_newly_activating(&pool_stake_status),
1038 )
1039 };
1040
1041 if !pool_is_active && !pool_is_activating {
1050 return Err(SinglePoolError::ReplenishRequired.into());
1051 } else if pool_is_active && pool_is_activating {
1052 unreachable!();
1054 };
1055
1056 match deserialize_stake(pool_onramp_info) {
1058 Ok(StakeStateV2::Initialized(_)) | Ok(StakeStateV2::Stake(_, _, _)) => (),
1059 _ => return Err(SinglePoolError::OnRampDoesntExist.into()),
1060 };
1061
1062 let pre_total_nav = pool_net_asset_value(pool_stake_info, pool_onramp_info, rent);
1064
1065 let pre_user_lamports = user_stake_info.lamports();
1066 let (user_stake_meta, user_stake_status) = match deserialize_stake(user_stake_info) {
1067 Ok(StakeStateV2::Stake(meta, stake, _)) => (
1068 meta,
1069 stake.delegation.stake_activating_and_deactivating(
1070 clock.epoch,
1071 stake_history,
1072 PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
1073 ),
1074 ),
1075 Ok(StakeStateV2::Initialized(meta)) => (meta, StakeActivationStatus::default()),
1076 _ => return Err(SinglePoolError::WrongStakeState.into()),
1077 };
1078
1079 if user_stake_meta.authorized
1081 != stake::state::Authorized::auto(pool_stake_authority_info.key)
1082 || user_stake_meta.lockup.is_in_force(clock, None)
1083 {
1084 return Err(SinglePoolError::WrongStakeState.into());
1085 }
1086
1087 if pool_is_active && is_stake_fully_active(&user_stake_status) {
1089 } else if pool_is_activating && is_stake_newly_activating(&user_stake_status) {
1091 } else if pool_is_activating && user_stake_status == StakeActivationStatus::default() {
1093 } else {
1095 return Err(SinglePoolError::WrongStakeState.into());
1097 }
1098
1099 Self::stake_merge(
1101 pool_info.key,
1102 user_stake_info.clone(),
1103 pool_stake_authority_info.clone(),
1104 stake_authority_bump_seed,
1105 pool_stake_info.clone(),
1106 clock_info.clone(),
1107 stake_history_info.clone(),
1108 )?;
1109
1110 let post_pool_stake = get_stake_amount(pool_stake_info)?;
1112 let new_stake_added = post_pool_stake
1113 .checked_sub(pre_pool_stake)
1114 .ok_or(SinglePoolError::ArithmeticOverflow)?;
1115
1116 let user_excess_lamports = pre_user_lamports
1118 .checked_sub(new_stake_added)
1119 .ok_or(SinglePoolError::ArithmeticOverflow)?;
1120
1121 if user_stake_info.lamports() != 0 {
1123 return Err(SinglePoolError::UnexpectedMathError.into());
1124 }
1125
1126 let new_pool_tokens =
1128 calculate_deposit_amount(token_supply, pre_total_nav, new_stake_added)
1129 .ok_or(SinglePoolError::UnexpectedMathError)?;
1130
1131 if new_pool_tokens == 0 {
1132 return Err(SinglePoolError::DepositTooSmall.into());
1133 }
1134
1135 Self::token_mint_to(
1137 pool_info.key,
1138 token_program_info.clone(),
1139 pool_mint_info.clone(),
1140 user_token_account_info.clone(),
1141 pool_mint_authority_info.clone(),
1142 mint_authority_bump_seed,
1143 new_pool_tokens,
1144 )?;
1145
1146 if user_excess_lamports > 0 {
1148 Self::stake_withdraw(
1149 pool_info.key,
1150 pool_stake_info.clone(),
1151 pool_stake_authority_info.clone(),
1152 stake_authority_bump_seed,
1153 user_lamport_account_info.clone(),
1154 clock_info.clone(),
1155 stake_history_info.clone(),
1156 user_excess_lamports,
1157 )?;
1158 }
1159
1160 Ok(())
1161 }
1162
1163 fn process_withdraw_stake(
1164 program_id: &Pubkey,
1165 accounts: &[AccountInfo],
1166 user_stake_authority: &Pubkey,
1167 token_amount: u64,
1168 ) -> ProgramResult {
1169 let account_info_iter = &mut accounts.iter();
1170 let pool_info = next_account_info(account_info_iter)?;
1171 let pool_stake_info = next_account_info(account_info_iter)?;
1172 let pool_onramp_info = next_account_info(account_info_iter)?;
1173 let pool_mint_info = next_account_info(account_info_iter)?;
1174 let pool_stake_authority_info = next_account_info(account_info_iter)?;
1175 let pool_mint_authority_info = next_account_info(account_info_iter)?;
1176 let user_stake_info = next_account_info(account_info_iter)?;
1177 let user_token_account_info = next_account_info(account_info_iter)?;
1178 let clock_info = next_account_info(account_info_iter)?;
1179 let clock = &Clock::from_account_info(clock_info)?;
1180 let token_program_info = next_account_info(account_info_iter)?;
1181 let stake_program_info = next_account_info(account_info_iter)?;
1182
1183 let rent = &Rent::get()?;
1184 let stake_history = &StakeHistorySysvar(clock.epoch);
1185
1186 SinglePool::from_account_info(pool_info, program_id)?;
1187
1188 check_pool_stake_address(program_id, pool_info.key, pool_stake_info.key)?;
1189 check_pool_onramp_address(program_id, pool_info.key, pool_onramp_info.key)?;
1190 let token_supply = check_pool_mint_with_supply(program_id, pool_info.key, pool_mint_info)?;
1191 let stake_authority_bump_seed = check_pool_stake_authority_address(
1192 program_id,
1193 pool_info.key,
1194 pool_stake_authority_info.key,
1195 )?;
1196 let mint_authority_bump_seed = check_pool_mint_authority_address(
1197 program_id,
1198 pool_info.key,
1199 pool_mint_authority_info.key,
1200 )?;
1201 check_token_program(token_program_info.key)?;
1202 check_stake_program(stake_program_info.key)?;
1203
1204 if pool_stake_info.key == user_stake_info.key {
1205 return Err(SinglePoolError::InvalidPoolStakeAccountUsage.into());
1206 }
1207
1208 if pool_onramp_info.key == user_stake_info.key {
1209 return Err(SinglePoolError::InvalidPoolStakeAccountUsage.into());
1210 }
1211
1212 if token_amount == 0 {
1213 return Err(SinglePoolError::WithdrawalTooSmall.into());
1214 }
1215
1216 let minimum_delegation = stake::tools::get_minimum_delegation()?;
1217
1218 let (withdrawable_value, pool_is_fully_inactive) = {
1223 let (_, pool_stake_state) = get_stake_state(pool_stake_info)?;
1224 let pool_stake_status = pool_stake_state
1225 .delegation
1226 .stake_activating_and_deactivating(
1227 clock.epoch,
1228 stake_history,
1229 PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
1230 );
1231
1232 if pool_stake_status == StakeActivationStatus::default() {
1235 (
1236 pool_stake_info
1237 .lamports()
1238 .saturating_sub(rent.minimum_balance(pool_stake_info.data_len())),
1239 true,
1240 )
1241 } else {
1242 (pool_stake_state.delegation.stake, false)
1243 }
1244 };
1245
1246 match deserialize_stake(pool_onramp_info) {
1249 Ok(StakeStateV2::Initialized(_)) | Ok(StakeStateV2::Stake(_, _, _)) => (),
1250 _ => return Err(SinglePoolError::OnRampDoesntExist.into()),
1251 };
1252
1253 let pre_total_nav = pool_net_asset_value(pool_stake_info, pool_onramp_info, rent);
1255
1256 let stake_to_withdraw =
1258 calculate_withdraw_amount(token_supply, pre_total_nav, token_amount)
1259 .ok_or(SinglePoolError::UnexpectedMathError)?;
1260
1261 if stake_to_withdraw == 0 {
1263 return Err(SinglePoolError::WithdrawalTooSmall.into());
1264 }
1265
1266 if withdrawable_value.saturating_sub(stake_to_withdraw) < minimum_delegation {
1269 return Err(SinglePoolError::WithdrawalViolatesPoolRequirements.into());
1270 }
1271
1272 if stake_to_withdraw == pool_stake_info.lamports() {
1274 return Err(SinglePoolError::WithdrawalViolatesPoolRequirements.into());
1275 }
1276
1277 if !pool_is_fully_inactive && stake_to_withdraw < minimum_delegation {
1279 return Err(SinglePoolError::WithdrawalTooSmall.into());
1280 }
1281
1282 if stake_to_withdraw > withdrawable_value {
1286 return Err(SinglePoolError::WithdrawalTooLarge.into());
1287 }
1288
1289 Self::token_burn(
1291 pool_info.key,
1292 token_program_info.clone(),
1293 user_token_account_info.clone(),
1294 pool_mint_info.clone(),
1295 pool_mint_authority_info.clone(),
1296 mint_authority_bump_seed,
1297 token_amount,
1298 )?;
1299
1300 Self::stake_split(
1302 pool_info.key,
1303 pool_stake_info.clone(),
1304 pool_stake_authority_info.clone(),
1305 stake_authority_bump_seed,
1306 stake_to_withdraw,
1307 user_stake_info.clone(),
1308 )?;
1309
1310 Self::stake_authorize(
1312 pool_info.key,
1313 user_stake_info.clone(),
1314 pool_stake_authority_info.clone(),
1315 stake_authority_bump_seed,
1316 user_stake_authority,
1317 clock_info.clone(),
1318 )?;
1319
1320 Ok(())
1321 }
1322
1323 fn process_create_pool_token_metadata(
1324 program_id: &Pubkey,
1325 accounts: &[AccountInfo],
1326 ) -> ProgramResult {
1327 let account_info_iter = &mut accounts.iter();
1328 let pool_info = next_account_info(account_info_iter)?;
1329 let pool_mint_info = next_account_info(account_info_iter)?;
1330 let pool_mint_authority_info = next_account_info(account_info_iter)?;
1331 let pool_mpl_authority_info = next_account_info(account_info_iter)?;
1332 let payer_info = next_account_info(account_info_iter)?;
1333 let metadata_info = next_account_info(account_info_iter)?;
1334 let mpl_token_metadata_program_info = next_account_info(account_info_iter)?;
1335 let system_program_info = next_account_info(account_info_iter)?;
1336
1337 let pool = SinglePool::from_account_info(pool_info, program_id)?;
1338
1339 let mint_authority_bump_seed = check_pool_mint_authority_address(
1340 program_id,
1341 pool_info.key,
1342 pool_mint_authority_info.key,
1343 )?;
1344 let mpl_authority_bump_seed = check_pool_mpl_authority_address(
1345 program_id,
1346 pool_info.key,
1347 pool_mpl_authority_info.key,
1348 )?;
1349 check_pool_mint_address(program_id, pool_info.key, pool_mint_info.key)?;
1350 check_system_program(system_program_info.key)?;
1351 check_account_owner(payer_info, &system_program::id())?;
1352 check_mpl_metadata_program(mpl_token_metadata_program_info.key)?;
1353 check_mpl_metadata_account_address(metadata_info.key, pool_mint_info.key)?;
1354
1355 if !payer_info.is_signer {
1356 msg!("Payer did not sign metadata creation");
1357 return Err(SinglePoolError::SignatureMissing.into());
1358 }
1359
1360 let vote_address_str = pool.vote_account_address.to_string();
1361 let token_name = format!("SPL Single Pool {}", &vote_address_str[0..15]);
1362 let token_symbol = format!("st{}", &vote_address_str[0..7]);
1363
1364 let new_metadata_instruction = create_metadata_accounts_v3(
1365 *mpl_token_metadata_program_info.key,
1366 *metadata_info.key,
1367 *pool_mint_info.key,
1368 *pool_mint_authority_info.key,
1369 *payer_info.key,
1370 *pool_mpl_authority_info.key,
1371 token_name,
1372 token_symbol,
1373 "".to_string(),
1374 );
1375
1376 let mint_authority_seeds = &[
1377 POOL_MINT_AUTHORITY_PREFIX,
1378 pool_info.key.as_ref(),
1379 &[mint_authority_bump_seed],
1380 ];
1381 let mpl_authority_seeds = &[
1382 POOL_MPL_AUTHORITY_PREFIX,
1383 pool_info.key.as_ref(),
1384 &[mpl_authority_bump_seed],
1385 ];
1386 let signers = &[&mint_authority_seeds[..], &mpl_authority_seeds[..]];
1387
1388 invoke_signed(
1389 &new_metadata_instruction,
1390 &[
1391 metadata_info.clone(),
1392 pool_mint_info.clone(),
1393 pool_mint_authority_info.clone(),
1394 payer_info.clone(),
1395 pool_mpl_authority_info.clone(),
1396 system_program_info.clone(),
1397 ],
1398 signers,
1399 )?;
1400
1401 Ok(())
1402 }
1403
1404 fn process_update_pool_token_metadata(
1405 program_id: &Pubkey,
1406 accounts: &[AccountInfo],
1407 name: String,
1408 symbol: String,
1409 uri: String,
1410 ) -> ProgramResult {
1411 let account_info_iter = &mut accounts.iter();
1412 let vote_account_info = next_account_info(account_info_iter)?;
1413 let pool_info = next_account_info(account_info_iter)?;
1414 let pool_mpl_authority_info = next_account_info(account_info_iter)?;
1415 let authorized_withdrawer_info = next_account_info(account_info_iter)?;
1416 let metadata_info = next_account_info(account_info_iter)?;
1417 let mpl_token_metadata_program_info = next_account_info(account_info_iter)?;
1418
1419 check_vote_account(vote_account_info)?;
1420 check_pool_address(program_id, vote_account_info.key, pool_info.key)?;
1421
1422 let pool = SinglePool::from_account_info(pool_info, program_id)?;
1423 if pool.vote_account_address != *vote_account_info.key {
1424 return Err(SinglePoolError::InvalidPoolAccount.into());
1425 }
1426
1427 let mpl_authority_bump_seed = check_pool_mpl_authority_address(
1428 program_id,
1429 pool_info.key,
1430 pool_mpl_authority_info.key,
1431 )?;
1432 let pool_mint_address = crate::find_pool_mint_address(program_id, pool_info.key);
1433 check_mpl_metadata_program(mpl_token_metadata_program_info.key)?;
1434 check_mpl_metadata_account_address(metadata_info.key, &pool_mint_address)?;
1435
1436 let vote_account_data = &vote_account_info.try_borrow_data()?;
1441 let vote_account_withdrawer = vote_account_data
1442 .get(VOTE_STATE_AUTHORIZED_WITHDRAWER_START..VOTE_STATE_AUTHORIZED_WITHDRAWER_END)
1443 .and_then(|x| Pubkey::try_from(x).ok())
1444 .ok_or(SinglePoolError::UnparseableVoteAccount)?;
1445
1446 if *authorized_withdrawer_info.key != vote_account_withdrawer {
1447 msg!("Vote account authorized withdrawer does not match the account provided.");
1448 return Err(SinglePoolError::InvalidMetadataSigner.into());
1449 }
1450
1451 if !authorized_withdrawer_info.is_signer {
1452 msg!("Vote account authorized withdrawer did not sign metadata update.");
1453 return Err(SinglePoolError::SignatureMissing.into());
1454 }
1455
1456 let update_metadata_accounts_instruction = update_metadata_accounts_v2(
1457 *mpl_token_metadata_program_info.key,
1458 *metadata_info.key,
1459 *pool_mpl_authority_info.key,
1460 None,
1461 Some(DataV2 {
1462 name,
1463 symbol,
1464 uri,
1465 seller_fee_basis_points: 0,
1466 creators: None,
1467 collection: None,
1468 uses: None,
1469 }),
1470 None,
1471 Some(true),
1472 );
1473
1474 let mpl_authority_seeds = &[
1475 POOL_MPL_AUTHORITY_PREFIX,
1476 pool_info.key.as_ref(),
1477 &[mpl_authority_bump_seed],
1478 ];
1479 let signers = &[&mpl_authority_seeds[..]];
1480
1481 invoke_signed(
1482 &update_metadata_accounts_instruction,
1483 &[metadata_info.clone(), pool_mpl_authority_info.clone()],
1484 signers,
1485 )?;
1486
1487 Ok(())
1488 }
1489
1490 fn process_initialize_pool_onramp(
1491 program_id: &Pubkey,
1492 accounts: &[AccountInfo],
1493 ) -> ProgramResult {
1494 let account_info_iter = &mut accounts.iter();
1495 let pool_info = next_account_info(account_info_iter)?;
1496 let pool_onramp_info = next_account_info(account_info_iter)?;
1497 let pool_stake_authority_info = next_account_info(account_info_iter)?;
1498 let rent_info = next_account_info(account_info_iter)?;
1499 let rent = &Rent::from_account_info(rent_info)?;
1500 let system_program_info = next_account_info(account_info_iter)?;
1501 let stake_program_info = next_account_info(account_info_iter)?;
1502
1503 SinglePool::from_account_info(pool_info, program_id)?;
1504
1505 let onramp_bump_seed =
1506 check_pool_onramp_address(program_id, pool_info.key, pool_onramp_info.key)?;
1507 let stake_authority_bump_seed = check_pool_stake_authority_address(
1508 program_id,
1509 pool_info.key,
1510 pool_stake_authority_info.key,
1511 )?;
1512 check_system_program(system_program_info.key)?;
1513 check_stake_program(stake_program_info.key)?;
1514
1515 let onramp_seeds = &[
1516 POOL_ONRAMP_PREFIX,
1517 pool_info.key.as_ref(),
1518 &[onramp_bump_seed],
1519 ];
1520 let onramp_signers = &[&onramp_seeds[..]];
1521
1522 let stake_authority_seeds = &[
1523 POOL_STAKE_AUTHORITY_PREFIX,
1524 pool_info.key.as_ref(),
1525 &[stake_authority_bump_seed],
1526 ];
1527 let stake_authority_signers = &[&stake_authority_seeds[..]];
1528
1529 let stake_space = StakeStateV2::size_of();
1531 let stake_rent = rent.minimum_balance(stake_space);
1532
1533 if pool_onramp_info.lamports() < stake_rent {
1534 return Err(SinglePoolError::WrongRentAmount.into());
1535 }
1536
1537 let authorized = stake::state::Authorized::auto(pool_stake_authority_info.key);
1538
1539 invoke_signed(
1540 &system_instruction::allocate(pool_onramp_info.key, stake_space as u64),
1541 core::slice::from_ref(pool_onramp_info),
1542 onramp_signers,
1543 )?;
1544
1545 invoke_signed(
1546 &system_instruction::assign(pool_onramp_info.key, stake_program_info.key),
1547 core::slice::from_ref(pool_onramp_info),
1548 onramp_signers,
1549 )?;
1550
1551 invoke_signed(
1552 &stake::instruction::initialize_checked(pool_onramp_info.key, &authorized),
1553 &[
1554 pool_onramp_info.clone(),
1555 rent_info.clone(),
1556 pool_stake_authority_info.clone(),
1557 pool_stake_authority_info.clone(),
1558 ],
1559 stake_authority_signers,
1560 )?;
1561
1562 Ok(())
1563 }
1564
1565 fn process_deposit_sol(
1566 program_id: &Pubkey,
1567 accounts: &[AccountInfo],
1568 deposit_amount: u64,
1569 ) -> ProgramResult {
1570 let account_info_iter = &mut accounts.iter();
1571 let vote_account_info = next_account_info(account_info_iter)?;
1572 let pool_info = next_account_info(account_info_iter)?;
1573 let pool_stake_info = next_account_info(account_info_iter)?;
1574 let pool_onramp_info = next_account_info(account_info_iter)?;
1575 let pool_mint_info = next_account_info(account_info_iter)?;
1576 let pool_stake_authority_info = next_account_info(account_info_iter)?;
1577 let pool_mint_authority_info = next_account_info(account_info_iter)?;
1578 let user_lamport_account_info = next_account_info(account_info_iter)?;
1579 let user_token_account_info = next_account_info(account_info_iter)?;
1580 let clock_info = next_account_info(account_info_iter)?;
1581 let clock = &Clock::from_account_info(clock_info)?;
1582 let stake_history_info = next_account_info(account_info_iter)?;
1583 let stake_config_info = next_account_info(account_info_iter)?;
1584 let system_program_info = next_account_info(account_info_iter)?;
1585 let token_program_info = next_account_info(account_info_iter)?;
1586 let stake_program_info = next_account_info(account_info_iter)?;
1587 let svsp_program_info = next_account_info(account_info_iter)?;
1588
1589 let rent = Rent::get()?;
1590 let stake_history = &StakeHistorySysvar(clock.epoch);
1591
1592 check_vote_account(vote_account_info)?;
1593 check_pool_address(program_id, vote_account_info.key, pool_info.key)?;
1594
1595 SinglePool::from_account_info(pool_info, program_id)?;
1596
1597 check_pool_stake_address(program_id, pool_info.key, pool_stake_info.key)?;
1598 check_pool_onramp_address(program_id, pool_info.key, pool_onramp_info.key)?;
1599 let token_supply = check_pool_mint_with_supply(program_id, pool_info.key, pool_mint_info)?;
1600 check_pool_stake_authority_address(
1601 program_id,
1602 pool_info.key,
1603 pool_stake_authority_info.key,
1604 )?;
1605 let mint_authority_bump_seed = check_pool_mint_authority_address(
1606 program_id,
1607 pool_info.key,
1608 pool_mint_authority_info.key,
1609 )?;
1610 check_system_program(system_program_info.key)?;
1611 check_token_program(token_program_info.key)?;
1612 check_stake_program(stake_program_info.key)?;
1613 if svsp_program_info.key != program_id {
1614 msg!(
1615 "Expected SVSP program {}, received {}",
1616 program_id,
1617 svsp_program_info.key,
1618 );
1619 return Err(ProgramError::IncorrectProgramId);
1620 }
1621
1622 if deposit_amount == 0 {
1623 return Err(SinglePoolError::DepositTooSmall.into());
1624 }
1625
1626 {
1628 let (_, pool_stake_state) = get_stake_state(pool_stake_info)?;
1629 let pool_stake_status = pool_stake_state
1630 .delegation
1631 .stake_activating_and_deactivating(
1632 clock.epoch,
1633 stake_history,
1634 PERPETUAL_NEW_WARMUP_COOLDOWN_RATE_EPOCH,
1635 );
1636
1637 if !is_stake_fully_active(&pool_stake_status) {
1638 return Err(SinglePoolError::WrongStakeState.into());
1639 }
1640 };
1641
1642 match deserialize_stake(pool_onramp_info) {
1644 Ok(StakeStateV2::Initialized(_)) | Ok(StakeStateV2::Stake(_, _, _)) => (),
1645 _ => return Err(SinglePoolError::OnRampDoesntExist.into()),
1646 }
1647
1648 if !user_lamport_account_info.is_signer
1650 || user_lamport_account_info.owner != &system_program::id()
1651 || user_lamport_account_info.lamports() < deposit_amount
1652 {
1653 return Err(SinglePoolError::InvalidDepositSolSource.into());
1654 }
1655
1656 let pre_total_nav = pool_net_asset_value(pool_stake_info, pool_onramp_info, &rent);
1657 let pre_onramp_lamports = pool_onramp_info.lamports();
1658
1659 invoke(
1661 &system_instruction::transfer(
1662 user_lamport_account_info.key,
1663 pool_onramp_info.key,
1664 deposit_amount,
1665 ),
1666 &[user_lamport_account_info.clone(), pool_onramp_info.clone()],
1667 )?;
1668
1669 if pool_onramp_info.lamports() != pre_onramp_lamports.saturating_add(deposit_amount) {
1671 return Err(SinglePoolError::UnexpectedMathError.into());
1672 }
1673
1674 let new_pool_tokens = {
1675 let raw_tokens = calculate_deposit_amount(token_supply, pre_total_nav, deposit_amount)
1676 .ok_or(SinglePoolError::UnexpectedMathError)?;
1677
1678 let deposit_sol_fee = raw_tokens
1681 .checked_mul(DEPOSIT_SOL_FEE_BPS)
1682 .and_then(|n| n.checked_div(MAX_BPS))
1683 .ok_or(SinglePoolError::UnexpectedMathError)?;
1684
1685 if deposit_sol_fee == 0 {
1687 return Err(SinglePoolError::DepositTooSmall.into());
1688 }
1689
1690 raw_tokens.saturating_sub(deposit_sol_fee)
1691 };
1692
1693 if new_pool_tokens == 0 {
1695 return Err(SinglePoolError::UnexpectedMathError.into());
1696 }
1697
1698 Self::token_mint_to(
1700 pool_info.key,
1701 token_program_info.clone(),
1702 pool_mint_info.clone(),
1703 user_token_account_info.clone(),
1704 pool_mint_authority_info.clone(),
1705 mint_authority_bump_seed,
1706 new_pool_tokens,
1707 )?;
1708
1709 invoke(
1711 &svsp_instruction::replenish_pool(program_id, vote_account_info.key),
1712 &[
1713 vote_account_info.clone(),
1714 pool_info.clone(),
1715 pool_stake_info.clone(),
1716 pool_onramp_info.clone(),
1717 pool_stake_authority_info.clone(),
1718 clock_info.clone(),
1719 stake_history_info.clone(),
1720 stake_config_info.clone(),
1721 stake_program_info.clone(),
1722 ],
1723 )?;
1724
1725 Ok(())
1726 }
1727
1728 pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
1730 let instruction = SinglePoolInstruction::try_from_slice(input)?;
1731 match instruction {
1732 SinglePoolInstruction::InitializePool => {
1733 msg!("Instruction: InitializePool");
1734 Self::process_initialize_pool(program_id, accounts)
1735 }
1736 SinglePoolInstruction::ReplenishPool => {
1737 msg!("Instruction: ReplenishPool");
1738 Self::process_replenish_pool(program_id, accounts)
1739 }
1740 SinglePoolInstruction::DepositStake => {
1741 msg!("Instruction: DepositStake");
1742 Self::process_deposit_stake(program_id, accounts)
1743 }
1744 SinglePoolInstruction::WithdrawStake {
1745 user_stake_authority,
1746 token_amount,
1747 } => {
1748 msg!("Instruction: WithdrawStake");
1749 Self::process_withdraw_stake(
1750 program_id,
1751 accounts,
1752 &user_stake_authority,
1753 token_amount,
1754 )
1755 }
1756 SinglePoolInstruction::CreateTokenMetadata => {
1757 msg!("Instruction: CreateTokenMetadata");
1758 Self::process_create_pool_token_metadata(program_id, accounts)
1759 }
1760 SinglePoolInstruction::UpdateTokenMetadata { name, symbol, uri } => {
1761 msg!("Instruction: UpdateTokenMetadata");
1762 Self::process_update_pool_token_metadata(program_id, accounts, name, symbol, uri)
1763 }
1764 SinglePoolInstruction::InitializePoolOnRamp => {
1765 msg!("Instruction: InitializePoolOnRamp");
1766 Self::process_initialize_pool_onramp(program_id, accounts)
1767 }
1768 SinglePoolInstruction::DepositSol { lamports } => {
1769 msg!("Instruction: DepositSol");
1770 Self::process_deposit_sol(program_id, accounts, lamports)
1771 }
1772 }
1773 }
1774}
1775
1776#[cfg(test)]
1777#[allow(clippy::arithmetic_side_effects)]
1778mod tests {
1779 use {
1780 super::*,
1781 approx::assert_relative_eq,
1782 rand::{
1783 distr::{Distribution, Uniform},
1784 rngs::StdRng,
1785 seq::IteratorRandom,
1786 Rng, SeedableRng,
1787 },
1788 std::collections::BTreeMap,
1789 test_case::test_case,
1790 };
1791
1792 const INFLATION_BASE_RATE: f64 = 0.0004;
1794
1795 #[derive(Clone, Debug, Default)]
1796 struct PoolState {
1797 pub token_supply: u64,
1798 pub total_stake: u64,
1799 pub user_token_balances: BTreeMap<Pubkey, u64>,
1800 }
1801 impl PoolState {
1802 #[rustfmt::skip]
1806 pub fn deposit(&mut self, user_pubkey: &Pubkey, stake_to_deposit: u64) -> Option<u64> {
1807 calculate_deposit_amount(self.token_supply, self.total_stake, stake_to_deposit)
1808 .and_then(|tokens_to_mint| self.token_supply.checked_add(tokens_to_mint)
1809 .and_then(|new_token_supply| self.total_stake.checked_add(stake_to_deposit)
1810 .and_then(|new_total_stake| self.user_token_balances.remove(user_pubkey).or(Some(0))
1811 .and_then(|old_user_token_balance| old_user_token_balance.checked_add(tokens_to_mint)
1812 .map(|new_user_token_balance| {
1813 self.token_supply = new_token_supply;
1814 self.total_stake = new_total_stake;
1815 let _ = self.user_token_balances.insert(*user_pubkey, new_user_token_balance);
1816 tokens_to_mint
1817 })))))
1818 }
1819
1820 #[rustfmt::skip]
1824 pub fn withdraw(&mut self, user_pubkey: &Pubkey, tokens_to_burn: u64) -> Option<u64> {
1825 calculate_withdraw_amount(self.token_supply, self.total_stake, tokens_to_burn)
1826 .and_then(|stake_to_withdraw| self.token_supply.checked_sub(tokens_to_burn)
1827 .and_then(|new_token_supply| self.total_stake.checked_sub(stake_to_withdraw)
1828 .and_then(|new_total_stake| self.user_token_balances.remove(user_pubkey)
1829 .and_then(|old_user_token_balance| old_user_token_balance.checked_sub(tokens_to_burn)
1830 .map(|new_user_token_balance| {
1831 self.token_supply = new_token_supply;
1832 self.total_stake = new_total_stake;
1833 let _ = self.user_token_balances.insert(*user_pubkey, new_user_token_balance);
1834 stake_to_withdraw
1835 })))))
1836 }
1837
1838 pub fn reward(&mut self, reward_amount: u64) {
1840 self.total_stake = self.total_stake.checked_add(reward_amount).unwrap();
1841 }
1842
1843 pub fn tokens(&self, user_pubkey: &Pubkey) -> u64 {
1845 *self.user_token_balances.get(user_pubkey).unwrap_or(&0)
1846 }
1847
1848 pub fn stake(&self, user_pubkey: &Pubkey) -> u64 {
1850 let tokens = self.tokens(user_pubkey);
1851 if tokens > 0 {
1852 u64::try_from(tokens as u128 * self.total_stake as u128 / self.token_supply as u128)
1853 .unwrap()
1854 } else {
1855 0
1856 }
1857 }
1858
1859 pub fn share(&self, user_pubkey: &Pubkey) -> f64 {
1861 let tokens = self.tokens(user_pubkey);
1862 if tokens > 0 {
1863 tokens as f64 / self.token_supply as f64
1864 } else {
1865 0.0
1866 }
1867 }
1868 }
1869
1870 #[test]
1873 fn simple_deposit_withdraw() {
1874 let mut pool = PoolState::default();
1875 let alice = Pubkey::new_unique();
1876 let bob = Pubkey::new_unique();
1877 let chad = Pubkey::new_unique();
1878
1879 pool.deposit(&alice, 250).unwrap();
1881 assert_eq!(pool.tokens(&alice), 250);
1882 assert_eq!(pool.token_supply, 250);
1883 assert_eq!(pool.total_stake, 250);
1884
1885 pool.deposit(&bob, 750).unwrap();
1887 assert_eq!(pool.tokens(&bob), 750);
1888 assert_eq!(pool.token_supply, 1000);
1889 assert_eq!(pool.total_stake, 1000);
1890
1891 assert_relative_eq!(pool.share(&alice), 0.25);
1895 assert_relative_eq!(pool.share(&bob), 0.75);
1896 pool.reward(1000);
1897 assert_eq!(pool.stake(&alice), pool.tokens(&alice) * 2);
1898 assert_eq!(pool.stake(&bob), pool.tokens(&bob) * 2);
1899 assert_relative_eq!(pool.share(&alice), 0.25);
1900 assert_relative_eq!(pool.share(&bob), 0.75);
1901
1902 let stake_removed = pool.withdraw(&alice, 125).unwrap();
1907 pool.deposit(&chad, 250).unwrap();
1908 assert_eq!(stake_removed, 250);
1909 assert_relative_eq!(pool.share(&alice), 0.125);
1910 assert_relative_eq!(pool.share(&bob), 0.75);
1911
1912 let stake_removed = pool.withdraw(&bob, 750).unwrap();
1914 assert_eq!(stake_removed, 1500);
1915 assert_relative_eq!(pool.share(&bob), 0.0);
1916 pool.withdraw(&chad, 125).unwrap();
1917 assert_relative_eq!(pool.share(&alice), 1.0);
1918 }
1919
1920 #[test_case(rand::random(), false, false; "no_rewards")]
1926 #[test_case(rand::random(), true, false; "with_rewards")]
1927 #[test_case(rand::random(), true, true; "no_minimum")]
1928 fn random_deposit_withdraw(seed: u64, with_rewards: bool, no_minimum: bool) {
1929 println!(
1930 "TEST SEED: {seed}. edit the test case to pass this value if needed to debug failures",
1931 );
1932 let mut prng = rand::rngs::StdRng::seed_from_u64(seed);
1933
1934 let deposit_range = Uniform::try_from(LAMPORTS_PER_SOL..LAMPORTS_PER_SOL * 1000).unwrap();
1939 let minnow_range = Uniform::try_from(1..LAMPORTS_PER_SOL).unwrap();
1940 let op_range = Uniform::try_from(if with_rewards { 0.0..1.0 } else { 0.0..0.65 }).unwrap();
1941 let std_range = Uniform::try_from(0.0..1.0).unwrap();
1942
1943 let deposit_amount = |prng: &mut StdRng| {
1944 if no_minimum && prng.random_bool(0.2) {
1945 minnow_range.sample(prng)
1946 } else {
1947 deposit_range.sample(prng)
1948 }
1949 };
1950
1951 for _ in 0..100 {
1953 let mut pool = PoolState::default();
1958
1959 let mut users = vec![];
1964 let user_count: usize = prng.random_range(1..=100);
1965 for _ in 0..user_count {
1966 let user = Pubkey::new_unique();
1967
1968 if prng.random_bool(0.5) {
1969 pool.deposit(&user, deposit_amount(&mut prng)).unwrap();
1970 }
1971
1972 users.push(user);
1973 }
1974
1975 for _ in 0..1000 {
1979 match op_range.sample(&mut prng) {
1980 n if n <= 0.35 => {
1983 let user = users.iter().choose(&mut prng).unwrap();
1984 let prev_share = pool.share(user);
1985 let prev_stake = pool.stake(user);
1986 let prev_token_supply = pool.token_supply;
1987 let prev_total_stake = pool.total_stake;
1988
1989 let stake_deposited = deposit_amount(&mut prng);
1990 let tokens_minted = pool.deposit(user, stake_deposited).unwrap();
1991
1992 assert_eq!(pool.total_stake - prev_total_stake, stake_deposited);
1994
1995 assert!(
1997 (pool.stake(user) as i64 - prev_stake as i64 - stake_deposited as i64)
1998 .abs()
1999 <= 2
2000 );
2001
2002 assert_eq!(pool.token_supply - prev_token_supply, tokens_minted);
2004
2005 if prev_total_stake > 0 {
2007 assert_relative_eq!(
2008 pool.share(user) - prev_share,
2009 pool.stake(user) as f64 / pool.total_stake as f64
2010 - prev_stake as f64 / prev_total_stake as f64,
2011 epsilon = 1e-6
2012 );
2013 }
2014 }
2015
2016 n if n > 0.35 && n <= 0.65 => {
2019 if let Some(user) = users
2020 .iter()
2021 .filter(|user| pool.tokens(user) > 0)
2022 .choose(&mut prng)
2023 {
2024 let prev_tokens = pool.tokens(user);
2025 let prev_share = pool.share(user);
2026 let prev_stake = pool.stake(user);
2027 let prev_token_supply = pool.token_supply;
2028 let prev_total_stake = pool.total_stake;
2029
2030 let tokens_burned = if std_range.sample(&mut prng) <= 0.1 {
2031 prev_tokens
2032 } else {
2033 prng.random_range(0..prev_tokens)
2034 };
2035 let stake_received = pool.withdraw(user, tokens_burned).unwrap();
2036
2037 assert_eq!(prev_total_stake - pool.total_stake, stake_received);
2039
2040 assert!(
2042 (prev_stake as i64
2043 - pool.stake(user) as i64
2044 - stake_received as i64)
2045 .abs()
2046 <= 2
2047 );
2048
2049 assert_eq!(prev_token_supply - pool.token_supply, tokens_burned);
2051
2052 if pool.total_stake > 0 {
2054 assert_relative_eq!(
2055 prev_share - pool.share(user),
2056 prev_stake as f64 / prev_total_stake as f64
2057 - pool.stake(user) as f64 / pool.total_stake as f64,
2058 epsilon = 1e-6
2059 );
2060 }
2061 };
2062 }
2063
2064 _ => {
2068 assert!(with_rewards);
2069
2070 let prev_shares_stakes = users
2071 .iter()
2072 .map(|user| (user, pool.share(user), pool.stake(user)))
2073 .filter(|(_, _, stake)| stake > &0)
2074 .collect::<Vec<_>>();
2075
2076 pool.reward((pool.total_stake as f64 * INFLATION_BASE_RATE) as u64);
2077
2078 for (user, prev_share, prev_stake) in prev_shares_stakes {
2079 assert_eq!(pool.share(user), prev_share);
2081
2082 let curr_stake = pool.stake(user);
2083 let stake_share = prev_stake as f64 * INFLATION_BASE_RATE;
2084 let stake_diff = (curr_stake - prev_stake) as f64;
2085
2086 assert!((stake_share - stake_diff).abs() <= 2.0);
2089 }
2090 }
2091 }
2092 }
2093 }
2094 }
2095}