https://github.com/la-bomba-studio/hesty-contract/tree/29596447b9a06d4ad53360d4a15f349ebf9fa0d8
Description:
The recoverFundsInvested function in TokenFactory contract do not refund the extra fees that users pay when purchasing tokens:
@> uint256 amount = userInvested[user][id];
userInvested[user][id] = 0;
rightForTokens[user][id] = 0;
@> SafeERC20.safeTransfer(p.paymentToken, user, amount);
Here is the amount investors pay in total to buy tokens via buyTokens function:
// Calculate the investment fee and then get the total investment cost
uint256 fee = boughtTokensPrice * platformFeeBasisPoints / BASIS_POINTS;
@> uint256 total = boughtTokensPrice + fee;
// Charge investment cost from user
@> SafeERC20.safeTransferFrom(p.paymentToken,msg.sender, address(this), total);
As a result, when a user requests a refund or when a property is canceled, only the principal investment is returned, while the extra fees remain stuck in the contract. There is no function to retrieve these fees, leading to an irreversible loss of funds for users. Since fees can be as high as 30% of the investment amount, investors stand to lose a significant portion of their funds due to this vulnerability.
Impact:
Mitigation:
Modify the refund logic in the recoverFundsInvested function to ensure that users receive their total refunded investment.
Exploit Scenario:
recoverFundsInvested or the property gets canceled via cancelProperty.Add this code in TokenFactory.test.js file and run the test:
describe("Fees not refunded", function () {
beforeEach(async function () {
//Not yet initialized so therefore address(0)
expect(await tokenFactory.referralSystemCtr()).to.equal("0x0000000000000000000000000000000000000000");
await tokenFactory.initialize(referral.address, issuance.address)
await hestyAccessControlCtr.connect(addr2).approveUserKYC(propertyManager.address);
await tokenFactory.addWhitelistedToken(token.address);
await tokenFactory.connect(propertyManager).createProperty(1000000, 1000, 1000, 100000, token.address, token.address, "token", "TKN", hestyAccessControlCtr.address)
expect(await tokenFactory.propertyCounter()).to.equal(1);
await tokenFactory.approveProperty(0, 2937487238472834);
// Approve owner kyc to allow him to buy property token
await hestyAccessControlCtr.connect(addr2).approveUserKYC(owner.address);
await token.approve(tokenFactory.address, 2000);
await token.mint(owner.address, 1050);
// User buy 1 Property Tokens with boughtTokensPrice = 1000
// Fees = 30; Total Price = 1030
await tokenFactory.buyTokens(owner.address, 0, 1, addr3.address);
})
it("FeesNotRefunded", async function () {
await ethers.provider.send("evm_mine", [2937487238472844]);
await tokenFactory.recoverFundsInvested(owner.address, 0);
// User only get 1000 back as compared to 1030
expect(await token.balanceof(owner.address)).to.equal(1020);
});
})