@@ -73,9 +73,24 @@ pub const LEDGERS_PER_WEEK: u32 = 120_960;
7373pub const POLICY_DURATION_LEDGERS : u32 = 30 * LEDGERS_PER_DAY ; // 518_400
7474
7575/// Voting window: ~7 days from claim filing.
76- /// Votes are accepted while `now < filed_at + VOTE_WINDOW_LEDGERS`.
76+ /// Default value for [`crate::storage::get_voting_duration_ledgers`] and historical
77+ /// behaviour: new claims use `voting_deadline_ledger = filed_at + duration` where
78+ /// `duration` defaults to this constant until an admin sets another value in bounds.
7779pub const VOTE_WINDOW_LEDGERS : u32 = 7 * LEDGERS_PER_DAY ; // 120_960
7880
81+ /// Minimum allowed `voting_duration_ledgers` (admin config).
82+ ///
83+ /// **17_280 ledgers (~1 day at nominal 5 s/ledger)** — guarantees at least one full
84+ /// nominal day so voters in all timezones have a reasonable window; shorter windows
85+ /// risk disenfranchisement and operational mistakes.
86+ pub const MIN_VOTING_DURATION_LEDGERS : u32 = LEDGERS_PER_DAY ;
87+
88+ /// Maximum allowed `voting_duration_ledgers` (admin config).
89+ ///
90+ /// **967_680 ledgers (~8 weeks)** — caps how long approved-but-unpaid claim flows and
91+ /// voter duty can stretch; longer windows require a contract upgrade and broader review.
92+ pub const MAX_VOTING_DURATION_LEDGERS : u32 = 8 * LEDGERS_PER_WEEK ;
93+
7994/// Renewal window: holder may renew starting this many ledgers before expiry.
8095/// Renewal is accepted while `end - RENEWAL_WINDOW_LEDGERS <= now < end`.
8196pub const RENEWAL_WINDOW_LEDGERS : u32 = 3 * LEDGERS_PER_DAY ; // 51_840
@@ -144,7 +159,11 @@ pub fn is_in_renewal_window(now: u32, end: u32, window: u32) -> bool {
144159///
145160/// Votes are accepted while `now < filed_at + vote_window`.
146161/// At `now == filed_at + vote_window` the window is closed.
162+ ///
163+ /// **Note:** On-chain claim voting uses [`is_claim_voting_open`] with the stored
164+ /// `voting_deadline_ledger` instead. This helper remains for unit tests and docs.
147165#[ inline]
166+ #[ allow( dead_code) ]
148167pub fn is_vote_open ( now : u32 , filed_at : u32 , vote_window : u32 ) -> bool {
149168 let deadline = filed_at. saturating_add ( vote_window) ;
150169 now < deadline
@@ -154,10 +173,43 @@ pub fn is_vote_open(now: u32, filed_at: u32, vote_window: u32) -> bool {
154173///
155174/// `finalize_claim` may be called once `now >= filed_at + vote_window`.
156175#[ inline]
176+ #[ allow( dead_code) ]
157177pub fn is_vote_deadline_passed ( now : u32 , filed_at : u32 , vote_window : u32 ) -> bool {
158178 !is_vote_open ( now, filed_at, vote_window)
159179}
160180
181+ /// Validates admin-supplied voting duration before it is written to instance storage.
182+ #[ inline]
183+ pub fn validate_voting_duration_ledgers ( v : u32 ) -> Result < ( ) , crate :: validate:: Error > {
184+ if v < MIN_VOTING_DURATION_LEDGERS || v > MAX_VOTING_DURATION_LEDGERS {
185+ return Err ( crate :: validate:: Error :: VotingDurationOutOfBounds ) ;
186+ }
187+ Ok ( ( ) )
188+ }
189+
190+ // ── Per-claim voting deadline (stored on `Claim::voting_deadline_ledger`) ─────
191+
192+ /// Returns `true` while votes are accepted for this claim.
193+ ///
194+ /// **Inclusive deadline:** `voting_deadline_ledger` is the **last** ledger in which a
195+ /// vote may be included (matches product requirement: vote at deadline ledger succeeds;
196+ /// one ledger later reverts).
197+ ///
198+ /// This differs from [`is_vote_open`], which uses a half-open window on
199+ /// `filed_at + vote_window` (exclusive end). Always use this helper (and the stored
200+ /// `voting_deadline_ledger`) for claim voting — never recompute the deadline from the
201+ /// current global duration config.
202+ #[ inline]
203+ pub fn is_claim_voting_open ( now : u32 , voting_deadline_ledger : u32 ) -> bool {
204+ now <= voting_deadline_ledger
205+ }
206+
207+ /// Returns `true` when [`finalize_claim`] may run (voting period fully ended).
208+ #[ inline]
209+ pub fn is_claim_past_voting_deadline ( now : u32 , voting_deadline_ledger : u32 ) -> bool {
210+ now > voting_deadline_ledger
211+ }
212+
161213/// Returns `true` if the rate-limit window has elapsed since `last_filed_at`.
162214///
163215/// A new claim may be filed once `now >= last_filed_at + rate_limit_window`.
@@ -352,4 +404,30 @@ mod tests {
352404 fn approx_secs_remaining_zero_when_expired ( ) {
353405 assert_eq ! ( approx_secs_remaining( 100 , 100 ) , 0 ) ;
354406 }
407+
408+ // ── is_claim_voting_open (inclusive deadline) ─────────────────────────────
409+
410+ #[ test]
411+ fn claim_vote_open_at_deadline_ledger ( ) {
412+ assert ! ( is_claim_voting_open( 200 , 200 ) ) ;
413+ }
414+
415+ #[ test]
416+ fn claim_vote_closed_one_ledger_after_deadline ( ) {
417+ assert ! ( !is_claim_voting_open( 201 , 200 ) ) ;
418+ }
419+
420+ #[ test]
421+ fn claim_past_deadline_strictly_after_deadline ( ) {
422+ assert ! ( !is_claim_past_voting_deadline( 200 , 200 ) ) ;
423+ assert ! ( is_claim_past_voting_deadline( 201 , 200 ) ) ;
424+ }
425+
426+ #[ test]
427+ fn validate_voting_duration_bounds ( ) {
428+ assert ! ( validate_voting_duration_ledgers( MIN_VOTING_DURATION_LEDGERS ) . is_ok( ) ) ;
429+ assert ! ( validate_voting_duration_ledgers( MAX_VOTING_DURATION_LEDGERS ) . is_ok( ) ) ;
430+ assert ! ( validate_voting_duration_ledgers( MIN_VOTING_DURATION_LEDGERS - 1 ) . is_err( ) ) ;
431+ assert ! ( validate_voting_duration_ledgers( MAX_VOTING_DURATION_LEDGERS + 1 ) . is_err( ) ) ;
432+ }
355433}
0 commit comments