Beginner FriendlyFoundryDeFiOracle
100 EXP
View results
Submission Details
Severity: high
Valid

User cannot fully burn their assetTokens to withdraw funds

Summary

Without external donated funds, the protocol's assetToken.totalSupply * assetToken.exchangeRate will always be less than the underlying token balance in assetToken. Users will not be able to completely destroy assetToken to withdraw funds.

Vulnerability Details

In the protocol, the number of assetToken multiplied by assetToken.exchangeRate indicates the amount of funds that the user can withdraw.

In the deposit function, fees are calculated for the funds deposited by the user, which are then used to increase the exchange rate. However, deposited funds will be fully converted into assetTokens at the current exchange rate, and no additional funds will be added to the protocol to match exchange rate increases. When users subsequently attempt to redeem, the increase in the exchange rate means they are unable to completely destroy their asset tokens to withdraw their funds.

The root cause of the issue is that users depositing funds into the protocol should not affect assetToken.exchangeRate.

PoC

pragma solidity 0.8.20;
import { Test, console } from "forge-std/Test.sol";
import { BaseTest, ThunderLoan } from "./BaseTest.t.sol";
import { AssetToken } from "../../src/protocol/AssetToken.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract ThunderLoanUserLossTest is BaseTest {
uint256 constant AMOUNT = 10e18;
uint256 constant DEPOSIT_AMOUNT = AMOUNT * 1000;
address liquidityProvider = address(123);
address user = address(456);
function setUp() public override {
super.setUp();
vm.prank(user);
}
modifier setAllowedToken() {
vm.prank(thunderLoan.owner());
thunderLoan.setAllowedToken(tokenA, true);
_;
}
modifier hasDeposits() {
vm.startPrank(liquidityProvider);
tokenA.mint(liquidityProvider, DEPOSIT_AMOUNT);
tokenA.approve(address(thunderLoan), DEPOSIT_AMOUNT);
thunderLoan.deposit(tokenA, DEPOSIT_AMOUNT);
vm.stopPrank();
_;
}
function testExploitUserLoss() public setAllowedToken hasDeposits {
vm.startPrank(liquidityProvider);
AssetToken asset = thunderLoan.getAssetFromToken(tokenA);
uint256 redeemAmount = asset.balanceOf(address(liquidityProvider));
vm.expectRevert("ERC20: transfer amount exceeds balance");
thunderLoan.redeem(tokenA, redeemAmount);
vm.stopPrank();
uint256 exchangeRate = asset.getExchangeRate();
uint256 redeemRequireAmount = asset.totalSupply() * exchangeRate / 1e18;
uint256 redeemAbleAmount = tokenA.balanceOf(address(asset));
emit log_named_decimal_uint("Difference between redeemAbleAmount and redeemRequireAmount", redeemRequireAmount - redeemAbleAmount, tokenA.decimals());
}
}

Impact

Users will not be able to completely burn assetToken to withdraw funds.

Tools Used

Manual review

Recommendations

Remove the assetToken.updateExchangeRate(calculatedFee) in function deposit().

Updates

Lead Judging Commences

0xnevi Lead Judge about 2 years ago
Submission Judgement Published
Validated
Assigned finding tags:

can't redeem because of the update exchange rate

Support

FAQs

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