diff --git a/libs/sanctum-stake-lib/src/account_resolvers/authorize.rs b/libs/sanctum-stake-lib/src/account_resolvers/authorize.rs index a0204d4..bf4ebc9 100644 --- a/libs/sanctum-stake-lib/src/account_resolvers/authorize.rs +++ b/libs/sanctum-stake-lib/src/account_resolvers/authorize.rs @@ -31,8 +31,7 @@ impl AuthorizeFreeAccounts { authority_getter: fn(&StakeOrInitializedStakeAccount<&'a S>) -> Pubkey, ) -> Result { let Self { stake } = self; - let s = ReadonlyStakeAccount(stake); - let s = s.try_into_valid()?; + let s = ReadonlyStakeAccount::try_new(stake)?; let s = s.try_into_stake_or_initialized()?; let authority = authority_getter(&s); Ok(AuthorizeFreeKeys { diff --git a/libs/sanctum-stake-lib/src/account_resolvers/authorize_checked.rs b/libs/sanctum-stake-lib/src/account_resolvers/authorize_checked.rs index a02ba42..77f5909 100644 --- a/libs/sanctum-stake-lib/src/account_resolvers/authorize_checked.rs +++ b/libs/sanctum-stake-lib/src/account_resolvers/authorize_checked.rs @@ -60,8 +60,7 @@ impl AuthorizeCheckedFreeAccount stake, new_authority, } = self; - let s = ReadonlyStakeAccount(stake); - let s = s.try_into_valid()?; + let s = ReadonlyStakeAccount::try_new(stake)?; let s = s.try_into_stake_or_initialized()?; Ok(AuthorizeCheckedFreeKeys { stake: *stake.pubkey(), diff --git a/libs/sanctum-stake-lib/src/account_resolvers/deactivate.rs b/libs/sanctum-stake-lib/src/account_resolvers/deactivate.rs index 551f863..65d2f01 100644 --- a/libs/sanctum-stake-lib/src/account_resolvers/deactivate.rs +++ b/libs/sanctum-stake-lib/src/account_resolvers/deactivate.rs @@ -16,8 +16,7 @@ impl DeactivateFreeAccounts { pub fn resolve_to_free_keys(&self) -> Result { let Self { stake } = self; - let s = ReadonlyStakeAccount(stake); - let s = s.try_into_valid()?; + let s = ReadonlyStakeAccount::try_new(stake)?; let s = s.try_into_stake_or_initialized()?; let stake_authority = s.stake_meta_authorized_staker(); Ok(DeactivateFreeKeys { diff --git a/libs/sanctum-stake-lib/src/account_resolvers/deactivate_delinquent.rs b/libs/sanctum-stake-lib/src/account_resolvers/deactivate_delinquent.rs index b3b00a0..54f829f 100644 --- a/libs/sanctum-stake-lib/src/account_resolvers/deactivate_delinquent.rs +++ b/libs/sanctum-stake-lib/src/account_resolvers/deactivate_delinquent.rs @@ -16,8 +16,7 @@ impl DeactivateDelinquentFreeAcc stake, reference_vote, } = self; - let s = ReadonlyStakeAccount(stake); - let s = s.try_into_valid()?; + let s = ReadonlyStakeAccount::try_new(stake)?; let s = s.try_into_stake()?; Ok(DeactivateDelinquentKeys { stake: *stake.pubkey(), diff --git a/libs/sanctum-stake-lib/src/account_resolvers/delegate_stake.rs b/libs/sanctum-stake-lib/src/account_resolvers/delegate_stake.rs index 8f96851..7079571 100644 --- a/libs/sanctum-stake-lib/src/account_resolvers/delegate_stake.rs +++ b/libs/sanctum-stake-lib/src/account_resolvers/delegate_stake.rs @@ -17,8 +17,7 @@ impl DelegateStakeFreeAccounts Result { let Self { stake, vote } = self; - let s = ReadonlyStakeAccount(stake); - let s = s.try_into_valid()?; + let s = ReadonlyStakeAccount::try_new(stake)?; let s = s.try_into_stake_or_initialized()?; Ok(DelegateStakeFreeKeys { stake: *stake.pubkey(), diff --git a/libs/sanctum-stake-lib/src/account_resolvers/merge.rs b/libs/sanctum-stake-lib/src/account_resolvers/merge.rs index c5102f7..add42a4 100644 --- a/libs/sanctum-stake-lib/src/account_resolvers/merge.rs +++ b/libs/sanctum-stake-lib/src/account_resolvers/merge.rs @@ -7,8 +7,7 @@ use stake_program_interface::MergeKeys; use crate::ReadonlyStakeAccount; fn read_stake_authority_checked(stake: T) -> Result { - let s = ReadonlyStakeAccount(stake); - let s = s.try_into_valid()?; + let s = ReadonlyStakeAccount::try_new(stake)?; let s = s.try_into_stake_or_initialized()?; Ok(s.stake_meta_authorized_staker()) } diff --git a/libs/sanctum-stake-lib/src/account_resolvers/redelegate.rs b/libs/sanctum-stake-lib/src/account_resolvers/redelegate.rs index 1bb442f..70bafc8 100644 --- a/libs/sanctum-stake-lib/src/account_resolvers/redelegate.rs +++ b/libs/sanctum-stake-lib/src/account_resolvers/redelegate.rs @@ -18,8 +18,7 @@ impl RedelegateFreeAccounts { uninitialized_stake, vote, } = self; - let s = ReadonlyStakeAccount(stake); - let s = s.try_into_valid()?; + let s = ReadonlyStakeAccount::try_new(stake)?; let s = s.try_into_stake_or_initialized()?; Ok(RedelegateFreeKeys { stake: *stake.pubkey(), diff --git a/libs/sanctum-stake-lib/src/account_resolvers/set_lockup.rs b/libs/sanctum-stake-lib/src/account_resolvers/set_lockup.rs index 71e9593..8981d5d 100644 --- a/libs/sanctum-stake-lib/src/account_resolvers/set_lockup.rs +++ b/libs/sanctum-stake-lib/src/account_resolvers/set_lockup.rs @@ -61,8 +61,7 @@ impl SetLockupFreeAccounts { getter: fn(&StakeOrInitializedStakeAccount<&'a S>) -> Pubkey, ) -> Result { let Self { stake } = self; - let s = ReadonlyStakeAccount(stake); - let s = s.try_into_valid()?; + let s = ReadonlyStakeAccount::try_new(stake)?; let s = s.try_into_stake_or_initialized()?; Ok(getter(&s)) } diff --git a/libs/sanctum-stake-lib/src/account_resolvers/split.rs b/libs/sanctum-stake-lib/src/account_resolvers/split.rs index b56ba55..b5b5244 100644 --- a/libs/sanctum-stake-lib/src/account_resolvers/split.rs +++ b/libs/sanctum-stake-lib/src/account_resolvers/split.rs @@ -13,8 +13,7 @@ pub struct SplitFreeAccounts { impl SplitFreeAccounts { pub fn resolve(&self) -> Result { let Self { from, to } = self; - let s = ReadonlyStakeAccount(from); - let s = s.try_into_valid()?; + let s = ReadonlyStakeAccount::try_new(from)?; let s = s.try_into_stake_or_initialized()?; Ok(SplitKeys { from: *from.pubkey(), diff --git a/libs/sanctum-stake-lib/src/account_resolvers/withdraw.rs b/libs/sanctum-stake-lib/src/account_resolvers/withdraw.rs index e05158f..a0c23da 100644 --- a/libs/sanctum-stake-lib/src/account_resolvers/withdraw.rs +++ b/libs/sanctum-stake-lib/src/account_resolvers/withdraw.rs @@ -17,8 +17,7 @@ impl WithdrawFreeAccounts { pub fn resolve_to_free_keys(&self) -> Result { let Self { from, to } = self; - let s = ReadonlyStakeAccount(from); - let s = s.try_into_valid()?; + let s = ReadonlyStakeAccount::try_new(from)?; let s = s.try_into_stake_or_initialized()?; let withdraw_authority = s.stake_meta_authorized_withdrawer(); Ok(WithdrawFreeKeys { diff --git a/libs/sanctum-stake-lib/src/readonly.rs b/libs/sanctum-stake-lib/src/readonly.rs index 60aa81b..d4dc1c9 100644 --- a/libs/sanctum-stake-lib/src/readonly.rs +++ b/libs/sanctum-stake-lib/src/readonly.rs @@ -48,7 +48,7 @@ pub const STAKE_STAKE_CREDITS_OBSERVED_OFFSET: usize = // stakeflags pub const STAKE_STAKE_FLAGS_OFFSET: usize = STAKE_STAKE_CREDITS_OBSERVED_OFFSET + 8; -/// A possible stake account +/// A valid stake account, checked at construction /// /// ## Example /// @@ -60,8 +60,7 @@ pub const STAKE_STAKE_FLAGS_OFFSET: usize = STAKE_STAKE_CREDITS_OBSERVED_OFFSET /// }; /// /// pub fn process(account: &AccountInfo) -> ProgramResult { -/// let account = ReadonlyStakeAccount(account); -/// let account = account.try_into_valid()?; +/// let account = ReadonlyStakeAccount::try_new(account)?; /// let account = account.try_into_stake()?; /// solana_program::msg!("{}", account.stake_stake_credits_observed()); /// Ok(()) @@ -69,7 +68,7 @@ pub const STAKE_STAKE_FLAGS_OFFSET: usize = STAKE_STAKE_CREDITS_OBSERVED_OFFSET /// ``` #[repr(transparent)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ReadonlyStakeAccount(pub T); +pub struct ReadonlyStakeAccount(T); impl ReadonlyStakeAccount { pub fn as_inner(&self) -> &T { @@ -82,53 +81,22 @@ impl ReadonlyStakeAccount { } impl ReadonlyStakeAccount { - pub fn stake_data_is_valid(&self) -> bool { - let d = self.0.data(); - if d.len() != STAKE_ACCOUNT_LEN { - return false; - } - let b: &[u8; 4] = d[STAKE_DISCM_OFFSET..STAKE_DISCM_OFFSET + 4] - .try_into() - .unwrap(); - StakeStateMarker::try_from(*b).is_ok() - } - - pub fn try_into_valid(self) -> Result, ProgramError> { - if !self.stake_data_is_valid() { - return Err(ProgramError::InvalidAccountData); + pub fn try_new(account: T) -> Result { + { + let d = account.data(); + if d.len() != STAKE_ACCOUNT_LEN { + return Err(ProgramError::InvalidAccountData); + } + let b: &[u8; 4] = d[STAKE_DISCM_OFFSET..STAKE_DISCM_OFFSET + 4] + .try_into() + .unwrap(); + StakeStateMarker::try_from(*b)?; } - Ok(ValidStakeAccount(self)) - } -} - -impl AsRef for ReadonlyStakeAccount { - fn as_ref(&self) -> &T { - self.as_inner() - } -} - -// can't impl From> for T due to orphan rules - -/// A stake account that has been checked to contain valid data. -/// -/// The only safe way to create this struct is via [`TryFrom`] -#[repr(transparent)] -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ValidStakeAccount(ReadonlyStakeAccount); - -impl ValidStakeAccount { - pub fn as_readonly(&self) -> &ReadonlyStakeAccount { - &self.0 + Ok(Self(account)) } - pub fn into_readonly(self) -> ReadonlyStakeAccount { - self.0 - } -} - -impl ValidStakeAccount { pub fn stake_state_marker(&self) -> StakeStateMarker { - let d = self.0.as_inner().data(); + let d = self.as_inner().data(); let b: &[u8; 4] = d[STAKE_DISCM_OFFSET..STAKE_DISCM_OFFSET + 4] .try_into() .unwrap(); @@ -158,39 +126,19 @@ impl ValidStakeAccount { } } -impl TryFrom> for ValidStakeAccount { - type Error = ProgramError; - - fn try_from(value: ReadonlyStakeAccount) -> Result { - value.try_into_valid() - } -} - -impl AsRef> for ValidStakeAccount { - fn as_ref(&self) -> &ReadonlyStakeAccount { - self.as_readonly() - } -} - -impl From> for ReadonlyStakeAccount { - fn from(value: ValidStakeAccount) -> Self { - value.into_readonly() - } -} - /// A stake account that has been checked to be in the `Initialized` or `Stake` state. /// /// The only safe way to create this struct is via [`TryFrom>`] #[repr(transparent)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct StakeOrInitializedStakeAccount(ValidStakeAccount); +pub struct StakeOrInitializedStakeAccount(ReadonlyStakeAccount); impl StakeOrInitializedStakeAccount { - pub fn as_valid(&self) -> &ValidStakeAccount { + pub fn as_readonly(&self) -> &ReadonlyStakeAccount { &self.0 } - pub fn into_valid(self) -> ValidStakeAccount { + pub fn into_readonly(self) -> ReadonlyStakeAccount { self.0 } } @@ -205,10 +153,7 @@ impl StakeOrInitializedStakeAccount { } pub fn stake_meta_rent_exempt_reserve(&self) -> u64 { - deser_u64_le_unchecked( - self.0.as_readonly().as_inner(), - STAKE_META_RENT_EXEMPT_RESERVE_OFFSET, - ) + deser_u64_le_unchecked(self.0.as_inner(), STAKE_META_RENT_EXEMPT_RESERVE_OFFSET) } pub fn stake_meta_authorized(&self) -> Authorized { @@ -219,17 +164,11 @@ impl StakeOrInitializedStakeAccount { } pub fn stake_meta_authorized_staker(&self) -> Pubkey { - deser_pubkey_unchecked( - self.0.as_readonly().as_inner(), - STAKE_META_AUTHORIZED_STAKER_OFFSET, - ) + deser_pubkey_unchecked(self.0.as_inner(), STAKE_META_AUTHORIZED_STAKER_OFFSET) } pub fn stake_meta_authorized_withdrawer(&self) -> Pubkey { - deser_pubkey_unchecked( - self.0.as_readonly().as_inner(), - STAKE_META_AUTHORIZED_WITHDRAWER_OFFSET, - ) + deser_pubkey_unchecked(self.0.as_inner(), STAKE_META_AUTHORIZED_WITHDRAWER_OFFSET) } pub fn stake_meta_lockup(&self) -> Lockup { @@ -241,24 +180,15 @@ impl StakeOrInitializedStakeAccount { } pub fn stake_meta_lockup_unix_timestamp(&self) -> i64 { - deser_i64_le_unchecked( - self.0.as_readonly().as_inner(), - STAKE_META_LOCKUP_UNIX_TIMESTAMP_OFFSET, - ) + deser_i64_le_unchecked(self.0.as_inner(), STAKE_META_LOCKUP_UNIX_TIMESTAMP_OFFSET) } pub fn stake_meta_lockup_epoch(&self) -> u64 { - deser_u64_le_unchecked( - self.0.as_readonly().as_inner(), - STAKE_META_LOCKUP_EPOCH_OFFSET, - ) + deser_u64_le_unchecked(self.0.as_inner(), STAKE_META_LOCKUP_EPOCH_OFFSET) } pub fn stake_meta_lockup_custodian(&self) -> Pubkey { - deser_pubkey_unchecked( - self.0.as_readonly().as_inner(), - STAKE_META_LOCKUP_CUSTODIAN_OFFSET, - ) + deser_pubkey_unchecked(self.0.as_inner(), STAKE_META_LOCKUP_CUSTODIAN_OFFSET) } /// The original solana-program API takes an optional custodian pubkey arg and returns true @@ -269,27 +199,29 @@ impl StakeOrInitializedStakeAccount { } pub fn try_into_stake(self) -> Result, ProgramError> { - self.into_valid().try_into() + self.into_readonly().try_into() } } -impl TryFrom> for StakeOrInitializedStakeAccount { +impl TryFrom> + for StakeOrInitializedStakeAccount +{ type Error = ProgramError; - fn try_from(value: ValidStakeAccount) -> Result { + fn try_from(value: ReadonlyStakeAccount) -> Result { value.try_into_stake_or_initialized() } } -impl AsRef> for StakeOrInitializedStakeAccount { - fn as_ref(&self) -> &ValidStakeAccount { - self.as_valid() +impl AsRef> for StakeOrInitializedStakeAccount { + fn as_ref(&self) -> &ReadonlyStakeAccount { + self.as_readonly() } } -impl From> for ValidStakeAccount { +impl From> for ReadonlyStakeAccount { fn from(value: StakeOrInitializedStakeAccount) -> Self { - value.into_valid() + value.into_readonly() } } @@ -331,49 +263,49 @@ impl StakeStakeAccount { pub fn stake_stake_delegation_voter_pubkey(&self) -> Pubkey { deser_pubkey_unchecked( - self.0.as_valid().as_readonly().as_inner(), + self.0.as_readonly().as_inner(), STAKE_STAKE_DELEGATION_VOTER_PUBKEY_OFFSET, ) } pub fn stake_stake_delegation_stake(&self) -> u64 { deser_u64_le_unchecked( - self.0.as_valid().as_readonly().as_inner(), + self.0.as_readonly().as_inner(), STAKE_STAKE_DELEGATION_STAKE_OFFSET, ) } pub fn stake_stake_delegation_activation_epoch(&self) -> u64 { deser_u64_le_unchecked( - self.0.as_valid().as_readonly().as_inner(), + self.0.as_readonly().as_inner(), STAKE_STAKE_DELEGATION_ACTIVATION_EPOCH_OFFSET, ) } pub fn stake_stake_delegation_deactivation_epoch(&self) -> u64 { deser_u64_le_unchecked( - self.0.as_valid().as_readonly().as_inner(), + self.0.as_readonly().as_inner(), STAKE_STAKE_DELEGATION_DEACTIVATION_EPOCH_OFFSET, ) } pub fn stake_stake_delegation_warmup_cooldown_rate_deprecated(&self) -> f64 { deser_f64_le_unchecked( - self.0.as_valid().as_readonly().as_inner(), + self.0.as_readonly().as_inner(), STAKE_STAKE_DELEGATION_WARMUP_COOLDOWN_RATE_DEPRECATED_OFFSET, ) } pub fn stake_stake_credits_observed(&self) -> u64 { deser_u64_le_unchecked( - self.0.as_valid().as_readonly().as_inner(), + self.0.as_readonly().as_inner(), STAKE_STAKE_CREDITS_OBSERVED_OFFSET, ) } // TODO: add tests for 1.17 pub fn stake_stake_flags(&self) -> u8 { - let d = self.0.as_valid().as_readonly().as_inner().data(); + let d = self.0.as_readonly().as_inner().data(); d[STAKE_STAKE_FLAGS_OFFSET] } @@ -382,10 +314,10 @@ impl StakeStakeAccount { } } -impl TryFrom> for StakeStakeAccount { +impl TryFrom> for StakeStakeAccount { type Error = ProgramError; - fn try_from(value: ValidStakeAccount) -> Result { + fn try_from(value: ReadonlyStakeAccount) -> Result { value.try_into_stake() } } @@ -484,10 +416,11 @@ mod tests { proptest! { #[test] fn stake_readonly_matches_full_deser_invalid(data: [u8; STAKE_ACCOUNT_LEN]) { - let account = ReadonlyStakeAccount(AccountData(&data)); + let res = ReadonlyStakeAccount::try_new(AccountData(&data)); let unpack_res = StakeState::deserialize(&mut data.as_ref()); - if !account.stake_data_is_valid() { - prop_assert!(unpack_res.is_err()); + match res { + Ok(_) => prop_assert!(unpack_res.is_ok()), + Err(_) => prop_assert!(unpack_res.is_err()), } } } @@ -542,9 +475,7 @@ mod tests { stake_state .serialize(&mut data.as_mut_slice()) .unwrap(); - let account = ReadonlyStakeAccount(AccountData(&data)); - prop_assert!(account.stake_data_is_valid()); - let account = account.try_into_valid().unwrap(); + let account = ReadonlyStakeAccount::try_new(AccountData(&data)).unwrap(); match stake_state { StakeState::Uninitialized => prop_assert_eq!(account.stake_state_marker(), StakeStateMarker::Uninitialized), StakeState::Initialized(meta) => assert_meta_eq(&account.try_into_stake_or_initialized().unwrap(), &meta, &clock).unwrap(),