Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: high
Valid

Vote Power Snapshot Issue (Time-of-Check to Time-of-Use) in Governance.sol function castVote()

Summary

The function castVote suffers from a Time-of-Check to Time-of-Use (TOCTOU) issue related to the retrieval of voting power. The function calls _veToken.getVotingPower(msg.sender) at the time of voting, but this value may change before the vote is finalized, leading to inconsistent or unfair voting results.

Vulnerability Details

function castVote(uint256 proposalId, bool support) external override returns (uint256) {
ProposalCore storage proposal = _proposals[proposalId];
if (proposal.startTime == 0) revert ProposalDoesNotExist(proposalId);
if (block.timestamp < proposal.startTime) {
revert VotingNotStarted(proposalId, proposal.startTime, block.timestamp);
}
if (block.timestamp > proposal.endTime) {
revert VotingEnded(proposalId, proposal.endTime, block.timestamp);
}
ProposalVote storage proposalVote = _proposalVotes[proposalId];
if (proposalVote.hasVoted[msg.sender]) {
revert AlreadyVoted(proposalId, msg.sender, block.timestamp);
}
>> uint256 weight = _veToken.getVotingPower(msg.sender);
if (weight == 0) {
revert NoVotingPower(msg.sender, block.number);
}
proposalVote.hasVoted[msg.sender] = true;
if (support) {
proposalVote.forVotes += weight;
} else {
proposalVote.againstVotes += weight;
}
emit VoteCast(msg.sender, proposalId, support, weight, "");
return weight;
}

The contract fetches the voter's voting power (weight = _veToken.getVotingPower(msg.sender)) at the time of vote submission.

  • However, _veToken.getVotingPower(msg.sender) is a dynamic value that may fluctuate due to token transfers, staking, delegation, or any governance mechanism affecting voting power.

  • This introduces a Time-of-Check to Time-of-Use (TOCTOU) problem where:

    • A voter may temporarily inflate their voting power, cast a vote, and then transfer or unstake tokens.

    • A malicious entity may borrow tokens (flash loans), cast a vote with high power, and return the borrowed tokens before the proposal ends.

    • Voting power may be reduced unexpectedly due to another protocol mechanism, affecting the vote's accuracy.

Impact

Double Voting: This allows double voting, where one entity can vote multiple times using different accounts.

Vote Manipulation: A user can transfer tokens to another address and vote again, effectively amplifying their voting power.

Unfair Governance Decisions: The final vote may not reflect the true state of governance token holders at the time of the vote.

Tools Used

Manual Review

Recommendations

Use Voting Power Snapshots:

  • Instead of checking _veToken.getVotingPower(msg.sender) at the time of voting, take a snapshot of voting power at the start of the proposal or beginning of the voting period.

// Old: Fetching current balance dynamically (vulnerable to transfers)
-- uint256 weight = _veToken.getVotingPower(msg.sender);
// ✅ New: Fetching voting power at the proposal start time (snapshot mechanism)
++ uint256 weight = _veToken.getVotingPowerAt(msg.sender, proposal.startTime);

Add code givenb below to veRAACToken.sol :

struct Checkpoint {
uint256 fromTimestamp;
uint256 votingPower;
}
mapping(address => Checkpoint[]) public checkpoints;
event VotingPowerUpdated(address indexed user, uint256 newPower);
function _updateVotingPower(address user, uint256 newPower) internal {
checkpoints[user].push(Checkpoint(block.timestamp, newPower));
emit VotingPowerUpdated(user, newPower);
}
function getVotingPowerAt(address user, uint256 timestamp) external view returns (uint256) {
Checkpoint[] storage userCheckpoints = checkpoints[user];
uint256 length = userCheckpoints.length;
if (length == 0 || timestamp < userCheckpoints[0].fromTimestamp) {
return 0;
}
for (uint256 i = length; i > 0; i--) {
if (userCheckpoints[i - 1].fromTimestamp <= timestamp) {
return userCheckpoints[i - 1].votingPower;
}
}
return 0;
}

This ensures that a voter's power is fixed when the proposal starts, preventing manipulation.

Updates

Lead Judging Commences

inallhonesty Lead Judge 3 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Governance.castVote uses current voting power instead of proposal creation snapshot, enabling vote manipulation through token transfers and potential double-voting

inallhonesty Lead Judge 3 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Governance.castVote uses current voting power instead of proposal creation snapshot, enabling vote manipulation through token transfers and potential double-voting

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.