# The proposal expiration logic is incorrect

Reversed comparison in Initia multisig is_proposal_expired marks active proposals expired and expired ones active, blocking legitimate voting and execution.

- Payout: Contest reward
- Severity: medium
- Program: Initia
- Credited: Rhaydden
- Disclosed: 2025-04-10
- Write-up quality: 6/10
- Original: https://code4rena.com/reports/2025-01-initia-move#m-03
- Library page: https://bugbountylibrary.com/r/c4%3A2025-01-initia-move%3AM-03
- Tags: domain:web3 impact:dos kind:contest platform:code4rena sev:medium tech:solidity technique:source-review vuln:business-logic vuln:dos

**Target:** Move multisig module (multisig.move) in initia_stdlib and minitia_stdlib
**Root cause:** is_proposal_expired compares (max_period + proposal_height) >= current_height instead of current_height >= (max_period + proposal_height), inverting the expiration condition for both height and timestamp checks.
**Steps:**
1. Create multisig with max voting period of 100 blocks via `create_multisig_account`.
2. Set block info to height 1000 with `set_block_info(1000, 0)`.
3. Create proposal at height 1000 via `create_proposal`.
4. Advance to height 1050 (still within voting window) and call `vote_proposal`; it aborts EPROPOSAL_EXPIRED because 1100 >= 1050 evaluates true.
5. Conversely, at height 1101 the proposal is treated as active, allowing votes past the deadline.
**How found:** Code review of the expiration helper in multisig.move; the reversed `>=` operator stood out against the intended semantics, confirmed with a Move unit test at boundary heights.
**Exploitation:** No attacker value extraction; the flaw is a logic inversion causing DoS on legitimate multisig proposal voting/execution and permitting votes after the intended deadline.
**Insight:** In time/height window checks, verify operand order against the semantic name: 'expired' should be current >= deadline, not deadline >= current. Boundary tests at deadline-1 and deadline+1 expose inversions immediately.
