# L2Governor.execute() accepts Expired / Defeated proposals, attacker front-runs BlackGovernor nudge(), blocks legitimate emission-rate votes, freezes tail emissions

L2Governor.execute() allowed Expired/Defeated proposals; attacker executed an expired nudge() to set the per-epoch flag, blocking a Succeeded emission-rate proposal.

- Payout: Contest reward
- Severity: medium
- Program: Blackhole
- Credited: lonelybones
- Disclosed: 2025-07-03
- Write-up quality: 7/10
- Original: https://code4rena.com/reports/2025-05-blackhole#m-15
- Library page: https://bugbountylibrary.com/r/c4%3A2025-05-blackhole%3AM-15
- Tags: domain:web3 impact:integrity kind:contest platform:code4rena sev:medium tech:evm tech:solidity technique:chained technique:source-review vuln:access-control vuln:business-logic vuln:governance

**Target:** L2Governor / BlackGovernor governance contract calling MinterUpgradeable.nudge()
**Root cause:** L2Governor.execute() permits execution of Defeated and Expired proposals, not just Succeeded ones. MinterUpgradeable.nudge() then reads a no-arg status() that resolves to Pending, taking the 'no change' branch and setting the one-time-per-epoch proposals[_period] flag.
**Steps:**
1. Deploy BlackGovernor, MinterUpgradeable and dependencies; advance Minter to tail emission phase.
2. Proposer creates PROP_EXPIRED and PROP_SUCCEED both targeting Minter.nudge() for the same future emission period.
3. Let PROP_EXPIRED reach state Expired (6); vote PROP_SUCCEED to Succeeded (4).
4. Advance Minter to the target active_period so proposals[period] is false.
5. Attacker calls `blackGovernor.execute(targets, values, calldatas, epochTimeHash_Expired)` — nudge() sets proposals[period]=true and leaves tailEmissionRate unchanged.
6. Legitimate `execute(...epochTimeHash_Succeed)` reverts because nudge()'s guard already tripped.
**How found:** Code review of L2Governor.execute() state check revealed Defeated/Expired were accepted; tracing BlackGovernor's restricted target (nudge()) and nudge()'s no-arg status() call (resolving to Pending) showed the one-time-per-epoch flag could be consumed by an expired proposal.
**Exploitation:** Attacker front-runs execution of a Succeeded emission-rate proposal by executing an Expired proposal for the same Minter period. The expired call to nudge() sets the per-epoch proposals flag and forces tailEmissionRate to 'no change', so the legitimate Succeeded proposal reverts and voter consensus is overridden.
**Insight:** When a governance executor accepts non-Succeeded states, check every downstream target for one-shot guards keyed on epoch/period — an attacker can burn the guard with a dead proposal to grief the legitimate one. Also watch no-arg status() calls that silently resolve to a default enum value.
