Voting power is based on current balance, not a snapshot at proposal creation\
The castVote()
function in Governance.sol has a fundamental trust assumption, it relies on current voting power rather than historical snapshots. The key line:
Initially, let's go through this State
Alice has 100,000 veRAACTokens
locked
Bob creates a governance proposal to adjust protocol parameters
The proposal enters voting period after votingDelay
The Issue Unfolds
Alice sees the proposal and plans to vote, but notices she can manipulate her voting power: Governance.sol#L196
This creates a temporal disconnect between proposal creation and vote casting. The code assumes voting power at voting time represents legitimate governance weight, which breaks the core principle of time-weighted governance.
Alice executes her attack:
She increases her lock amount to 500,000 veRAACTokens
Calls castVote(proposalId, true)
with her inflated voting power
Immediately after voting, she reduces her lock back to 100,000
The manipulation succeeds because:
No snapshot of voting power is taken at proposal creation
proposalVote.forVotes
gets inflated by temporary voting power
The contract only checks hasVoted[msg.sender]
to prevent multiple votes
Resulting Impact
Proposal voting becomes manipulated
A single user can temporarily boost their voting power
The quorum()
calculation becomes unreliable
Governance decisions no longer reflect true long-term token holder intent
If the proposal was to adjust MAX_WEEKLY_EMISSION
in RAACGauge from 500,000 to 1,000,000, Alice's manipulated vote could swing the outcome despite only having legitimate voting power of 100,000 tokens.
Given the protocol's governance controls critical parameters like:
MAX_WEEKLY_EMISSION
(500,000 RAAC) in RAACGauge
Fee distributions in FeeCollector
Treasury management
A malicious actor could:
Flash loan veRAACTokens
Cast votes with inflated power
Return borrowed tokens
Impact: Up to 100% of protocol parameters could be maliciously modified
This mirrors the Beanstalk governance exploit (April 2022) where an attacker flash loaned $1B in assets to gain emergency voting power. The attacker proposed and executed a malicious governance proposal within a single transaction by exploiting the lack of voting power snapshots.
vs
Store voting power at proposal creation. n the current Governance.sol
, we could enhance the propose()
function to capture voting power snapshots when proposals are created.
Then modify castVote()
to use this snapshot
This aligns with how the protocol's other time-sensitive components work.
TimeWeightedAverage.sol
for tracking historical values
veRAACToken.sol
checkpoint system
GaugeController.sol
period-based weight tracking
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.