gemachain_program/stake/
state.rs

1#![allow(clippy::integer_arithmetic)]
2use {
3    crate::{
4        clock::{Clock, Epoch, UnixTimestamp},
5        instruction::InstructionError,
6        pubkey::Pubkey,
7        rent::Rent,
8        stake::{
9            config::Config,
10            instruction::{LockupArgs, StakeError},
11        },
12        stake_history::StakeHistory,
13    },
14    borsh::{maybestd::io, BorshDeserialize, BorshSchema},
15    std::collections::HashSet,
16};
17
18#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)]
19#[allow(clippy::large_enum_variant)]
20pub enum StakeState {
21    Uninitialized,
22    Initialized(Meta),
23    Stake(Meta, Stake),
24    RewardsPool,
25}
26
27impl BorshDeserialize for StakeState {
28    fn deserialize(buf: &mut &[u8]) -> io::Result<Self> {
29        let enum_value: u32 = BorshDeserialize::deserialize(buf)?;
30        match enum_value {
31            0 => Ok(StakeState::Uninitialized),
32            1 => {
33                let meta: Meta = BorshDeserialize::deserialize(buf)?;
34                Ok(StakeState::Initialized(meta))
35            }
36            2 => {
37                let meta: Meta = BorshDeserialize::deserialize(buf)?;
38                let stake: Stake = BorshDeserialize::deserialize(buf)?;
39                Ok(StakeState::Stake(meta, stake))
40            }
41            3 => Ok(StakeState::RewardsPool),
42            _ => Err(io::Error::new(
43                io::ErrorKind::InvalidData,
44                "Invalid enum value",
45            )),
46        }
47    }
48}
49
50impl Default for StakeState {
51    fn default() -> Self {
52        StakeState::Uninitialized
53    }
54}
55
56impl StakeState {
57    pub fn get_rent_exempt_reserve(rent: &Rent) -> u64 {
58        rent.minimum_balance(std::mem::size_of::<StakeState>())
59    }
60
61    pub fn stake(&self) -> Option<Stake> {
62        match self {
63            StakeState::Stake(_meta, stake) => Some(*stake),
64            _ => None,
65        }
66    }
67
68    pub fn delegation(&self) -> Option<Delegation> {
69        match self {
70            StakeState::Stake(_meta, stake) => Some(stake.delegation),
71            _ => None,
72        }
73    }
74
75    pub fn authorized(&self) -> Option<Authorized> {
76        match self {
77            StakeState::Stake(meta, _stake) => Some(meta.authorized),
78            StakeState::Initialized(meta) => Some(meta.authorized),
79            _ => None,
80        }
81    }
82
83    pub fn lockup(&self) -> Option<Lockup> {
84        self.meta().map(|meta| meta.lockup)
85    }
86
87    pub fn meta(&self) -> Option<Meta> {
88        match self {
89            StakeState::Stake(meta, _stake) => Some(*meta),
90            StakeState::Initialized(meta) => Some(*meta),
91            _ => None,
92        }
93    }
94}
95
96#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample)]
97pub enum StakeAuthorize {
98    Staker,
99    Withdrawer,
100}
101
102#[derive(
103    Default,
104    Debug,
105    Serialize,
106    Deserialize,
107    PartialEq,
108    Clone,
109    Copy,
110    AbiExample,
111    BorshDeserialize,
112    BorshSchema,
113)]
114pub struct Lockup {
115    /// UnixTimestamp at which this stake will allow withdrawal, unless the
116    ///   transaction is signed by the custodian
117    pub unix_timestamp: UnixTimestamp,
118    /// epoch height at which this stake will allow withdrawal, unless the
119    ///   transaction is signed by the custodian
120    pub epoch: Epoch,
121    /// custodian signature on a transaction exempts the operation from
122    ///  lockup constraints
123    pub custodian: Pubkey,
124}
125
126impl Lockup {
127    pub fn is_in_force(&self, clock: &Clock, custodian: Option<&Pubkey>) -> bool {
128        if custodian == Some(&self.custodian) {
129            return false;
130        }
131        self.unix_timestamp > clock.unix_timestamp || self.epoch > clock.epoch
132    }
133}
134
135#[derive(
136    Default,
137    Debug,
138    Serialize,
139    Deserialize,
140    PartialEq,
141    Clone,
142    Copy,
143    AbiExample,
144    BorshDeserialize,
145    BorshSchema,
146)]
147pub struct Authorized {
148    pub staker: Pubkey,
149    pub withdrawer: Pubkey,
150}
151
152impl Authorized {
153    pub fn auto(authorized: &Pubkey) -> Self {
154        Self {
155            staker: *authorized,
156            withdrawer: *authorized,
157        }
158    }
159    pub fn check(
160        &self,
161        signers: &HashSet<Pubkey>,
162        stake_authorize: StakeAuthorize,
163    ) -> Result<(), InstructionError> {
164        match stake_authorize {
165            StakeAuthorize::Staker if signers.contains(&self.staker) => Ok(()),
166            StakeAuthorize::Withdrawer if signers.contains(&self.withdrawer) => Ok(()),
167            _ => Err(InstructionError::MissingRequiredSignature),
168        }
169    }
170
171    pub fn authorize(
172        &mut self,
173        signers: &HashSet<Pubkey>,
174        new_authorized: &Pubkey,
175        stake_authorize: StakeAuthorize,
176        lockup_custodian_args: Option<(&Lockup, &Clock, Option<&Pubkey>)>,
177    ) -> Result<(), InstructionError> {
178        match stake_authorize {
179            StakeAuthorize::Staker => {
180                // Allow either the staker or the withdrawer to change the staker key
181                if !signers.contains(&self.staker) && !signers.contains(&self.withdrawer) {
182                    return Err(InstructionError::MissingRequiredSignature);
183                }
184                self.staker = *new_authorized
185            }
186            StakeAuthorize::Withdrawer => {
187                if let Some((lockup, clock, custodian)) = lockup_custodian_args {
188                    if lockup.is_in_force(clock, None) {
189                        match custodian {
190                            None => {
191                                return Err(StakeError::CustodianMissing.into());
192                            }
193                            Some(custodian) => {
194                                if !signers.contains(custodian) {
195                                    return Err(StakeError::CustodianSignatureMissing.into());
196                                }
197
198                                if lockup.is_in_force(clock, Some(custodian)) {
199                                    return Err(StakeError::LockupInForce.into());
200                                }
201                            }
202                        }
203                    }
204                }
205                self.check(signers, stake_authorize)?;
206                self.withdrawer = *new_authorized
207            }
208        }
209        Ok(())
210    }
211}
212
213#[derive(
214    Default,
215    Debug,
216    Serialize,
217    Deserialize,
218    PartialEq,
219    Clone,
220    Copy,
221    AbiExample,
222    BorshDeserialize,
223    BorshSchema,
224)]
225pub struct Meta {
226    pub rent_exempt_reserve: u64,
227    pub authorized: Authorized,
228    pub lockup: Lockup,
229}
230
231impl Meta {
232    pub fn set_lockup(
233        &mut self,
234        lockup: &LockupArgs,
235        signers: &HashSet<Pubkey>,
236        clock: Option<&Clock>,
237    ) -> Result<(), InstructionError> {
238        match clock {
239            None => {
240                // pre-stake_program_v4 behavior: custodian can set lockups at any time
241                if !signers.contains(&self.lockup.custodian) {
242                    return Err(InstructionError::MissingRequiredSignature);
243                }
244            }
245            Some(clock) => {
246                // post-stake_program_v4 behavior:
247                // * custodian can update the lockup while in force
248                // * withdraw authority can set a new lockup
249                //
250                if self.lockup.is_in_force(clock, None) {
251                    if !signers.contains(&self.lockup.custodian) {
252                        return Err(InstructionError::MissingRequiredSignature);
253                    }
254                } else if !signers.contains(&self.authorized.withdrawer) {
255                    return Err(InstructionError::MissingRequiredSignature);
256                }
257            }
258        }
259        if let Some(unix_timestamp) = lockup.unix_timestamp {
260            self.lockup.unix_timestamp = unix_timestamp;
261        }
262        if let Some(epoch) = lockup.epoch {
263            self.lockup.epoch = epoch;
264        }
265        if let Some(custodian) = lockup.custodian {
266            self.lockup.custodian = custodian;
267        }
268        Ok(())
269    }
270
271    pub fn rewrite_rent_exempt_reserve(
272        &mut self,
273        rent: &Rent,
274        data_len: usize,
275    ) -> Option<(u64, u64)> {
276        let corrected_rent_exempt_reserve = rent.minimum_balance(data_len);
277        if corrected_rent_exempt_reserve != self.rent_exempt_reserve {
278            // We forcibly update rent_excempt_reserve even
279            // if rent_exempt_reserve > account_balance, hoping user might restore
280            // rent_exempt status by depositing.
281            let (old, new) = (self.rent_exempt_reserve, corrected_rent_exempt_reserve);
282            self.rent_exempt_reserve = corrected_rent_exempt_reserve;
283            Some((old, new))
284        } else {
285            None
286        }
287    }
288
289    pub fn auto(authorized: &Pubkey) -> Self {
290        Self {
291            authorized: Authorized::auto(authorized),
292            ..Meta::default()
293        }
294    }
295}
296
297#[derive(
298    Debug, Serialize, Deserialize, PartialEq, Clone, Copy, AbiExample, BorshDeserialize, BorshSchema,
299)]
300pub struct Delegation {
301    /// to whom the stake is delegated
302    pub voter_pubkey: Pubkey,
303    /// activated stake amount, set at delegate() time
304    pub stake: u64,
305    /// epoch at which this stake was activated, std::Epoch::MAX if is a bootstrap stake
306    pub activation_epoch: Epoch,
307    /// epoch the stake was deactivated, std::Epoch::MAX if not deactivated
308    pub deactivation_epoch: Epoch,
309    /// how much stake we can activate per-epoch as a fraction of currently effective stake
310    pub warmup_cooldown_rate: f64,
311}
312
313impl Default for Delegation {
314    fn default() -> Self {
315        Self {
316            voter_pubkey: Pubkey::default(),
317            stake: 0,
318            activation_epoch: 0,
319            deactivation_epoch: std::u64::MAX,
320            warmup_cooldown_rate: Config::default().warmup_cooldown_rate,
321        }
322    }
323}
324
325impl Delegation {
326    pub fn new(
327        voter_pubkey: &Pubkey,
328        stake: u64,
329        activation_epoch: Epoch,
330        warmup_cooldown_rate: f64,
331    ) -> Self {
332        Self {
333            voter_pubkey: *voter_pubkey,
334            stake,
335            activation_epoch,
336            warmup_cooldown_rate,
337            ..Delegation::default()
338        }
339    }
340    pub fn is_bootstrap(&self) -> bool {
341        self.activation_epoch == std::u64::MAX
342    }
343
344    pub fn stake(&self, epoch: Epoch, history: Option<&StakeHistory>) -> u64 {
345        self.stake_activating_and_deactivating(epoch, history).0
346    }
347
348    // returned tuple is (effective, activating, deactivating) stake
349    #[allow(clippy::comparison_chain)]
350    pub fn stake_activating_and_deactivating(
351        &self,
352        target_epoch: Epoch,
353        history: Option<&StakeHistory>,
354    ) -> (u64, u64, u64) {
355        let delegated_stake = self.stake;
356
357        // first, calculate an effective and activating stake
358        let (effective_stake, activating_stake) = self.stake_and_activating(target_epoch, history);
359
360        // then de-activate some portion if necessary
361        if target_epoch < self.deactivation_epoch {
362            // not deactivated
363            (effective_stake, activating_stake, 0)
364        } else if target_epoch == self.deactivation_epoch {
365            // can only deactivate what's activated
366            (effective_stake, 0, effective_stake.min(delegated_stake))
367        } else if let Some((history, mut prev_epoch, mut prev_cluster_stake)) =
368            history.and_then(|history| {
369                history
370                    .get(&self.deactivation_epoch)
371                    .map(|cluster_stake_at_deactivation_epoch| {
372                        (
373                            history,
374                            self.deactivation_epoch,
375                            cluster_stake_at_deactivation_epoch,
376                        )
377                    })
378            })
379        {
380            // target_epoch > self.deactivation_epoch
381
382            // loop from my deactivation epoch until the target epoch
383            // current effective stake is updated using its previous epoch's cluster stake
384            let mut current_epoch;
385            let mut current_effective_stake = effective_stake;
386            loop {
387                current_epoch = prev_epoch + 1;
388                // if there is no deactivating stake at prev epoch, we should have been
389                // fully undelegated at this moment
390                if prev_cluster_stake.deactivating == 0 {
391                    break;
392                }
393
394                // I'm trying to get to zero, how much of the deactivation in stake
395                //   this account is entitled to take
396                let weight =
397                    current_effective_stake as f64 / prev_cluster_stake.deactivating as f64;
398
399                // portion of newly not-effective cluster stake I'm entitled to at current epoch
400                let newly_not_effective_cluster_stake =
401                    prev_cluster_stake.effective as f64 * self.warmup_cooldown_rate;
402                let newly_not_effective_stake =
403                    ((weight * newly_not_effective_cluster_stake) as u64).max(1);
404
405                current_effective_stake =
406                    current_effective_stake.saturating_sub(newly_not_effective_stake);
407                if current_effective_stake == 0 {
408                    break;
409                }
410
411                if current_epoch >= target_epoch {
412                    break;
413                }
414                if let Some(current_cluster_stake) = history.get(&current_epoch) {
415                    prev_epoch = current_epoch;
416                    prev_cluster_stake = current_cluster_stake;
417                } else {
418                    break;
419                }
420            }
421
422            // deactivating stake should equal to all of currently remaining effective stake
423            (current_effective_stake, 0, current_effective_stake)
424        } else {
425            // no history or I've dropped out of history, so assume fully deactivated
426            (0, 0, 0)
427        }
428    }
429
430    // returned tuple is (effective, activating) stake
431    fn stake_and_activating(
432        &self,
433        target_epoch: Epoch,
434        history: Option<&StakeHistory>,
435    ) -> (u64, u64) {
436        let delegated_stake = self.stake;
437
438        if self.is_bootstrap() {
439            // fully effective immediately
440            (delegated_stake, 0)
441        } else if self.activation_epoch == self.deactivation_epoch {
442            // activated but instantly deactivated; no stake at all regardless of target_epoch
443            // this must be after the bootstrap check and before all-is-activating check
444            (0, 0)
445        } else if target_epoch == self.activation_epoch {
446            // all is activating
447            (0, delegated_stake)
448        } else if target_epoch < self.activation_epoch {
449            // not yet enabled
450            (0, 0)
451        } else if let Some((history, mut prev_epoch, mut prev_cluster_stake)) =
452            history.and_then(|history| {
453                history
454                    .get(&self.activation_epoch)
455                    .map(|cluster_stake_at_activation_epoch| {
456                        (
457                            history,
458                            self.activation_epoch,
459                            cluster_stake_at_activation_epoch,
460                        )
461                    })
462            })
463        {
464            // target_epoch > self.activation_epoch
465
466            // loop from my activation epoch until the target epoch summing up my entitlement
467            // current effective stake is updated using its previous epoch's cluster stake
468            let mut current_epoch;
469            let mut current_effective_stake = 0;
470            loop {
471                current_epoch = prev_epoch + 1;
472                // if there is no activating stake at prev epoch, we should have been
473                // fully effective at this moment
474                if prev_cluster_stake.activating == 0 {
475                    break;
476                }
477
478                // how much of the growth in stake this account is
479                //  entitled to take
480                let remaining_activating_stake = delegated_stake - current_effective_stake;
481                let weight =
482                    remaining_activating_stake as f64 / prev_cluster_stake.activating as f64;
483
484                // portion of newly effective cluster stake I'm entitled to at current epoch
485                let newly_effective_cluster_stake =
486                    prev_cluster_stake.effective as f64 * self.warmup_cooldown_rate;
487                let newly_effective_stake =
488                    ((weight * newly_effective_cluster_stake) as u64).max(1);
489
490                current_effective_stake += newly_effective_stake;
491                if current_effective_stake >= delegated_stake {
492                    current_effective_stake = delegated_stake;
493                    break;
494                }
495
496                if current_epoch >= target_epoch || current_epoch >= self.deactivation_epoch {
497                    break;
498                }
499                if let Some(current_cluster_stake) = history.get(&current_epoch) {
500                    prev_epoch = current_epoch;
501                    prev_cluster_stake = current_cluster_stake;
502                } else {
503                    break;
504                }
505            }
506
507            (
508                current_effective_stake,
509                delegated_stake - current_effective_stake,
510            )
511        } else {
512            // no history or I've dropped out of history, so assume fully effective
513            (delegated_stake, 0)
514        }
515    }
516}
517
518#[derive(
519    Debug,
520    Default,
521    Serialize,
522    Deserialize,
523    PartialEq,
524    Clone,
525    Copy,
526    AbiExample,
527    BorshDeserialize,
528    BorshSchema,
529)]
530pub struct Stake {
531    pub delegation: Delegation,
532    /// credits observed is credits from vote account state when delegated or redeemed
533    pub credits_observed: u64,
534}
535
536impl Stake {
537    pub fn stake(&self, epoch: Epoch, history: Option<&StakeHistory>) -> u64 {
538        self.delegation.stake(epoch, history)
539    }
540
541    pub fn split(
542        &mut self,
543        remaining_stake_delta: u64,
544        split_stake_amount: u64,
545    ) -> Result<Self, StakeError> {
546        if remaining_stake_delta > self.delegation.stake {
547            return Err(StakeError::InsufficientStake);
548        }
549        self.delegation.stake -= remaining_stake_delta;
550        let new = Self {
551            delegation: Delegation {
552                stake: split_stake_amount,
553                ..self.delegation
554            },
555            ..*self
556        };
557        Ok(new)
558    }
559
560    pub fn deactivate(&mut self, epoch: Epoch) -> Result<(), StakeError> {
561        if self.delegation.deactivation_epoch != std::u64::MAX {
562            Err(StakeError::AlreadyDeactivated)
563        } else {
564            self.delegation.deactivation_epoch = epoch;
565            Ok(())
566        }
567    }
568}
569
570#[cfg(test)]
571mod test {
572    use {
573        super::*, crate::borsh::try_from_slice_unchecked, assert_matches::assert_matches,
574        bincode::serialize,
575    };
576
577    fn check_borsh_deserialization(stake: StakeState) {
578        let serialized = serialize(&stake).unwrap();
579        let deserialized = StakeState::try_from_slice(&serialized).unwrap();
580        assert_eq!(stake, deserialized);
581    }
582
583    #[test]
584    fn bincode_vs_borsh() {
585        check_borsh_deserialization(StakeState::Uninitialized);
586        check_borsh_deserialization(StakeState::RewardsPool);
587        check_borsh_deserialization(StakeState::Initialized(Meta {
588            rent_exempt_reserve: u64::MAX,
589            authorized: Authorized {
590                staker: Pubkey::new_unique(),
591                withdrawer: Pubkey::new_unique(),
592            },
593            lockup: Lockup::default(),
594        }));
595        check_borsh_deserialization(StakeState::Stake(
596            Meta {
597                rent_exempt_reserve: 1,
598                authorized: Authorized {
599                    staker: Pubkey::new_unique(),
600                    withdrawer: Pubkey::new_unique(),
601                },
602                lockup: Lockup::default(),
603            },
604            Stake {
605                delegation: Delegation {
606                    voter_pubkey: Pubkey::new_unique(),
607                    stake: u64::MAX,
608                    activation_epoch: Epoch::MAX,
609                    deactivation_epoch: Epoch::MAX,
610                    warmup_cooldown_rate: f64::MAX,
611                },
612                credits_observed: 1,
613            },
614        ));
615    }
616
617    #[test]
618    fn borsh_deserialization_live_data() {
619        let data = [
620            1, 0, 0, 0, 128, 213, 34, 0, 0, 0, 0, 0, 133, 0, 79, 231, 141, 29, 73, 61, 232, 35,
621            119, 124, 168, 12, 120, 216, 195, 29, 12, 166, 139, 28, 36, 182, 186, 154, 246, 149,
622            224, 109, 52, 100, 133, 0, 79, 231, 141, 29, 73, 61, 232, 35, 119, 124, 168, 12, 120,
623            216, 195, 29, 12, 166, 139, 28, 36, 182, 186, 154, 246, 149, 224, 109, 52, 100, 0, 0,
624            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
625            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
626            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
627            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
628            0, 0, 0, 0, 0, 0,
629        ];
630        // As long as we get the 4-byte enum and the first field right, then
631        // we're sure the rest works out
632        let deserialized = try_from_slice_unchecked::<StakeState>(&data).unwrap();
633        assert_matches!(
634            deserialized,
635            StakeState::Initialized(Meta {
636                rent_exempt_reserve: 2282880,
637                ..
638            })
639        );
640    }
641}