# The 1-second overlap between the during- and after-allocation periods may cause funds to become stuck, permanently.

QV/DonationVoting strategies allow both active-allocation and after-allocation checks to pass at block.timestamp == allocationEndTime, letting a late vote after distribute() strand funds permanently.

- Payout: Contest reward
- Severity: medium
- Program: Gitcoin
- Credited: alexxander
- Disclosed: 2023-09-01
- Write-up quality: 6/10
- Original: https://github.com/sherlock-audit/2023-09-Gitcoin-judging/issues/261
- Library page: https://bugbountylibrary.com/r/sherlock%3A2023-09-Gitcoin%3AM-7
- Tags: domain:web3 impact:funds-loss kind:contest platform:sherlock sev:medium tech:evm tech:solidity technique:race-window technique:source-review vuln:accounting-error vuln:business-logic

**Target:** QVBaseStrategy / DonationVotingMerkleStrategy allocation time checks
**Root cause:** Boundary conditions in _checkOnlyActiveAllocation (block.timestamp > allocationEndTime) and _checkOnlyAfterAllocation (block.timestamp < allocationEndTime) both pass when block.timestamp == allocationEndTime, creating a 1-second window where voting and distribution are simultaneously valid.
**Steps:**
1. Create QVSimpleStrategy pool with allocationEndTime T, fund 100k USDC, 10 voters.
2. All but Bob vote; Carol and Dan each hold 50%.
3. Manager schedules distribute() at block.timestamp == T.
4. Bob submits vote for Carol in same block, ordered after distribute().
5. distribute() pays Carol 50k based on 50%; Bob's vote then shifts totals to 55/45.
6. Dan can claim 45k; remaining 5k is stuck with no withdraw function.
**How found:** Code reading of the boundary comparisons in _checkOnlyActiveAllocation and _checkOnlyAfterAllocation; noticed both pass at block.timestamp == allocationEndTime and that timestamps are seconds not milliseconds, widening the window 1000x versus author assumption.
**Exploitation:** Requires a voter to land a vote transaction in the same block as distribute() at exactly block.timestamp == allocationEndTime, ordered after distribute(). On 1-second-block chains this window is hit every block; on 40s blocks ~2.5% chance, higher with miner collusion. Result: distribution computed on stale tallies, leaving residual funds permanently locked.
**Insight:** Audit inclusive/exclusive boundary operators on paired time-gate modifiers: when one uses `>` and the other `<` against the same timestamp, the equality second satisfies both. Also verify unit assumptions (seconds vs ms) in time constants.
