Core Contracts

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

Fee Distribution DOS In FeeCollector Due to Incorrect Fee Accounting with RAAC Token

Summary

The FeeCollector contract's collectFee() function uses the input amount parameter for internal accounting rather than the actual amount of tokens received after transfer fees. This causes the tracked fees to be higher than the actual token balance, making fee distribution impossible due to insufficient balance checks. This issue is also found in `veRAACToken::lock` function.

Vulnerability Details

In FeeCollector.sol:

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;
}

RAACToken implements a fee-on-transfer mechanism but when collecting fees, FeeCollector updates its accounting with the pre-fee amount. That makes the actual balance received less than the tracked amount, this causes `FeeCollector::distributeCollectedFees` to revert with insufficient balance error.

POC

To use foundry in the codebase, follow the hardhat guide here: Foundry-Hardhat hybrid integration by Nomic foundation

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {FeeCollector} from "../../../../contracts/core/collectors/FeeCollector.sol";
import {RAACToken} from "../../../../contracts/core/tokens/RAACToken.sol";
import {veRAACToken} from "../../../../contracts/core/tokens/veRAACToken.sol";
import {Test, console} from "forge-std/Test.sol";
contract TestSuite is Test {
FeeCollector feeCollector;
RAACToken raacToken;
veRAACToken veRAACTok;
address treasury;
address repairFund;
address admin;
uint256 initialSwapTaxRate = 100; //1%
uint256 initialBurnTaxRate = 50; //0.5%
function setUp() public {
treasury = makeAddr("treasury");
repairFund = makeAddr("repairFund");
admin = makeAddr("admin");
raacToken = new RAACToken(admin, initialSwapTaxRate, initialBurnTaxRate);
veRAACTok = new veRAACToken(address(raacToken));
feeCollector = new FeeCollector(address(raacToken), address(veRAACTok), treasury, repairFund, admin);
vm.startPrank(admin);
raacToken.setFeeCollector(address(feeCollector));
raacToken.setMinter(admin);
vm.stopPrank();
}
function testRaacTokenFeeOnTransferDOSFeeDistribution() public {
uint256 amount = feeCollector.MAX_FEE_AMOUNT();
vm.startPrank(admin);
raacToken.mint(admin, amount);
raacToken.approve(address(feeCollector), amount);
//admin calls collect fee
feeCollector.collectFee(amount, 0);
//admin calls distribute but it always reverts with InsufficientBalance error
vm.expectRevert();
feeCollector.distributeCollectedFees();
vm.stopPrank();
uint256 expectedBalance = feeCollector._calculateTotalFees(); //function made public for ease of access
uint256 actualBalance = raacToken.balanceOf(address(feeCollector));
console.log("Expected balance of feeCollector: ", expectedBalance);
console.log("Actual balance of feeCollector: ", actualBalance);
}
}

Impact

Fee distribution is completely broken as it becomes impossible once any fees are collected. No fees can be distributed to veRAACToken holders, treasury, or repair fund.

Tools Used

Manual review, foundry test suite

Recommendations

Track actual received amounts instead of input amounts

Updates

Lead Judging Commences

inallhonesty Lead Judge 3 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.