# An edge case in amplifier allows user to stake after end time, causing reward to be locked in the contract

MuteAmplifier's first-stake branch skips the endTime check, letting a user stake after the period ends and permanently lock unclaimed rewards in the contract.

- Payout: Contest reward
- Severity: medium
- Program: Mute.io
- Credited: evan
- Disclosed: 2023-04-27
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2023-03-mute-findings/issues/23
- Library page: https://bugbountylibrary.com/r/c4%3A2023-03-mute%3AM-04
- Tags: domain:web3 impact:dos kind:contest platform:code4rena sev:medium tech:solidity technique:source-review vuln:accounting-error vuln:business-logic

**Target:** MuteAmplifier staking contract (Solidity)
**Root cause:** The `block.timestamp < endTime` require is only in the else branch of the firstStakeTime check, so the very first stake ever is accepted regardless of endTime; later reward-drip logic then never runs because _mostRecentValueCalcTime >= endTime.
**Steps:**
1. Deploy state: staking period ends with totalStakers == 0 and rewards funded in contract.
2. Admin prepares `rescueTokens(muteToken, amount)` to recover rewards.
3. Attacker front-runs with `stake(amount)`; firstStakeTime == 0 so the endTime require is skipped and stake succeeds.
4. totalStakers becomes >0; update() sets _mostRecentValueCalcTime = firstStakeTime >= endTime so no rewards drip.
5. rescueTokens now fails the `totalStakers > 0` branch check since amount exceeds balance minus unclaimed rewards; rewards stuck.
**How found:** Code reading of the stake function's conditional: noticed the endTime require sits inside the else branch, meaning the firstStakeTime==0 path bypasses the deadline check, then traced update() and rescueTokens() to confirm the lock.
**Exploitation:** Attacker observes pending rescueTokens tx in mempool, front-runs with a minimal stake to flip totalStakers to 1, blocking reward withdrawal indefinitely; attacker gains nothing but griefs the protocol (rewards locked).
**Insight:** When a deadline check is nested inside a branch that handles the 'already initialized' case, the initialization path silently skips it; trace every state-mutating guard against the zero/default-state path, and check whether rescue/withdraw functions gate on that same state.
