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

Token with less than 18 decimal will cause lose of fund for LP

Summary

As this protocol allow owner of the protocol to set any token for lending and flashloaning. However not all token have 18 decimals like USDC which has only 6 decimals and WBTC which has 8 decimals.

Vulnerability Details

This protocol is minting a ERC20 token for every token deposited according to the exchangeRate of that token but the protocol is not taking account for tokens which has less than 18 decimals like WBTC & USDC.

Let's take an example:
Bob deposited 2 USDC ie 2e6
According to protocol Bob should get 2 lpUSDC ie 2e18 at an exchangeRate of 1:1 but
Bob is getting only 0.000000000002 lpUSDC ie 2e6 because protocol consider all token 18 decimals.
Similar is the case with ThunderLoan::redeem, and ThunderLoan::getCalculateFee

//here is the POC and I've created a MockERC20 USDC which has 6 decimals, below is MockERC20 USDC code and test file.
Create this MockERC20 in mock test file and import it in baseTest.t.sol file and make a USDC token with 6 decimals just like you are making tokenA.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MockERC20 is ERC20 {
uint8 _decimals;
constructor(uint8 __decimals) ERC20("ERC20Mock", "E20M") {
_decimals = __decimals;
}
function mint(address account, uint256 amount) external {
_mint(account, amount);
}
function burn(address account, uint256 amount) external {
_burn(account, amount);
}
function decimals() public view override returns (uint8) {
return _decimals;
}
}

This is the test case

// SPDX-License-Identifier: MIT
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 { ERC20Mock } from "@openzeppelin/contracts/mocks/ERC20Mock.sol";
import { MockFlashLoanReceiver } from "../mocks/MockFlashLoanReceiver.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
contract ThunderLoanTest is BaseTest {
uint256 constant AMOUNT = 10e18;
uint256 constant DEPOSIT_AMOUNT = AMOUNT * 100;
address liquidityProvider = address(123);
address user = address(456);
address user2 = address(789);
MockFlashLoanReceiver mockFlashLoanReceiver;
function setUp() public override {
super.setUp();
vm.prank(user);
mockFlashLoanReceiver = new MockFlashLoanReceiver(address(thunderLoan));
}
function test_tokenLessThan18Decimal_lose_funds() public {
// Allowing USDC to get deposite
vm.startPrank(thunderLoan.owner());
thunderLoan.setAllowedToken(USDC, true);
AssetToken assetUSDC = thunderLoan.getAssetFromToken(USDC);
uint256 amount = 2e6;
//minting 2 USDC tokens to LP
USDC.mint(user, amount);
//approving and depositing USDC
vm.startPrank(user);
USDC.approve(address(thunderLoan), amount);
thunderLoan.deposit(USDC, amount);
console.log("userLpUSDC", assetUSDC.balanceOf(user)); //2e6 only not 2e18
assert(assetUSDC.balanceOf(user) == 2e18); //should be 2 lpUSDC at 1:1 exchangeRate but it fails
}
}

To run the test

forge test --mt test_tokenLessThan18Decimal_lose_funds -vvv

Here is the result

Test result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 2.50ms
Ran 1 test suites: 0 tests passed, 1 failed, 0 skipped (1 total tests)
Failing tests:
Encountered 1 failing test in test/unit/MyTest.t.sol:ThunderLoanTest
[FAIL. Reason: Assertion violated] test_tokenLessThan18Decimal_lose_funds() (gas: 1196849)

Impact

LP will loose funds as well as interest in the form of exchangeRate

Tools Used

Manual review, Foundry

Recommendations

Add this to ThunderLoan::deposit

+ uint256 mintAmount = (
+ amount * 10 ** (18 - IERC20Metadata(address(token)).decimals()) * assetToken.EXCHANGE_RATE_PRECISION()
+ ) / exchangeRate;

Add this to ThunderLoan::redeem

+ uint256 amountUnderlying = (amountOfAssetToken * exchangeRate)
+ / (assetToken.EXCHANGE_RATE_PRECISION() * 10 ** (18 - IERC20Metadata(address(token)).decimals()));

Add this to ThunderLoan::getCalculatedFee

+ uint256 valueOfBorrowedToken = (
+ amount * 10 ** (18 - IERC20Metadata(address(token)).decimals()) * getPriceInWeth(address(token))
+ ) / s_feePrecision;
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.