1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
// Copyright (c) Subzero Labs, Inc.
// SPDX-License-Identifier: Apache-2.0
//! The Stake Manager program interface.
declare_id!;
/// Check if an activation is still pending (has not taken effect yet).
///
/// State transitions occur at epoch boundaries (FreezeStakes). An activation
/// is considered "pending" or "activating" until at least one FreezeStakes
/// has occurred since the activation was requested.
///
/// # State Transition Model
///
/// - **Pending/Activating**: `activation_timestamp >= last_freeze_timestamp`
/// - No FreezeStakes has occurred since activation was requested
/// - Stake is in the "activating" state, changes take effect at next epoch boundary
///
/// - **Activated**: `activation_timestamp < last_freeze_timestamp`
/// - At least one FreezeStakes has occurred since activation
/// - Stake has transitioned to "activated" state, delegation is now effective
///
/// # Arguments
/// * `activation_timestamp` - When ActivateStake was called (in milliseconds)
/// * `last_freeze_timestamp` - When FreezeStakes was last called (epoch boundary)
///
/// # Returns
/// * `true` - Activation is pending (activating state)
/// * `false` - Activation has taken effect (activated state)
/// Check if a deactivation is still pending (has not taken effect yet).
///
/// State transitions occur at epoch boundaries (FreezeStakes). A deactivation
/// is considered "pending" or "deactivating" until at least one FreezeStakes
/// has occurred since the deactivation was requested.
///
/// # State Transition Model
///
/// - **Pending/Deactivating**: `deactivation_timestamp >= last_freeze_timestamp`
/// - No FreezeStakes has occurred since deactivation was requested
/// - Stake is in the "deactivating" state, still counted for validator selection
///
/// - **Deactivated**: `deactivation_timestamp < last_freeze_timestamp`
/// - At least one FreezeStakes has occurred since deactivation
/// - Stake has transitioned to "deactivated" state, unbonding period begins
///
/// # Arguments
/// * `deactivation_timestamp` - When DeactivateStake was called (in milliseconds)
/// * `last_freeze_timestamp` - When FreezeStakes was last called (epoch boundary)
///
/// # Returns
/// * `true` - Deactivation is pending (deactivating state)
/// * `false` - Deactivation has taken effect (deactivated state, in unbonding)
/// Check if unbonding is complete using the two-step validation process.
///
/// Unbonding completion requires TWO conditions to be met:
///
/// 1. **State transition**: Stake must have transitioned to "deactivated" state.
/// - A stake is "deactivating" while `deactivation_timestamp >= last_freeze_timestamp`
/// - A stake becomes "deactivated" when `deactivation_timestamp < last_freeze_timestamp`
/// - This ensures at least one FreezeStakes (epoch boundary) has occurred since deactivation
///
/// 2. **Duration enforcement**: The unbonding period must have actually elapsed in real time.
/// - Uses current block timestamp (Clock sysvar) for real-time guarantees
/// - This ensures the time-based penalty has been served, even if FreezeStakes occurred
///
/// # Arguments
/// * `deactivation_timestamp` - When deactivation was requested (in milliseconds)
/// * `last_freeze_timestamp` - When FreezeStakes was last called (epoch boundary)
/// * `unbonding_end` - When unbonding completes (from `ValidatorInfo::end_of_unbonding`)
/// * `current_timestamp` - Current block timestamp from Clock sysvar (in milliseconds)
///
/// # Returns
/// * `true` - Unbonding is complete, stake can be reactivated or fully withdrawn
/// * `false` - Unbonding is not complete, stake is still locked
///
/// # Security
/// This two-step check prevents attackers from bypassing unbonding penalties by
/// exploiting timestamp manipulation or epoch boundary timing.
///
/// # Example
/// ```
/// use rialo_stake_manager_interface::is_unbonding_complete;
///
/// // Deactivated at 1000ms, last freeze at 2000ms, unbonding_end = 1500ms, current time 2000ms
/// // State transition: 1000 < 2000 ✓
/// // Duration: 1500 < 2000 ✓
/// assert!(is_unbonding_complete(1000, 2000, 1500, 2000));
///
/// // Deactivated at 1000ms, last freeze at 2000ms, unbonding_end = 2500ms, current time 2000ms
/// // State transition: 1000 < 2000 ✓
/// // Duration: 2500 >= 2000 ✗
/// assert!(!is_unbonding_complete(1000, 2000, 2500, 2000));
/// ```