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