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