1use {
4 crate::{
5 big_vec::BigVec, error::StakePoolError, MAX_WITHDRAWAL_FEE_INCREASE,
6 MAX_WITHDRAWAL_FEE_INCREASE_FACTOR, WITHDRAWAL_BASELINE_FEE,
7 },
8 borsh::{BorshDeserialize, BorshSchema, BorshSerialize},
9 bytemuck::{Pod, Zeroable},
10 num_derive::{FromPrimitive, ToPrimitive},
11 num_traits::{FromPrimitive, ToPrimitive},
12 solana_account_info::AccountInfo,
13 solana_borsh::v1::get_instance_packed_len,
14 solana_msg::msg,
15 solana_program_error::ProgramError,
16 solana_program_memory::sol_memcmp,
17 solana_program_pack::{Pack, Sealed},
18 solana_pubkey::{Pubkey, PUBKEY_BYTES},
19 solana_stake_interface::state::Lockup,
20 spl_pod::primitives::{PodU32, PodU64},
21 spl_token_2022_interface::{
22 extension::{BaseStateWithExtensions, ExtensionType, StateWithExtensions},
23 state::{Account, AccountState, Mint},
24 },
25 std::{borrow::Borrow, convert::TryFrom, fmt, matches},
26};
27
28#[derive(Clone, Debug, Default, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
30pub enum AccountType {
31 #[default]
33 Uninitialized,
34 StakePool,
36 ValidatorList,
38}
39
40#[repr(C)]
42#[derive(Clone, Debug, Default, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
43pub struct StakePool {
44 pub account_type: AccountType,
46
47 pub manager: Pubkey,
50
51 pub staker: Pubkey,
54
55 pub stake_deposit_authority: Pubkey,
65
66 pub stake_withdraw_bump_seed: u8,
69
70 pub validator_list: Pubkey,
72
73 pub reserve_stake: Pubkey,
75
76 pub pool_mint: Pubkey,
78
79 pub manager_fee_account: Pubkey,
81
82 pub token_program_id: Pubkey,
84
85 pub total_lamports: u64,
89
90 pub pool_token_supply: u64,
93
94 pub last_update_epoch: u64,
96
97 pub lockup: Lockup,
99
100 pub epoch_fee: Fee,
102
103 pub next_epoch_fee: FutureEpoch<Fee>,
105
106 pub preferred_deposit_validator_vote_address: Option<Pubkey>,
108
109 pub preferred_withdraw_validator_vote_address: Option<Pubkey>,
111
112 pub stake_deposit_fee: Fee,
114
115 pub stake_withdrawal_fee: Fee,
117
118 pub next_stake_withdrawal_fee: FutureEpoch<Fee>,
120
121 pub stake_referral_fee: u8,
127
128 pub sol_deposit_authority: Option<Pubkey>,
131
132 pub sol_deposit_fee: Fee,
134
135 pub sol_referral_fee: u8,
141
142 pub sol_withdraw_authority: Option<Pubkey>,
145
146 pub sol_withdrawal_fee: Fee,
148
149 pub next_sol_withdrawal_fee: FutureEpoch<Fee>,
151
152 pub last_epoch_pool_token_supply: u64,
154
155 pub last_epoch_total_lamports: u64,
157}
158impl StakePool {
159 #[inline]
162 pub fn calc_pool_tokens_for_deposit(&self, stake_lamports: u64) -> Option<u64> {
163 if self.total_lamports == 0 || self.pool_token_supply == 0 {
164 return Some(stake_lamports);
165 }
166 u64::try_from(
167 (stake_lamports as u128)
168 .checked_mul(self.pool_token_supply as u128)?
169 .checked_div(self.total_lamports as u128)?,
170 )
171 .ok()
172 }
173
174 #[inline]
176 pub fn calc_lamports_withdraw_amount(&self, pool_tokens: u64) -> Option<u64> {
177 let numerator = (pool_tokens as u128).checked_mul(self.total_lamports as u128)?;
181 let denominator = self.pool_token_supply as u128;
182 if numerator < denominator || denominator == 0 {
183 Some(0)
184 } else {
185 u64::try_from(numerator.checked_div(denominator)?).ok()
186 }
187 }
188
189 #[inline]
191 pub fn calc_pool_tokens_stake_withdrawal_fee(&self, pool_tokens: u64) -> Option<u64> {
192 u64::try_from(self.stake_withdrawal_fee.apply(pool_tokens)?).ok()
193 }
194
195 #[inline]
197 pub fn calc_pool_tokens_sol_withdrawal_fee(&self, pool_tokens: u64) -> Option<u64> {
198 u64::try_from(self.sol_withdrawal_fee.apply(pool_tokens)?).ok()
199 }
200
201 #[inline]
203 pub fn calc_pool_tokens_stake_deposit_fee(&self, pool_tokens_minted: u64) -> Option<u64> {
204 u64::try_from(self.stake_deposit_fee.apply(pool_tokens_minted)?).ok()
205 }
206
207 #[inline]
209 pub fn calc_pool_tokens_stake_referral_fee(&self, stake_deposit_fee: u64) -> Option<u64> {
210 u64::try_from(
211 (stake_deposit_fee as u128)
212 .checked_mul(self.stake_referral_fee as u128)?
213 .checked_div(100u128)?,
214 )
215 .ok()
216 }
217
218 #[inline]
220 pub fn calc_pool_tokens_sol_deposit_fee(&self, pool_tokens_minted: u64) -> Option<u64> {
221 u64::try_from(self.sol_deposit_fee.apply(pool_tokens_minted)?).ok()
222 }
223
224 #[inline]
227 pub fn calc_pool_tokens_sol_referral_fee(&self, sol_deposit_fee: u64) -> Option<u64> {
228 u64::try_from(
229 (sol_deposit_fee as u128)
230 .checked_mul(self.sol_referral_fee as u128)?
231 .checked_div(100u128)?,
232 )
233 .ok()
234 }
235
236 #[inline]
241 pub fn calc_epoch_fee_amount(&self, reward_lamports: u64) -> Option<u64> {
242 if reward_lamports == 0 {
243 return Some(0);
244 }
245 let total_lamports = (self.total_lamports as u128).checked_add(reward_lamports as u128)?;
246 let fee_lamports = self.epoch_fee.apply(reward_lamports)?;
247 if total_lamports == fee_lamports || self.pool_token_supply == 0 {
248 Some(reward_lamports)
249 } else {
250 u64::try_from(
251 (self.pool_token_supply as u128)
252 .checked_mul(fee_lamports)?
253 .checked_div(total_lamports.checked_sub(fee_lamports)?)?,
254 )
255 .ok()
256 }
257 }
258
259 #[inline]
261 pub fn get_lamports_per_pool_token(&self) -> Option<u64> {
262 self.total_lamports
263 .checked_add(self.pool_token_supply)?
264 .checked_sub(1)?
265 .checked_div(self.pool_token_supply)
266 }
267
268 fn check_program_derived_authority(
270 authority_address: &Pubkey,
271 program_id: &Pubkey,
272 stake_pool_address: &Pubkey,
273 authority_seed: &[u8],
274 bump_seed: u8,
275 ) -> Result<(), ProgramError> {
276 let expected_address = Pubkey::create_program_address(
277 &[stake_pool_address.as_ref(), authority_seed, &[bump_seed]],
278 program_id,
279 )?;
280
281 if *authority_address == expected_address {
282 Ok(())
283 } else {
284 msg!(
285 "Incorrect authority provided, expected {}, received {}",
286 expected_address,
287 authority_address
288 );
289 Err(StakePoolError::InvalidProgramAddress.into())
290 }
291 }
292
293 pub fn check_manager_fee_info(
296 &self,
297 manager_fee_info: &AccountInfo,
298 ) -> Result<(), ProgramError> {
299 let account_data = manager_fee_info.try_borrow_data()?;
300 let token_account = StateWithExtensions::<Account>::unpack(&account_data)?;
301 if manager_fee_info.owner != &self.token_program_id
302 || token_account.base.state != AccountState::Initialized
303 || token_account.base.mint != self.pool_mint
304 {
305 msg!("Manager fee account is not owned by token program, is not initialized, or does not match stake pool's mint");
306 return Err(StakePoolError::InvalidFeeAccount.into());
307 }
308 let extensions = token_account.get_extension_types()?;
309 if extensions
310 .iter()
311 .any(|x| !is_extension_supported_for_fee_account(x))
312 {
313 return Err(StakePoolError::UnsupportedFeeAccountExtension.into());
314 }
315 Ok(())
316 }
317
318 #[inline]
320 pub(crate) fn check_authority_withdraw(
321 &self,
322 withdraw_authority: &Pubkey,
323 program_id: &Pubkey,
324 stake_pool_address: &Pubkey,
325 ) -> Result<(), ProgramError> {
326 Self::check_program_derived_authority(
327 withdraw_authority,
328 program_id,
329 stake_pool_address,
330 crate::AUTHORITY_WITHDRAW,
331 self.stake_withdraw_bump_seed,
332 )
333 }
334 #[inline]
336 pub(crate) fn check_stake_deposit_authority(
337 &self,
338 stake_deposit_authority: &Pubkey,
339 ) -> Result<(), ProgramError> {
340 if self.stake_deposit_authority == *stake_deposit_authority {
341 Ok(())
342 } else {
343 Err(StakePoolError::InvalidStakeDepositAuthority.into())
344 }
345 }
346
347 #[inline]
350 pub(crate) fn check_sol_deposit_authority(
351 &self,
352 maybe_sol_deposit_authority: Result<&AccountInfo, ProgramError>,
353 ) -> Result<(), ProgramError> {
354 if let Some(auth) = self.sol_deposit_authority {
355 let sol_deposit_authority = maybe_sol_deposit_authority?;
356 if auth != *sol_deposit_authority.key {
357 msg!("Expected {}, received {}", auth, sol_deposit_authority.key);
358 return Err(StakePoolError::InvalidSolDepositAuthority.into());
359 }
360 if !sol_deposit_authority.is_signer {
361 msg!("SOL Deposit authority signature missing");
362 return Err(StakePoolError::SignatureMissing.into());
363 }
364 }
365 Ok(())
366 }
367
368 #[inline]
371 pub(crate) fn check_sol_withdraw_authority(
372 &self,
373 maybe_sol_withdraw_authority: Result<&AccountInfo, ProgramError>,
374 ) -> Result<(), ProgramError> {
375 if let Some(auth) = self.sol_withdraw_authority {
376 let sol_withdraw_authority = maybe_sol_withdraw_authority?;
377 if auth != *sol_withdraw_authority.key {
378 return Err(StakePoolError::InvalidSolWithdrawAuthority.into());
379 }
380 if !sol_withdraw_authority.is_signer {
381 msg!("SOL withdraw authority signature missing");
382 return Err(StakePoolError::SignatureMissing.into());
383 }
384 }
385 Ok(())
386 }
387
388 #[inline]
390 pub(crate) fn check_mint(&self, mint_info: &AccountInfo) -> Result<u8, ProgramError> {
391 if *mint_info.key != self.pool_mint {
392 Err(StakePoolError::WrongPoolMint.into())
393 } else {
394 let mint_data = mint_info.try_borrow_data()?;
395 let mint = StateWithExtensions::<Mint>::unpack(&mint_data)?;
396 Ok(mint.base.decimals)
397 }
398 }
399
400 pub(crate) fn check_manager(&self, manager_info: &AccountInfo) -> Result<(), ProgramError> {
402 if *manager_info.key != self.manager {
403 msg!(
404 "Incorrect manager provided, expected {}, received {}",
405 self.manager,
406 manager_info.key
407 );
408 return Err(StakePoolError::WrongManager.into());
409 }
410 if !manager_info.is_signer {
411 msg!("Manager signature missing");
412 return Err(StakePoolError::SignatureMissing.into());
413 }
414 Ok(())
415 }
416
417 pub(crate) fn check_staker(&self, staker_info: &AccountInfo) -> Result<(), ProgramError> {
419 if *staker_info.key != self.staker {
420 msg!(
421 "Incorrect staker provided, expected {}, received {}",
422 self.staker,
423 staker_info.key
424 );
425 return Err(StakePoolError::WrongStaker.into());
426 }
427 if !staker_info.is_signer {
428 msg!("Staker signature missing");
429 return Err(StakePoolError::SignatureMissing.into());
430 }
431 Ok(())
432 }
433
434 pub fn check_validator_list(
436 &self,
437 validator_list_info: &AccountInfo,
438 ) -> Result<(), ProgramError> {
439 if *validator_list_info.key != self.validator_list {
440 msg!(
441 "Invalid validator list provided, expected {}, received {}",
442 self.validator_list,
443 validator_list_info.key
444 );
445 Err(StakePoolError::InvalidValidatorStakeList.into())
446 } else {
447 Ok(())
448 }
449 }
450
451 pub fn check_reserve_stake(
453 &self,
454 reserve_stake_info: &AccountInfo,
455 ) -> Result<(), ProgramError> {
456 if *reserve_stake_info.key != self.reserve_stake {
457 msg!(
458 "Invalid reserve stake provided, expected {}, received {}",
459 self.reserve_stake,
460 reserve_stake_info.key
461 );
462 Err(StakePoolError::InvalidProgramAddress.into())
463 } else {
464 Ok(())
465 }
466 }
467
468 pub fn is_valid(&self) -> bool {
470 self.account_type == AccountType::StakePool
471 }
472
473 pub fn is_uninitialized(&self) -> bool {
475 self.account_type == AccountType::Uninitialized
476 }
477
478 pub fn update_fee(&mut self, fee: &FeeType) -> Result<(), StakePoolError> {
480 match fee {
481 FeeType::SolReferral(new_fee) => self.sol_referral_fee = *new_fee,
482 FeeType::StakeReferral(new_fee) => self.stake_referral_fee = *new_fee,
483 FeeType::Epoch(new_fee) => self.next_epoch_fee = FutureEpoch::new(*new_fee),
484 FeeType::StakeWithdrawal(new_fee) => {
485 new_fee.check_withdrawal(&self.stake_withdrawal_fee)?;
486 self.next_stake_withdrawal_fee = FutureEpoch::new(*new_fee)
487 }
488 FeeType::SolWithdrawal(new_fee) => {
489 new_fee.check_withdrawal(&self.sol_withdrawal_fee)?;
490 self.next_sol_withdrawal_fee = FutureEpoch::new(*new_fee)
491 }
492 FeeType::SolDeposit(new_fee) => self.sol_deposit_fee = *new_fee,
493 FeeType::StakeDeposit(new_fee) => self.stake_deposit_fee = *new_fee,
494 };
495 Ok(())
496 }
497}
498
499pub fn is_extension_supported_for_mint(extension_type: &ExtensionType) -> bool {
501 const SUPPORTED_EXTENSIONS: [ExtensionType; 8] = [
502 ExtensionType::Uninitialized,
503 ExtensionType::TransferFeeConfig,
504 ExtensionType::ConfidentialTransferMint,
505 ExtensionType::ConfidentialTransferFeeConfig,
506 ExtensionType::DefaultAccountState,
507 ExtensionType::InterestBearingConfig,
508 ExtensionType::MetadataPointer,
509 ExtensionType::TokenMetadata,
510 ];
511 if !SUPPORTED_EXTENSIONS.contains(extension_type) {
512 msg!(
513 "Stake pool mint account cannot have the {:?} extension",
514 extension_type
515 );
516 false
517 } else {
518 true
519 }
520}
521
522pub fn is_extension_supported_for_fee_account(extension_type: &ExtensionType) -> bool {
524 const SUPPORTED_EXTENSIONS: [ExtensionType; 4] = [
528 ExtensionType::Uninitialized,
529 ExtensionType::TransferFeeAmount,
530 ExtensionType::ImmutableOwner,
531 ExtensionType::CpiGuard,
532 ];
533 if !SUPPORTED_EXTENSIONS.contains(extension_type) {
534 msg!("Fee account cannot have the {:?} extension", extension_type);
535 false
536 } else {
537 true
538 }
539}
540
541#[repr(C)]
543#[derive(Clone, Debug, Default, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
544pub struct ValidatorList {
545 pub header: ValidatorListHeader,
548
549 pub validators: Vec<ValidatorStakeInfo>,
551}
552
553#[repr(C)]
555#[derive(Clone, Debug, Default, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
556pub struct ValidatorListHeader {
557 pub account_type: AccountType,
559
560 pub max_validators: u32,
562}
563
564#[derive(
566 ToPrimitive,
567 FromPrimitive,
568 Copy,
569 Clone,
570 Debug,
571 Default,
572 PartialEq,
573 BorshDeserialize,
574 BorshSerialize,
575 BorshSchema,
576)]
577pub enum StakeStatus {
578 #[default]
580 Active,
581 DeactivatingTransient,
584 ReadyForRemoval,
587 DeactivatingValidator,
590 DeactivatingAll,
593}
594
595#[repr(transparent)]
598#[derive(
599 Clone,
600 Copy,
601 Debug,
602 Default,
603 PartialEq,
604 Pod,
605 Zeroable,
606 BorshDeserialize,
607 BorshSerialize,
608 BorshSchema,
609)]
610pub struct PodStakeStatus(u8);
611impl PodStakeStatus {
612 pub fn remove_validator_stake(&mut self) -> Result<(), ProgramError> {
615 let status = StakeStatus::try_from(*self)?;
616 let new_self = match status {
617 StakeStatus::Active
618 | StakeStatus::DeactivatingTransient
619 | StakeStatus::ReadyForRemoval => status,
620 StakeStatus::DeactivatingAll => StakeStatus::DeactivatingTransient,
621 StakeStatus::DeactivatingValidator => StakeStatus::ReadyForRemoval,
622 };
623 *self = new_self.into();
624 Ok(())
625 }
626 pub fn remove_transient_stake(&mut self) -> Result<(), ProgramError> {
629 let status = StakeStatus::try_from(*self)?;
630 let new_self = match status {
631 StakeStatus::Active
632 | StakeStatus::DeactivatingValidator
633 | StakeStatus::ReadyForRemoval => status,
634 StakeStatus::DeactivatingAll => StakeStatus::DeactivatingValidator,
635 StakeStatus::DeactivatingTransient => StakeStatus::ReadyForRemoval,
636 };
637 *self = new_self.into();
638 Ok(())
639 }
640}
641impl TryFrom<PodStakeStatus> for StakeStatus {
642 type Error = ProgramError;
643 fn try_from(pod: PodStakeStatus) -> Result<Self, Self::Error> {
644 FromPrimitive::from_u8(pod.0).ok_or(ProgramError::InvalidAccountData)
645 }
646}
647impl From<StakeStatus> for PodStakeStatus {
648 fn from(status: StakeStatus) -> Self {
649 PodStakeStatus(status.to_u8().unwrap())
652 }
653}
654
655#[derive(Debug, PartialEq)]
657pub(crate) enum StakeWithdrawSource {
658 Active,
660 Transient,
662 ValidatorRemoval,
664}
665
666#[repr(C)]
675#[derive(
676 Clone,
677 Copy,
678 Debug,
679 Default,
680 PartialEq,
681 Pod,
682 Zeroable,
683 BorshDeserialize,
684 BorshSerialize,
685 BorshSchema,
686)]
687pub struct ValidatorStakeInfo {
688 pub active_stake_lamports: PodU64,
693
694 pub transient_stake_lamports: PodU64,
699
700 pub last_update_epoch: PodU64,
702
703 pub transient_seed_suffix: PodU64,
706
707 pub unused: PodU32,
709
710 pub validator_seed_suffix: PodU32, pub status: PodStakeStatus,
715
716 pub vote_account_address: Pubkey,
718}
719
720impl ValidatorStakeInfo {
721 pub fn stake_lamports(&self) -> Result<u64, StakePoolError> {
723 u64::from(self.active_stake_lamports)
724 .checked_add(self.transient_stake_lamports.into())
725 .ok_or(StakePoolError::CalculationFailure)
726 }
727
728 pub fn memcmp_pubkey(data: &[u8], vote_address: &Pubkey) -> bool {
731 unsafe {
732 sol_memcmp(
733 &data[41..41_usize.saturating_add(PUBKEY_BYTES)],
734 vote_address.as_ref(),
735 PUBKEY_BYTES,
736 ) == 0
737 }
738 }
739
740 pub fn active_lamports_greater_than(data: &[u8], lamports: &u64) -> bool {
743 u64::try_from_slice(&data[0..8]).unwrap() > *lamports
745 }
746
747 pub fn transient_lamports_greater_than(data: &[u8], lamports: &u64) -> bool {
750 u64::try_from_slice(&data[8..16]).unwrap() > *lamports
752 }
753
754 pub fn is_removed(data: &[u8]) -> bool {
756 FromPrimitive::from_u8(data[40]) == Some(StakeStatus::ReadyForRemoval)
757 && data[0..16] == [0; 16] }
759
760 pub fn is_active(data: &[u8]) -> bool {
762 FromPrimitive::from_u8(data[40]) == Some(StakeStatus::Active)
763 }
764}
765
766impl Sealed for ValidatorStakeInfo {}
767
768impl Pack for ValidatorStakeInfo {
769 const LEN: usize = 73;
770 fn pack_into_slice(&self, data: &mut [u8]) {
771 borsh::to_writer(data, self).unwrap();
774 }
775 fn unpack_from_slice(src: &[u8]) -> Result<Self, ProgramError> {
776 let unpacked = Self::try_from_slice(src)?;
777 Ok(unpacked)
778 }
779}
780
781impl ValidatorList {
782 pub fn new(max_validators: u32) -> Self {
785 Self {
786 header: ValidatorListHeader {
787 account_type: AccountType::ValidatorList,
788 max_validators,
789 },
790 validators: vec![ValidatorStakeInfo::default(); max_validators as usize],
791 }
792 }
793
794 pub fn calculate_max_validators(buffer_length: usize) -> usize {
797 let header_size = ValidatorListHeader::LEN.saturating_add(4);
798 buffer_length
799 .saturating_sub(header_size)
800 .saturating_div(ValidatorStakeInfo::LEN)
801 }
802
803 pub fn contains(&self, vote_account_address: &Pubkey) -> bool {
805 self.validators
806 .iter()
807 .any(|x| x.vote_account_address == *vote_account_address)
808 }
809
810 pub fn find_mut(&mut self, vote_account_address: &Pubkey) -> Option<&mut ValidatorStakeInfo> {
812 self.validators
813 .iter_mut()
814 .find(|x| x.vote_account_address == *vote_account_address)
815 }
816 pub fn find(&self, vote_account_address: &Pubkey) -> Option<&ValidatorStakeInfo> {
818 self.validators
819 .iter()
820 .find(|x| x.vote_account_address == *vote_account_address)
821 }
822
823 pub fn has_active_stake(&self) -> bool {
825 self.validators
826 .iter()
827 .any(|x| u64::from(x.active_stake_lamports) > 0)
828 }
829}
830
831impl ValidatorListHeader {
832 const LEN: usize = 1 + 4;
833
834 pub fn is_valid(&self) -> bool {
837 self.account_type == AccountType::ValidatorList
838 }
839
840 pub fn is_uninitialized(&self) -> bool {
842 self.account_type == AccountType::Uninitialized
843 }
844
845 pub fn deserialize_mut_slice<'a>(
848 big_vec: &'a mut BigVec,
849 skip: usize,
850 len: usize,
851 ) -> Result<&'a mut [ValidatorStakeInfo], ProgramError> {
852 big_vec.deserialize_mut_slice::<ValidatorStakeInfo>(skip, len)
853 }
854
855 pub fn deserialize_vec(data: &mut [u8]) -> Result<(Self, BigVec<'_>), ProgramError> {
857 let mut data_mut = data.borrow();
858 let header = ValidatorListHeader::deserialize(&mut data_mut)?;
859 let length = get_instance_packed_len(&header)?;
860
861 let big_vec = BigVec {
862 data: &mut data[length..],
863 };
864 Ok((header, big_vec))
865 }
866}
867
868#[repr(C)]
871#[derive(Clone, Copy, Debug, Default, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema)]
872pub enum FutureEpoch<T> {
873 #[default]
875 None,
876 One(T),
878 Two(T),
880}
881impl<T> FutureEpoch<T> {
882 pub fn new(value: T) -> Self {
884 Self::Two(value)
885 }
886}
887impl<T: Clone> FutureEpoch<T> {
888 pub fn update_epoch(&mut self) {
890 match self {
891 Self::None => {}
892 Self::One(_) => {
893 *self = Self::None;
895 }
896 Self::Two(v) => {
898 *self = Self::One(v.clone());
899 }
900 }
901 }
902
903 pub fn get(&self) -> Option<&T> {
905 match self {
906 Self::None | Self::Two(_) => None,
907 Self::One(v) => Some(v),
908 }
909 }
910}
911impl<T> From<FutureEpoch<T>> for Option<T> {
912 fn from(v: FutureEpoch<T>) -> Option<T> {
913 match v {
914 FutureEpoch::None => None,
915 FutureEpoch::One(inner) | FutureEpoch::Two(inner) => Some(inner),
916 }
917 }
918}
919
920#[repr(C)]
925#[derive(Clone, Copy, Debug, Default, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema)]
926pub struct Fee {
927 pub denominator: u64,
929 pub numerator: u64,
931}
932
933impl Fee {
934 #[inline]
939 pub fn apply(&self, amt: u64) -> Option<u128> {
940 if self.denominator == 0 {
941 return Some(0);
942 }
943 let numerator = (amt as u128).checked_mul(self.numerator as u128)?;
944 let denominator = self.denominator as u128;
946 numerator
947 .checked_add(denominator)?
948 .checked_sub(1)?
949 .checked_div(denominator)
950 }
951
952 pub fn check_withdrawal(&self, old_withdrawal_fee: &Fee) -> Result<(), StakePoolError> {
955 let (old_num, old_denom) =
958 if old_withdrawal_fee.denominator == 0 || old_withdrawal_fee.numerator == 0 {
959 (
960 WITHDRAWAL_BASELINE_FEE.numerator,
961 WITHDRAWAL_BASELINE_FEE.denominator,
962 )
963 } else {
964 (old_withdrawal_fee.numerator, old_withdrawal_fee.denominator)
965 };
966
967 if (old_denom as u128)
971 .checked_mul(self.denominator as u128)
972 .and_then(|x| x.checked_mul(MAX_WITHDRAWAL_FEE_INCREASE.numerator as u128))
973 .ok_or(StakePoolError::CalculationFailure)?
974 < ((self.numerator as u128)
975 .checked_mul(old_denom as u128)
976 .ok_or(StakePoolError::CalculationFailure)?
977 .saturating_sub(
978 (self.denominator as u128)
979 .checked_mul(old_num as u128)
980 .ok_or(StakePoolError::CalculationFailure)?,
981 )
982 .checked_mul(MAX_WITHDRAWAL_FEE_INCREASE.denominator as u128)
983 .ok_or(StakePoolError::CalculationFailure)?)
984 {
985 msg!(
986 "Fee increase exceeds maximum allowed, need to increase by {} / {}",
987 MAX_WITHDRAWAL_FEE_INCREASE.numerator,
988 MAX_WITHDRAWAL_FEE_INCREASE.denominator,
989 );
990 return Err(StakePoolError::FeeIncreaseTooHigh);
991 }
992
993 if (old_num as u128)
997 .checked_mul(self.denominator as u128)
998 .and_then(|x| x.checked_mul(MAX_WITHDRAWAL_FEE_INCREASE_FACTOR.numerator as u128))
999 .ok_or(StakePoolError::CalculationFailure)?
1000 < (self.numerator as u128)
1001 .checked_mul(old_denom as u128)
1002 .and_then(|x| x.checked_mul(MAX_WITHDRAWAL_FEE_INCREASE_FACTOR.denominator as u128))
1003 .ok_or(StakePoolError::CalculationFailure)?
1004 {
1005 msg!(
1006 "Fee increase exceeds maximum allowed, proposed increase factor ({} / {})",
1007 self.numerator.saturating_mul(old_denom),
1008 old_num.saturating_mul(self.denominator),
1009 );
1010 return Err(StakePoolError::FeeIncreaseTooHigh);
1011 }
1012 Ok(())
1013 }
1014}
1015
1016impl fmt::Display for Fee {
1017 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1018 if self.numerator > 0 && self.denominator > 0 {
1019 write!(f, "{}/{}", self.numerator, self.denominator)
1020 } else {
1021 write!(f, "none")
1022 }
1023 }
1024}
1025
1026#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
1028pub enum FeeType {
1029 SolReferral(u8),
1031 StakeReferral(u8),
1033 Epoch(Fee),
1035 StakeWithdrawal(Fee),
1037 SolDeposit(Fee),
1039 StakeDeposit(Fee),
1041 SolWithdrawal(Fee),
1043}
1044
1045impl FeeType {
1046 pub fn check_too_high(&self) -> Result<(), StakePoolError> {
1048 let too_high = match self {
1049 Self::SolReferral(pct) => *pct > 100u8,
1050 Self::StakeReferral(pct) => *pct > 100u8,
1051 Self::Epoch(fee) => fee.numerator > fee.denominator,
1052 Self::StakeWithdrawal(fee) => fee.numerator > fee.denominator,
1053 Self::SolWithdrawal(fee) => fee.numerator > fee.denominator,
1054 Self::SolDeposit(fee) => fee.numerator > fee.denominator,
1055 Self::StakeDeposit(fee) => fee.numerator > fee.denominator,
1056 };
1057 if too_high {
1058 msg!("Fee greater than 100%: {:?}", self);
1059 return Err(StakePoolError::FeeTooHigh);
1060 }
1061 Ok(())
1062 }
1063
1064 #[inline]
1067 pub fn can_only_change_next_epoch(&self) -> bool {
1068 matches!(
1069 self,
1070 Self::StakeWithdrawal(_) | Self::SolWithdrawal(_) | Self::Epoch(_)
1071 )
1072 }
1073}
1074
1075#[cfg(test)]
1076mod test {
1077 #![allow(clippy::arithmetic_side_effects)]
1078 use {
1079 super::*,
1080 proptest::prelude::*,
1081 solana_borsh::v1::{get_packed_len, try_from_slice_unchecked},
1082 solana_clock::{DEFAULT_SLOTS_PER_EPOCH, DEFAULT_S_PER_SLOT, SECONDS_PER_DAY},
1083 solana_native_token::LAMPORTS_PER_SOL,
1084 };
1085
1086 fn uninitialized_validator_list() -> ValidatorList {
1087 ValidatorList {
1088 header: ValidatorListHeader {
1089 account_type: AccountType::Uninitialized,
1090 max_validators: 0,
1091 },
1092 validators: vec![],
1093 }
1094 }
1095
1096 fn test_validator_list(max_validators: u32) -> ValidatorList {
1097 ValidatorList {
1098 header: ValidatorListHeader {
1099 account_type: AccountType::ValidatorList,
1100 max_validators,
1101 },
1102 validators: vec![
1103 ValidatorStakeInfo {
1104 status: StakeStatus::Active.into(),
1105 vote_account_address: Pubkey::new_from_array([1; 32]),
1106 active_stake_lamports: u64::from_le_bytes([255; 8]).into(),
1107 transient_stake_lamports: u64::from_le_bytes([128; 8]).into(),
1108 last_update_epoch: u64::from_le_bytes([64; 8]).into(),
1109 transient_seed_suffix: 0.into(),
1110 unused: 0.into(),
1111 validator_seed_suffix: 0.into(),
1112 },
1113 ValidatorStakeInfo {
1114 status: StakeStatus::DeactivatingTransient.into(),
1115 vote_account_address: Pubkey::new_from_array([2; 32]),
1116 active_stake_lamports: 998877665544.into(),
1117 transient_stake_lamports: 222222222.into(),
1118 last_update_epoch: 11223445566.into(),
1119 transient_seed_suffix: 0.into(),
1120 unused: 0.into(),
1121 validator_seed_suffix: 0.into(),
1122 },
1123 ValidatorStakeInfo {
1124 status: StakeStatus::ReadyForRemoval.into(),
1125 vote_account_address: Pubkey::new_from_array([3; 32]),
1126 active_stake_lamports: 0.into(),
1127 transient_stake_lamports: 0.into(),
1128 last_update_epoch: 999999999999999.into(),
1129 transient_seed_suffix: 0.into(),
1130 unused: 0.into(),
1131 validator_seed_suffix: 0.into(),
1132 },
1133 ],
1134 }
1135 }
1136
1137 #[test]
1138 fn state_packing() {
1139 let max_validators = 10_000;
1140 let size = get_instance_packed_len(&ValidatorList::new(max_validators)).unwrap();
1141 let stake_list = uninitialized_validator_list();
1142 let mut byte_vec = vec![0u8; size];
1143 let bytes = byte_vec.as_mut_slice();
1144 borsh::to_writer(bytes, &stake_list).unwrap();
1145 let stake_list_unpacked = try_from_slice_unchecked::<ValidatorList>(&byte_vec).unwrap();
1146 assert_eq!(stake_list_unpacked, stake_list);
1147
1148 let stake_list = ValidatorList {
1150 header: ValidatorListHeader {
1151 account_type: AccountType::ValidatorList,
1152 max_validators: 0,
1153 },
1154 validators: vec![],
1155 };
1156 let mut byte_vec = vec![0u8; size];
1157 let bytes = byte_vec.as_mut_slice();
1158 borsh::to_writer(bytes, &stake_list).unwrap();
1159 let stake_list_unpacked = try_from_slice_unchecked::<ValidatorList>(&byte_vec).unwrap();
1160 assert_eq!(stake_list_unpacked, stake_list);
1161
1162 let stake_list = test_validator_list(max_validators);
1164 let mut byte_vec = vec![0u8; size];
1165 let bytes = byte_vec.as_mut_slice();
1166 borsh::to_writer(bytes, &stake_list).unwrap();
1167 let stake_list_unpacked = try_from_slice_unchecked::<ValidatorList>(&byte_vec).unwrap();
1168 assert_eq!(stake_list_unpacked, stake_list);
1169 }
1170
1171 #[test]
1172 fn validator_list_active_stake() {
1173 let max_validators = 10_000;
1174 let mut validator_list = test_validator_list(max_validators);
1175 assert!(validator_list.has_active_stake());
1176 for validator in validator_list.validators.iter_mut() {
1177 validator.active_stake_lamports = 0.into();
1178 }
1179 assert!(!validator_list.has_active_stake());
1180 }
1181
1182 #[test]
1183 fn validator_list_deserialize_mut_slice() {
1184 let max_validators = 10;
1185 let stake_list = test_validator_list(max_validators);
1186 let mut serialized = borsh::to_vec(&stake_list).unwrap();
1187 let (header, mut big_vec) = ValidatorListHeader::deserialize_vec(&mut serialized).unwrap();
1188 let list = ValidatorListHeader::deserialize_mut_slice(
1189 &mut big_vec,
1190 0,
1191 stake_list.validators.len(),
1192 )
1193 .unwrap();
1194 assert_eq!(header.account_type, AccountType::ValidatorList);
1195 assert_eq!(header.max_validators, max_validators);
1196 assert!(list
1197 .iter()
1198 .zip(stake_list.validators.iter())
1199 .all(|(a, b)| a == b));
1200
1201 let list = ValidatorListHeader::deserialize_mut_slice(&mut big_vec, 1, 2).unwrap();
1202 assert!(list
1203 .iter()
1204 .zip(stake_list.validators[1..].iter())
1205 .all(|(a, b)| a == b));
1206 let list = ValidatorListHeader::deserialize_mut_slice(&mut big_vec, 2, 1).unwrap();
1207 assert!(list
1208 .iter()
1209 .zip(stake_list.validators[2..].iter())
1210 .all(|(a, b)| a == b));
1211 let list = ValidatorListHeader::deserialize_mut_slice(&mut big_vec, 0, 2).unwrap();
1212 assert!(list
1213 .iter()
1214 .zip(stake_list.validators[..2].iter())
1215 .all(|(a, b)| a == b));
1216
1217 assert_eq!(
1218 ValidatorListHeader::deserialize_mut_slice(&mut big_vec, 0, 4).unwrap_err(),
1219 ProgramError::AccountDataTooSmall
1220 );
1221 assert_eq!(
1222 ValidatorListHeader::deserialize_mut_slice(&mut big_vec, 1, 3).unwrap_err(),
1223 ProgramError::AccountDataTooSmall
1224 );
1225 }
1226
1227 #[test]
1228 fn validator_list_iter() {
1229 let max_validators = 10;
1230 let stake_list = test_validator_list(max_validators);
1231 let mut serialized = borsh::to_vec(&stake_list).unwrap();
1232 let (_, big_vec) = ValidatorListHeader::deserialize_vec(&mut serialized).unwrap();
1233 for (a, b) in big_vec
1234 .deserialize_slice::<ValidatorStakeInfo>(0, big_vec.len() as usize)
1235 .unwrap()
1236 .iter()
1237 .zip(stake_list.validators.iter())
1238 {
1239 assert_eq!(a, b);
1240 }
1241 }
1242
1243 proptest! {
1244 #[test]
1245 fn stake_list_size_calculation(test_amount in 0..=100_000_u32) {
1246 let validators = ValidatorList::new(test_amount);
1247 let size = get_instance_packed_len(&validators).unwrap();
1248 assert_eq!(ValidatorList::calculate_max_validators(size), test_amount as usize);
1249 assert_eq!(ValidatorList::calculate_max_validators(size.saturating_add(1)), test_amount as usize);
1250 assert_eq!(ValidatorList::calculate_max_validators(size.saturating_add(get_packed_len::<ValidatorStakeInfo>())), (test_amount + 1)as usize);
1251 assert_eq!(ValidatorList::calculate_max_validators(size.saturating_sub(1)), (test_amount.saturating_sub(1)) as usize);
1252 }
1253 }
1254
1255 prop_compose! {
1256 fn fee()(denominator in 1..=u16::MAX)(
1257 denominator in Just(denominator),
1258 numerator in 0..=denominator,
1259 ) -> (u64, u64) {
1260 (numerator as u64, denominator as u64)
1261 }
1262 }
1263
1264 prop_compose! {
1265 fn total_stake_and_rewards()(total_lamports in 1..u64::MAX)(
1266 total_lamports in Just(total_lamports),
1267 rewards in 0..=total_lamports,
1268 ) -> (u64, u64) {
1269 (total_lamports - rewards, rewards)
1270 }
1271 }
1272
1273 #[test]
1274 fn specific_fee_calculation() {
1275 let epoch_fee = Fee {
1277 numerator: 1,
1278 denominator: 10,
1279 };
1280 let mut stake_pool = StakePool {
1281 total_lamports: 100 * LAMPORTS_PER_SOL,
1282 pool_token_supply: 100 * LAMPORTS_PER_SOL,
1283 epoch_fee,
1284 ..StakePool::default()
1285 };
1286 let reward_lamports = 10 * LAMPORTS_PER_SOL;
1287 let pool_token_fee = stake_pool.calc_epoch_fee_amount(reward_lamports).unwrap();
1288
1289 stake_pool.total_lamports += reward_lamports;
1290 stake_pool.pool_token_supply += pool_token_fee;
1291
1292 let fee_lamports = stake_pool
1293 .calc_lamports_withdraw_amount(pool_token_fee)
1294 .unwrap();
1295 assert_eq!(fee_lamports, LAMPORTS_PER_SOL - 1); }
1298
1299 #[test]
1300 fn zero_withdraw_calculation() {
1301 let epoch_fee = Fee {
1302 numerator: 0,
1303 denominator: 1,
1304 };
1305 let stake_pool = StakePool {
1306 epoch_fee,
1307 ..StakePool::default()
1308 };
1309 let fee_lamports = stake_pool.calc_lamports_withdraw_amount(0).unwrap();
1310 assert_eq!(fee_lamports, 0);
1311 }
1312
1313 #[test]
1314 fn divide_by_zero_fee() {
1315 let stake_pool = StakePool {
1316 total_lamports: 0,
1317 epoch_fee: Fee {
1318 numerator: 1,
1319 denominator: 10,
1320 },
1321 ..StakePool::default()
1322 };
1323 let rewards = 10;
1324 let fee = stake_pool.calc_epoch_fee_amount(rewards).unwrap();
1325 assert_eq!(fee, rewards);
1326 }
1327
1328 #[test]
1329 fn approximate_apr_calculation() {
1330 let stake_pool = StakePool {
1332 last_epoch_total_lamports: 100_000,
1333 last_epoch_pool_token_supply: 100_000,
1334 total_lamports: 100_044,
1335 pool_token_supply: 100_000,
1336 ..StakePool::default()
1337 };
1338 let pool_token_value =
1339 stake_pool.total_lamports as f64 / stake_pool.pool_token_supply as f64;
1340 let last_epoch_pool_token_value = stake_pool.last_epoch_total_lamports as f64
1341 / stake_pool.last_epoch_pool_token_supply as f64;
1342 let epoch_rate = pool_token_value / last_epoch_pool_token_value - 1.0;
1343 const SECONDS_PER_EPOCH: f64 = DEFAULT_SLOTS_PER_EPOCH as f64 * DEFAULT_S_PER_SLOT;
1344 const EPOCHS_PER_YEAR: f64 = SECONDS_PER_DAY as f64 * 365.25 / SECONDS_PER_EPOCH;
1345 const EPSILON: f64 = 0.00001;
1346 let yearly_rate = epoch_rate * EPOCHS_PER_YEAR;
1347 assert!((yearly_rate - 0.080355).abs() < EPSILON);
1348 }
1349
1350 proptest! {
1351 #[test]
1352 fn fee_calculation(
1353 (numerator, denominator) in fee(),
1354 (total_lamports, reward_lamports) in total_stake_and_rewards(),
1355 ) {
1356 let epoch_fee = Fee { denominator, numerator };
1357 let mut stake_pool = StakePool {
1358 total_lamports,
1359 pool_token_supply: total_lamports,
1360 epoch_fee,
1361 ..StakePool::default()
1362 };
1363 let pool_token_fee = stake_pool.calc_epoch_fee_amount(reward_lamports).unwrap();
1364
1365 stake_pool.total_lamports += reward_lamports;
1366 stake_pool.pool_token_supply += pool_token_fee;
1367
1368 let fee_lamports = stake_pool.calc_lamports_withdraw_amount(pool_token_fee).unwrap();
1369 let max_fee_lamports = u64::try_from((reward_lamports as u128) * (epoch_fee.numerator as u128) / (epoch_fee.denominator as u128)).unwrap();
1370 assert!(max_fee_lamports >= fee_lamports,
1371 "Max possible fee must always be greater than or equal to what is actually withdrawn, max {} actual {}",
1372 max_fee_lamports,
1373 fee_lamports);
1374
1375 let epsilon = 2 + reward_lamports / total_lamports;
1379 assert!(max_fee_lamports - fee_lamports <= epsilon,
1380 "Max expected fee in lamports {}, actually receive {}, epsilon {}",
1381 max_fee_lamports, fee_lamports, epsilon);
1382 }
1383 }
1384
1385 prop_compose! {
1386 fn total_tokens_and_deposit()(total_lamports in 1..u64::MAX)(
1387 total_lamports in Just(total_lamports),
1388 pool_token_supply in 1..=total_lamports,
1389 deposit_lamports in 1..total_lamports,
1390 ) -> (u64, u64, u64) {
1391 (total_lamports - deposit_lamports, pool_token_supply.saturating_sub(deposit_lamports).max(1), deposit_lamports)
1392 }
1393 }
1394
1395 proptest! {
1396 #[test]
1397 fn deposit_and_withdraw(
1398 (total_lamports, pool_token_supply, deposit_stake) in total_tokens_and_deposit()
1399 ) {
1400 let mut stake_pool = StakePool {
1401 total_lamports,
1402 pool_token_supply,
1403 ..StakePool::default()
1404 };
1405 let deposit_result = stake_pool.calc_pool_tokens_for_deposit(deposit_stake).unwrap();
1406 prop_assume!(deposit_result > 0);
1407 stake_pool.total_lamports += deposit_stake;
1408 stake_pool.pool_token_supply += deposit_result;
1409 let withdraw_result = stake_pool.calc_lamports_withdraw_amount(deposit_result).unwrap();
1410 assert!(withdraw_result <= deposit_stake);
1411
1412 if deposit_result >= 2 {
1414 let first_half_deposit = deposit_result / 2;
1415 let first_withdraw_result = stake_pool.calc_lamports_withdraw_amount(first_half_deposit).unwrap();
1416 stake_pool.total_lamports -= first_withdraw_result;
1417 stake_pool.pool_token_supply -= first_half_deposit;
1418 let second_half_deposit = deposit_result - first_half_deposit; let second_withdraw_result = stake_pool.calc_lamports_withdraw_amount(second_half_deposit).unwrap();
1420 assert!(first_withdraw_result + second_withdraw_result <= deposit_stake);
1421 }
1422 }
1423 }
1424
1425 #[test]
1426 fn specific_split_withdrawal() {
1427 let total_lamports = 1_100_000_000_000;
1428 let pool_token_supply = 1_000_000_000_000;
1429 let deposit_stake = 3;
1430 let mut stake_pool = StakePool {
1431 total_lamports,
1432 pool_token_supply,
1433 ..StakePool::default()
1434 };
1435 let deposit_result = stake_pool
1436 .calc_pool_tokens_for_deposit(deposit_stake)
1437 .unwrap();
1438 assert!(deposit_result > 0);
1439 stake_pool.total_lamports += deposit_stake;
1440 stake_pool.pool_token_supply += deposit_result;
1441 let withdraw_result = stake_pool
1442 .calc_lamports_withdraw_amount(deposit_result / 2)
1443 .unwrap();
1444 assert!(withdraw_result * 2 <= deposit_stake);
1445 }
1446
1447 #[test]
1448 fn withdraw_all() {
1449 let total_lamports = 1_100_000_000_000;
1450 let pool_token_supply = 1_000_000_000_000;
1451 let mut stake_pool = StakePool {
1452 total_lamports,
1453 pool_token_supply,
1454 ..StakePool::default()
1455 };
1456 let withdraw_result = stake_pool
1458 .calc_lamports_withdraw_amount(pool_token_supply)
1459 .unwrap();
1460 assert_eq!(stake_pool.total_lamports, withdraw_result);
1461
1462 let withdraw_result = stake_pool.calc_lamports_withdraw_amount(1).unwrap();
1464 stake_pool.total_lamports -= withdraw_result;
1465 stake_pool.pool_token_supply -= 1;
1466 let withdraw_result = stake_pool
1467 .calc_lamports_withdraw_amount(stake_pool.pool_token_supply)
1468 .unwrap();
1469 assert_eq!(stake_pool.total_lamports, withdraw_result);
1470
1471 let mut stake_pool = StakePool {
1473 total_lamports,
1474 pool_token_supply,
1475 ..StakePool::default()
1476 };
1477 let withdraw_result = stake_pool
1478 .calc_lamports_withdraw_amount(pool_token_supply - 1)
1479 .unwrap();
1480 stake_pool.total_lamports -= withdraw_result;
1481 stake_pool.pool_token_supply = 1;
1482 assert_ne!(stake_pool.total_lamports, 0);
1483
1484 let withdraw_result = stake_pool.calc_lamports_withdraw_amount(1).unwrap();
1485 assert_eq!(stake_pool.total_lamports, withdraw_result);
1486 }
1487
1488 #[test]
1489 fn check_withdrawal_fee_overflow_failure() {
1490 let old_fee = Fee {
1491 numerator: 1,
1492 denominator: u64::MAX,
1493 };
1494 let new_fee = Fee {
1495 numerator: u64::MAX,
1496 denominator: u64::MAX,
1497 };
1498 assert_eq!(
1499 StakePoolError::CalculationFailure,
1500 new_fee.check_withdrawal(&old_fee).unwrap_err()
1501 );
1502 }
1503
1504 #[test]
1505 fn check_withdrawal_fee_max_increase() {
1506 let old_fee = Fee {
1507 numerator: 10,
1508 denominator: 200,
1509 };
1510 let new_fee = Fee {
1511 numerator: 11_000_000_000_000_001,
1512 denominator: 200_000_000_000_000_000,
1513 };
1514 assert_eq!(
1515 StakePoolError::FeeIncreaseTooHigh,
1516 new_fee.check_withdrawal(&old_fee).unwrap_err()
1517 );
1518 }
1519}