# Reusing a SALT that has already been used for voting can allow a malicious proposal to pass and compromise the protocol

Salty.IO ballots have no max duration, so a voter can unstake SALT, transfer to a fresh account, and re-vote to inflate quorum and pass a malicious proposal.

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

**Target:** DAO Proposals.sol castVote / canFinalizeBallot
**Root cause:** Voting power is read live from staking.userShareForPool at castVote time with no snapshot, and ballots have no maximum end time, so unstaked-and-transferred SALT can vote again on the same ballot.
**Steps:**
1. `staking.stakeSALT(100000 ether)`
2. `proposals.proposeCallContract(randomAddress, 1000, "Malicious call")`
3. `proposals.castVote(ballotID, Vote.YES)`
4. `staking.unstake(100000 ether, 2)` then wait and `staking.recoverSALT(id)`
5. `salt.transfer(bob, 100000 ether)`
6. As bob: `staking.stakeSALT(100000 ether)` and `proposals.castVote(ballotID, Vote.YES)` — votesCastForBallot now 200000
**How found:** Code review of castVote and canFinalizeBallot: noticed voting power is read live from staking rather than snapshotted, and that ballots have no maximum end time, so the unstaking period can be outlasted and SALT re-voted from a new account.
**Exploitation:** Attacker stakes SALT, creates a malicious proposal, votes YES, unstakes, waits out the unstaking period, transfers SALT to a second account, restakes and votes YES again; repeating across accounts inflates votesCastForBallot until quorum is met and the proposal finalizes.
**Escalation:** Repeat the unstake/transfer/re-vote cycle across many accounts to reach quorum on a malicious proposal and compromise the protocol.
**Insight:** When governance reads voting power live from a staking contract instead of a snapshot, check whether the unstake/withdraw delay is shorter than the ballot's maximum lifetime — if ballots can stay open indefinitely, stake-transfer-revote lets one holder count their tokens multiple times.
