The GaugeController
contract’s vote
function allows any user with sufficient voting power (derived from their veRAAC token balance) to cast votes on gauges. Its purpose is to update the gauge’s weight based on the user's chosen vote weight, which in turn affects reward distribution and governance outcomes. The function performs several checks: it verifies that the gauge exists, that the vote weight does not exceed WEIGHT_PRECISION
, and that the caller has nonzero voting power. It then updates the gauge’s weight via an internal function and emits a WeightUpdated
event.
However, critical constraints are missing. Although the contract defines constants such as:
VOTE_DELAY
(required delay between votes),
MIN_VOTE_DELAY
, MAX_VOTE_DELAY
(allowed bounds for vote delay),
MIN_VOTE_WEIGHT
(minimum allowed vote weight),
these are not enforced in the vote
function. Consequently, there is no mechanism to prevent a user from voting repeatedly in a short period or casting votes with weights below the minimum threshold. This oversight may allow malicious actors to manipulate gauge weights arbitrarily, thereby skewing reward distributions and governance outcomes.
The following excerpt from GaugeController::vote
illustrates the issue:
Missing Checks:
Vote Delay: No verification that the caller waited at least
VOTE_DELAY
seconds since their last vote.Minimum Vote Weight: No enforcement of a minimum vote weight (
MIN_VOTE_WEIGHT
).
Function Flow:
The vote
function first checks that the provided gauge is registered via isGauge(gauge)
.
It verifies that the vote weight does not exceed WEIGHT_PRECISION
.
The function then retrieves the caller’s voting power via veRAACToken.balanceOf(msg.sender)
and ensures it is nonzero.
It records the vote weight and calls _updateGaugeWeight
to adjust the gauge’s weight using the formula:
newGaugeWeight =
oldGaugeWeight - (oldWeight _ votingPower / WEIGHT_PRECISION) + (newWeight _ votingPower / WEIGHT_PRECISION);
Finally, it emits a WeightUpdated
event.
Missing Constraints:
Voting Delay: The contract defines VOTE_DELAY
(e.g., 10 days) and maintains a lastVoteTime
mapping for each user, but these are not checked before a vote is accepted. Without a delay, users can vote repeatedly, possibly altering gauge weights in rapid succession.
Minimum Vote Weight: Although MIN_VOTE_WEIGHT
is declared (e.g., 100, representing a 1% minimum vote), the vote
function does not enforce that the submitted weight is at least this minimum. Votes with extremely low weight might be accepted, which could enable spamming or dilution of meaningful votes.
Protocol Implications:
Reward Distribution: Gauge weights determine the distribution of rewards. If voters can manipulate their votes arbitrarily, the reward allocation can be skewed, potentially resulting in an unfair distribution.
Governance and System Stability: Frequent and arbitrary vote changes may undermine the intended stability of the gauge system, possibly affecting long-term governance decisions and protocol integrity.
Setup:
A gauge is added using GaugeController::addGauge
by a gauge admin.
The gauge is now active, and any user with sufficient veRAAC voting power can cast a vote.
Voting Without Constraints:
Initial Vote:
User ALICE has locked RAAC tokens to obtain veRAAC tokens. ALICE calls vote(address(rwaGauge), 100)
and successfully casts a vote.
Rapid Re-Voting:
Immediately after, ALICE calls vote(address(rwaGauge), 99)
to change her vote. Since there is no check on voting frequency, the contract processes this vote without any delay requirement.
Minimum Weight Violation:
A user could even vote with a weight lower than MIN_VOTE_WEIGHT
(if not explicitly reverted), potentially polluting the gauge weight calculations.
Foundry Test Suite:
The following Foundry test demonstrates the issue:
Step 1: Create a new Foundry project (if not already done):
Step 2: Remove any unnecessary files from the project.
Step 3: Convert your Hardhat project (if applicable) to a Foundry project by placing your contracts in the src
directory.
Step 4: Create a test
directory adjacent to your src
folder and add all necessary contract files and mocks.
Step 5: Paste the above test suite into a new test file (e.g., GaugeTest.t.sol
) in the test
directory.
Step 6: Run the test with:
Expected Log Output:
Reward Distribution Distortion:
Unrestricted voting may allow malicious users to repeatedly cast votes, thereby artificially inflating or deflating gauge weights. This can lead to skewed reward distributions, where some gauges receive disproportionate allocations.
Governance Manipulation:
Frequent vote changes could influence governance outcomes by modifying the relative weight of gauges. This undermines the intended fairness and stability of the governance mechanism.
System Abuse and Spam:
Without a voting delay, attackers may spam the vote function with minimal cost, burdening the system and potentially increasing gas usage unnecessarily.
Long-Term Trust Erosion:
Persistent manipulation of gauge weights and rewards can lead to a loss of trust among protocol participants and undermine confidence in the system’s fairness and security.
Manual Review
Foundry
The following diffs provide recommendations to incorporate the missing constraints (voting delay and minimum vote weight) in the vote
function of the GaugeController
contract.
GaugeController::vote
Implementing these changes ensures that voters cannot manipulate gauge weights by frequently re-voting or by submitting votes with negligible weight. This strengthens the integrity of reward distribution, stabilizes governance processes, and upholds the trust in the protocol's gauge system.
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.