Summary
The governance-dao contract calls token::Client::new(&env, &gov_token).balance(&voter) at vote time to determine how many tokens the voter can stake (and by extension their voting power). Since the governance-token has no implemented snapshot mechanism (see related VotingSnapshot issue #709), a sophisticated actor could:
- Borrow large amounts of governance tokens in the same transaction that calls
cast_vote
- Cast a high-power vote
- Return the tokens in the same transaction
While Soroban's transaction atomicity means the borrowed tokens must be returned in the same transaction, a voter could still hold them briefly mid-transaction. For tokens with liquidly-available DEX pools, this creates a flash-vote attack vector where voting power can be temporarily amplified beyond genuine holdings.
Impact
Governance decisions can be swayed by flash-loan-magnified votes at the cost of only transaction fees.
Fix
Implement and use the VotingSnapshot mechanism: take a snapshot at proposal creation, and require voters to prove their balance AT the snapshot ledger rather than the current ledger. See #709 for the snapshot implementation issue.
Summary
The
governance-daocontract callstoken::Client::new(&env, &gov_token).balance(&voter)at vote time to determine how many tokens the voter can stake (and by extension their voting power). Since the governance-token has no implemented snapshot mechanism (see relatedVotingSnapshotissue #709), a sophisticated actor could:cast_voteWhile Soroban's transaction atomicity means the borrowed tokens must be returned in the same transaction, a voter could still hold them briefly mid-transaction. For tokens with liquidly-available DEX pools, this creates a flash-vote attack vector where voting power can be temporarily amplified beyond genuine holdings.
Impact
Governance decisions can be swayed by flash-loan-magnified votes at the cost of only transaction fees.
Fix
Implement and use the
VotingSnapshotmechanism: take a snapshot at proposal creation, and require voters to prove their balance AT the snapshot ledger rather than the current ledger. See #709 for the snapshot implementation issue.