# Persistent Contract Call revert prevents finalizing a ballot

DAO._executeApproval calls external contract without try/catch; a reverting callFromDAO bricks ballot finalization, locking the proposer's active-proposal slot.

- Payout: Contest reward
- Severity: medium
- Program: Salty.IO
- Credited: vnavascues
- Disclosed: 2024-04-19
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2024-01-salty-findings/issues/1009
- Library page: https://bugbountylibrary.com/r/c4%3A2024-01-salty%3AM-02
- Tags: domain:web3 impact:dos kind:contest platform:code4rena sev:medium tech:evm tech:solidity technique:source-review vuln:business-logic vuln:dos vuln:governance

**Target:** DAO.sol _executeApproval / _finalizeApprovalBallot
**Root cause:** The CALL_CONTRACT branch invokes ICalledContract.callFromDAO without try/catch, so any revert propagates and aborts markBallotAsFinalized.
**Steps:**
1. Deploy a contract implementing ICalledContract whose callFromDAO always reverts.
2. As a staker, call proposals.proposeCallContract(receiver, 123, "description").
3. castVote(ballotID, Vote.YES) and warp 11 days.
4. Call dao.finalizeBallot(ballotID) — it reverts with the receiver's error.
5. Observe ballotIsLive stays true and userHasActiveProposal(alice) stays true.
**How found:** Code review of _finalizeApprovalBallot: noticed markBallotAsFinalized runs after _executeApproval, and the CALL_CONTRACT branch has no error handling, so an external revert skips finalization.
**Exploitation:** A proposer (or any contract they point to) makes callFromDAO revert; the approved ballot can never be finalized, permanently occupying the proposer's active-proposal slot and blocking new proposals until they migrate SALT to a new account or quorum votes NO.
**Insight:** When a state transition (marking finalized) is sequenced after an external call, any revert in the callee leaves state half-applied; check whether the caller can choose a reverting callee to grief a slot or lock.
