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