# VirtualGenesisDAO.sol:earlyExecute() proposals can be executed two times

earlyExecute() bypasses the executed flag, letting any user re-run a succeeded proposal via public execute(), double-executing governance actions.

- Payout: Contest reward
- Severity: medium
- Program: Virtuals Protocol
- Credited: Fitro
- Disclosed: 2025-08-04
- Write-up quality: 6/10
- Original: https://code4rena.com/reports/2025-04-virtuals-protocol#m-17
- Library page: https://bugbountylibrary.com/r/c4%3A2025-04-virtuals-protocol%3AM-17
- Tags: domain:web3 impact:integrity kind:contest platform:code4rena sev:medium tech:evm tech:solidity technique:source-review vuln:access-control vuln:business-logic

**Target:** VirtualGenesisDAO governance contract (Solidity)
**Root cause:** earlyExecute() calls _executeOperations() directly, which does not set _proposals[proposalId].executed, so the inherited public execute() can run the same proposal again.
**Steps:**
1. EXECUTOR_ROLE calls `earlyExecute(proposalId)` on a succeeded, quorum-reached Active proposal; _earlyExecutions[proposalId]=true but _proposals[proposalId].executed stays false.
2. Any user calls inherited `execute(targets, values, calldatas, descriptionHash)` with the same proposal data.
3. State validation passes (Succeeded/Queued) and executed flag is set, so _executeOperations runs a second time.
**How found:** Code reading: noticed earlyExecute uses a separate _earlyExecutions mapping while _executeOperations' docstring warns it does not set the executed flag, then checked that inherited execute() is public and unguarded.
**Exploitation:** After an executor early-executes a proposal, any account replays the same targets/values/calldatas via the public execute() to trigger the proposal's actions a second time, e.g. double-spending treasury transfers or re-running privileged calls.
**Insight:** When a contract adds a parallel execution path with its own replay guard, check whether the base contract's canonical executed flag is still set; a separate mapping only protects that one entrypoint, not the inherited public one.
