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

[H-01] Inaccurate Fee Calculation for Flash Loans with Non-Standard Decimals in ThunderLoan Protocol

Summary

The ThunderLoan Protocol's flashloan function miscalculates fees for tokens with non-standard decimal units, such as USDC and USDT, which have 6 decimals. This issue arises from the getCalculatedFee function, which incorrectly applies a precision factor intended for tokens with 18 decimals, resulting in an inflated fee by a factor of 1e12 for tokens with fewer decimals.

Vulnerability Details

In the getCalculatedFee function, the calculation (amount * getPriceInWeth(address(token))) / s_feePrecision assumes amount is in wei (1e18 decimals). However, for tokens like USDC and USDT with only 6 decimals, this results in a fee calculation that is vastly inflated. The second line, (valueOfBorrowedToken * s_flashLoanFee) / s_feePrecision, compounds this error by applying the precision factor again.

Impact

The inflated fees due to incorrect decimal handling can result in users being significantly overcharged for flash loans, reducing the competitiveness and reliability of the ThunderLoan Protocol and potentially causing financial loss and damage to user trust.

Proof of Concept (POC)

Add thetest below to the ThunderLoanTest.t.sol:

uint256 public constant USDCAmount = 1e6; // Set AMOUNT to represent 6 decimals
function test_USDC_USDT_Amount() public setAllowedToken hasDeposits {
uint256 amountToBorrow = USDCAmount * 10;
uint256 calculatedFee = thunderLoan.getCalculatedFee(tokenA, amountToBorrow);
// Store the balance before the flash loan to check against after
uint256 initialBalance = tokenA.balanceOf(address(mockFlashLoanReceiver));
vm.startPrank(user);
tokenA.mint(address(mockFlashLoanReceiver), USDCAmount);
thunderLoan.flashloan(address(mockFlashLoanReceiver), tokenA, amountToBorrow, "");
vm.stopPrank();
// Calculate the expected final balance based on the initial balance, amount borrowed, and the fee
uint256 expectedFinalBalance = initialBalance + amountToBorrow - calculatedFee;
assertEq(mockFlashLoanReceiver.getbalanceDuring(), amountToBorrow + USDCAmount);
assertEq(tokenA.balanceOf(address(mockFlashLoanReceiver)), expectedFinalBalance);
// Additional check: Ensure the fee isn't greater than expected due to precision loss
uint256 maxExpectedFee = (amountToBorrow * 3) / 1000; // Assuming a 0.3% fee for simplicity
assertEq(calculatedFee, maxExpectedFee, "Fee is greater than expected");
}

output:

├─ [0] VM::stopPrank()
│ └─ ← ()
├─ [281] MockFlashLoanReceiver::getbalanceDuring() [staticcall]
│ └─ ← 11000000
├─ [585] ERC20Mock::balanceOf(MockFlashLoanReceiver: [0x1cF5c39A1828B446da31fADb01059F2A6B9B327b]) [staticcall]
│ └─ ← 970000
├─ emit log(: Error: a == b not satisfied [uint])
├─ emit log_named_uint(key: Left, val: 970000)
├─ emit log_named_uint(key: Right, val: 9970000)
├─ [0] VM::store(VM: [0x7109709ECfa91a80626fF3989D68f67F5b1DD12D], 0x6661696c65640000000000000000000000000000000000000000000000000000, 0x0000000000000000000000000000000000000000000000000000000000000001)
│ └─ ← ()
└─ ← ()
Test result: FAILED. 0 passed; 1 failed; finished in 2.69ms
Failing tests:
Encountered 1 failing test in test/unit/ThunderLoanTest.t.sol:ThunderLoanTest
[FAIL. Reason: Assertion failed.] test_USDC_USDT_Amount() (gas: 1290796)

We can see that the test failed because of the wrongly calculated fee.

Tools Used

  • Manual Review

  • Foundry

Recommendations

To fix this vulnerability:

  • Modify the getCalculatedFee function to account for the token's actual decimal places by incorporating the decimals() function of the ERC20 token interface.

  • Create a utility function or modifier to handle the conversion of decimal places accurately within the smart contract.

  • Establish comprehensive testing for various tokens with different decimal places to ensure fee calculations are accurate across all scenarios.

Updates

Lead Judging Commences

0xnevi Lead Judge
over 1 year ago
0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Other

Support

FAQs

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