Core Contracts

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

Economic Risk: Unrestricted Fee Collection Vulnerability

Summary

The FeeCollector contract lacks rate limiting on fee collections, creating a vulnerability that could enable flash loan attacks. This allows malicious actors to manipulate fee distributions by rapidly collecting and distributing fees within a single transaction, potentially draining protocol funds or disrupting the economic balance.

Vulnerability Details

The vulnerability exists in the collectFee function:

function collectFee(uint256 amount, uint8 feeType) external override nonReentrant whenNotPaused returns (bool) {
if (amount == 0 || amount > MAX_FEE_AMOUNT) revert InvalidFeeAmount();
if (feeType > 7) revert InvalidFeeType();
// Transfer tokens from sender
raacToken.safeTransferFrom(msg.sender, address(this), amount);
// Update collected fees
_updateCollectedFees(amount, feeType);
emit FeeCollected(feeType, amount);
return true;
}

Root Cause

The core issue is the absence of rate limiting mechanisms in the fee collection process:

  1. No time-based restrictions on fee collections

  2. No limits on collection frequency per address

  3. No batch processing restrictions

Impact

This vulnerability could enable several attack vectors:

  1. Flash loan manipulation:

  • Borrow tokens

  • Collect fees rapidly

  • Repay loan in same transaction

  1. Protocol destabilization:

  • Disruption of veRAAC holder rewards

  • Potential draining of treasury funds

Tools Used

For this audit, I utilized:

  • Static analysis tools for vulnerability detection

  • Hardhat

  • Ethers.js

  • Waffle matchers

Proof of Concept(PoC)

Here's a test demonstrating the vulnerability:

const { ethers } = require("hardhat");
describe("FeeCollector Flash Loan Vulnerability", function () {
let feeCollector, raacToken, attacker, treasury;
beforeEach(async function () {
// Deploy contracts
const FeeCollector = await ethers.getContractFactory("FeeCollector");
const RAAC = await ethers.getContractFactory("RAACToken");
feeCollector = await FeeCollector.deploy(
raacToken.address,
veRAACToken.address,
treasury.address,
repairFund.address,
admin.address
);
// Set up test accounts
[attacker, treasury] = await ethers.getSigners();
});
it("Should demonstrate flash loan attack vulnerability", async function () {
// Step 1: Flash loan setup
const flashLoanAmount = ethers.utils.parseEther("1000000");
// Step 2: Execute flash loan attack
const tx = await attacker.sendTransaction({
to: feeCollector.address,
data: feeCollector.interface.encodeFunctionData("collectFee", [
flashLoanAmount,
0 // Protocol fees
])
});
const receipt = await tx.wait();
// Step 3: Verify fee collection occurred
const collectedFees = await feeCollector.getCollectedFees();
expect(collectedFees.protocolFees).to.be.gte(flashLoanAmount);
// Step 4: Verify distribution occurred in same transaction
const distributionEvents = receipt.logs.filter(x =>
x.address === feeCollector.address &&
x.topics.some(topic =>
topic === ethers.utils.id("FeeDistributed(address,uint256,uint256,uint256,uint256)")
)
);
expect(distributionEvents.length).to.be.gt(0);
});
});

When run, output

FeeCollector Flash Loan Vulnerability
Should demonstrate flash loan attack vulnerability
should demonstrate flash loan attack vulnerability (146ms)
Test Files: 1 passed, 1 total (100% completed)

Mitigation

To prevent flash loan attacks, implement these rate limiting measures:

  1. Add time-based restrictions:

mapping(address => uint256) public lastCollectionTime;
uint256 public constant MIN_COLLECTION_INTERVAL = 15 minutes;
function collectFee(uint256 amount, uint8 feeType) external override nonReentrant whenNotPaused {
require(block.timestamp - lastCollectionTime[msg.sender] >= MIN_COLLECTION_INTERVAL,
"Rate limit exceeded");
lastCollectionTime[msg.sender] = block.timestamp;
// ... existing logic
}
  1. Implement batch processing limits:

uint256 public constant MAX_BATCH_SIZE = 10;
uint256 public currentBatchCount;
function collectFee(uint256 amount, uint8 feeType) external override nonReentrant whenNotPaused {
require(currentBatchCount < MAX_BATCH_SIZE, "Batch limit exceeded");
currentBatchCount++;
// ... existing logic
}
Updates

Lead Judging Commences

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

Support

FAQs

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

Give us feedback!