Core Contracts

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

[H-01] FeeCollector's fees sent to Treasury cannot be recovered

Summary

FeeCollector sends the fee shares directly to the treasury using transfer() instead of the intended deposit() function implemented by Treasury.

Vulnerability Details

This way of transferring shares makes the amount unaccounted for in the internal state of the Treasury contract.
This results in having the shares stuck in the treasury since calling the withdraw() function will result in a revert due to underflow of the funds

Instance 1

Instance 2

Instance 3

Impact

The fee shares will become stuck in Treasury without a way to get them out since there is also no "emergency withdraw"-type of mechanism.

Tools Used

Manual review.

Recommendations

Instead of using transfer() to move funds to the Treasury, use deposit() as intended.

Proof of Code

This snippet show the issue at hand and how it's not possible to retrieve the funds afterwards.

import { expect } from "chai";
import hre from "hardhat";
const { ethers } = hre;
import { time } from "@nomicfoundation/hardhat-network-helpers";
import { deployContracts } from './utils/deployContracts.js';
describe('Exploit Tests', function () {
// Set higher timeout for deployments
this.timeout(300000); // 5 minutes
let contracts;
let owner, user1, user2, user3, treasury, repairFund;
const INITIAL_MINT_AMOUNT = ethers.parseEther('1000');
const HOUSE_TOKEN_ID = '1021000';
const HOUSE_PRICE = ethers.parseEther('100');
const ONE_YEAR = 365 * 24 * 3600;
const FOUR_YEARS = 4 * ONE_YEAR;
const BASIS_POINTS = 10000;
before(async function () {
[owner, user1, user2, user3, treasury, repairFund] = await ethers.getSigners();
contracts = await deployContracts(owner, user1, user2, user3);
const displayContracts = Object.fromEntries(Object.entries(contracts).map(([key, value]) => [key, value.target]));
console.log(displayContracts);
// Set house price for testing
await contracts.housePrices.setHousePrice(HOUSE_TOKEN_ID, HOUSE_PRICE);
// Mint initial tokens to users
for (const user of [user1, user2, user3]) {
await contracts.crvUSD.mint(user.address, INITIAL_MINT_AMOUNT);
}
});
describe.only('Bugs:', function () {
it('[H-01] FeeCollector\'s fees sent to Treasury cannot be recovered', async function () {
const TRANSFER_AMOUNT = ethers.parseEther('1');
// Collect fee
await contracts.raacToken.connect(user2).approve(contracts.feeCollector.target, TRANSFER_AMOUNT);
await contracts.feeCollector.connect(user2).collectFee(TRANSFER_AMOUNT, 0);
console.log("Amount of fees in feeCollector: " + await contracts.raacToken.balanceOf(contracts.feeCollector.target)); // 1000000000000000000
// Distribute fees, including treasury
await contracts.feeCollector.connect(owner).distributeCollectedFees();
console.log("Amount of fees in Treasury: " + await contracts.raacToken.balanceOf(contracts.treasury.target)); // 1000000000000000000
// Try and fail withdrawing due to lack of funds
try {
await contracts.treasury.connect(owner).withdraw(contracts.raacToken.target, 1, user2);
} catch (error) {
expect(error.message).to.include('InsufficientBalance()');
}
});
});
});
Updates

Lead Judging Commences

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

FeeCollector::_processDistributions and emergencyWithdraw directly transfer funds to Treasury where they get permanently stuck

Support

FAQs

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

Give us feedback!