@@ -19,6 +19,8 @@ pub enum StakingKey {
1919 Vote ( Address , u32 ) ,
2020 /// Whether a proposal is currently active.
2121 ProposalActive ( u32 ) ,
22+ /// List of all currently-active proposal IDs (used to gate withdrawals).
23+ ActiveProposals ,
2224}
2325
2426// ---------------------------------------------------------------------------
@@ -58,14 +60,29 @@ impl StakingContract {
5860 }
5961
6062 /// Withdraw `amount` RS-Tokens for `staker`.
61- /// Panics if the staker has any active vote outstanding.
63+ /// Panics if the staker has any active vote outstanding (checked via `ActiveProposals` list) .
6264 pub fn withdraw_tokens ( env : Env , staker : Address , amount : i128 ) {
6365 staker. require_auth ( ) ;
6466
6567 if amount <= 0 {
6668 panic_with_error ! ( & env, StakingError :: InvalidAmount ) ;
6769 }
6870
71+ // Block withdrawal if the staker has voted on any currently-active proposal.
72+ let active_proposals: soroban_sdk:: Vec < u32 > = env
73+ . storage ( )
74+ . instance ( )
75+ . get ( & StakingKey :: ActiveProposals )
76+ . unwrap_or_else ( || soroban_sdk:: Vec :: new ( & env) ) ;
77+
78+ for proposal_id in active_proposals. iter ( ) {
79+ let vote_key = StakingKey :: Vote ( staker. clone ( ) , proposal_id) ;
80+ let voted: i128 = env. storage ( ) . instance ( ) . get ( & vote_key) . unwrap_or ( 0 ) ;
81+ if voted > 0 {
82+ panic_with_error ! ( & env, StakingError :: VoteActive ) ;
83+ }
84+ }
85+
6986 let stake_key = StakingKey :: Stake ( staker. clone ( ) ) ;
7087 let current: i128 = env. storage ( ) . instance ( ) . get ( & stake_key) . unwrap_or ( 0 ) ;
7188
@@ -110,6 +127,19 @@ impl StakingContract {
110127 env. storage ( )
111128 . instance ( )
112129 . set ( & StakingKey :: ProposalActive ( proposal_id) , & true ) ;
130+
131+ // Track in the active proposals list so withdraw_tokens can check it.
132+ let mut active: soroban_sdk:: Vec < u32 > = env
133+ . storage ( )
134+ . instance ( )
135+ . get ( & StakingKey :: ActiveProposals )
136+ . unwrap_or_else ( || soroban_sdk:: Vec :: new ( & env) ) ;
137+ if !active. contains ( proposal_id) {
138+ active. push_back ( proposal_id) ;
139+ env. storage ( )
140+ . instance ( )
141+ . set ( & StakingKey :: ActiveProposals , & active) ;
142+ }
113143 }
114144
115145 /// Close a proposal, allowing stakers to withdraw again.
@@ -118,6 +148,22 @@ impl StakingContract {
118148 env. storage ( )
119149 . instance ( )
120150 . set ( & StakingKey :: ProposalActive ( proposal_id) , & false ) ;
151+
152+ // Remove from the active proposals list.
153+ let active: soroban_sdk:: Vec < u32 > = env
154+ . storage ( )
155+ . instance ( )
156+ . get ( & StakingKey :: ActiveProposals )
157+ . unwrap_or_else ( || soroban_sdk:: Vec :: new ( & env) ) ;
158+ let mut updated: soroban_sdk:: Vec < u32 > = soroban_sdk:: Vec :: new ( & env) ;
159+ for id in active. iter ( ) {
160+ if id != proposal_id {
161+ updated. push_back ( id) ;
162+ }
163+ }
164+ env. storage ( )
165+ . instance ( )
166+ . set ( & StakingKey :: ActiveProposals , & updated) ;
121167 }
122168
123169 pub fn get_stake ( env : Env , staker : Address ) -> i128 {
@@ -179,6 +225,31 @@ mod tests {
179225 client. withdraw_tokens ( & staker, & 100 ) ;
180226 }
181227
228+ #[ test]
229+ #[ should_panic]
230+ fn cannot_withdraw_while_vote_active ( ) {
231+ let ( env, admin, client) = setup ( ) ;
232+ let staker = Address :: generate ( & env) ;
233+ client. stake_tokens ( & staker, & 100 ) ;
234+ client. open_proposal ( & admin, & 5 ) ;
235+ client. cast_vote ( & staker, & 5 , & 50 ) ;
236+ // Should panic: staker has an active vote on proposal 5
237+ client. withdraw_tokens ( & staker, & 10 ) ;
238+ }
239+
240+ #[ test]
241+ fn can_withdraw_after_proposal_closed ( ) {
242+ let ( env, admin, client) = setup ( ) ;
243+ let staker = Address :: generate ( & env) ;
244+ client. stake_tokens ( & staker, & 100 ) ;
245+ client. open_proposal ( & admin, & 6 ) ;
246+ client. cast_vote ( & staker, & 6 , & 50 ) ;
247+ client. close_proposal ( & admin, & 6 ) ;
248+ // Proposal closed — withdrawal should succeed
249+ client. withdraw_tokens ( & staker, & 10 ) ;
250+ assert_eq ! ( client. get_stake( & staker) , 90 ) ;
251+ }
252+
182253 #[ test]
183254 fn cast_vote_records_weight ( ) {
184255 let ( env, admin, client) = setup ( ) ;
0 commit comments