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
Manual review.
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 () {
this.timeout(300000);
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);
await contracts.housePrices.setHousePrice(HOUSE_TOKEN_ID, HOUSE_PRICE);
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');
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));
await contracts.feeCollector.connect(owner).distributeCollectedFees();
console.log("Amount of fees in Treasury: " + await contracts.raacToken.balanceOf(contracts.treasury.target));
try {
await contracts.treasury.connect(owner).withdraw(contracts.raacToken.target, 1, user2);
} catch (error) {
expect(error.message).to.include('InsufficientBalance()');
}
});
});
});