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

ThunderLoan::setAllowedToken() lags check whether the token has a pool or not.

Summary

ThunderLoan::setAllowedToken() lags check whether the token has a pool or not. Fees is calculated when the user takes flash loan on the basis of price of token in weth, which is brought from oracle pool but if the token don't have a pool then user can't take flash loan as it will always revert on fee calculation because the pool will be equal to address(0) and it will get reverted as no function calls can be made on address(0). Thus, no user can take flash loan and it is necessary that there is a check in setAllowedToken to revert if there is no pool corresponding to token.

Vulnerability Details

If the token being allowed in the protocol don't have a pool, then fees can never be calculated as it uses the weth price corresponding to that token which is fetched from pool. But if there is no pool for a token then fees can never be calculated and all functions which involves fee calculation will always revert.
NOTE: This vulnerability is present in both ThunderLoan and ThunderLoanUpgraded contract.

PoC

  • Add the new tokenB in test/unit/BaseTest.t.sol

contract BaseTest is Test {
ThunderLoan thunderLoanImplementation;
MockPoolFactory mockPoolFactory;
ERC1967Proxy proxy;
ThunderLoan thunderLoan;
ERC20Mock weth;
ERC20Mock tokenA;
+ ERC20Mock tokenB;
function setUp() public virtual {
thunderLoan = new ThunderLoan();
mockPoolFactory = new MockPoolFactory();
weth = new ERC20Mock();
tokenA = new ERC20Mock();
+ tokenB = new ERC20Mock();
mockPoolFactory.createPool(address(tokenA));
proxy = new ERC1967Proxy(address(thunderLoan), "");
thunderLoan = ThunderLoan(address(proxy));
thunderLoan.initialize(address(mockPoolFactory));
}
}
  • Add the test in the file: test/unit/ThunderLoanTest.t.sol

  • Run the test: forge test --mt test_deposit_RevertsIf_TokenAllowlistedHasNoPool

modifier setAllowedTokenB_AndMintToken() {
thunderLoan.setAllowedToken(tokenB, true);
tokenB.mint(liquidityProvider, DEPOSIT_AMOUNT);
_;
}
function test_deposit_RevertsIf_TokenAllowlistedHasNoPool() public setAllowedTokenB_AndMintToken {
vm.startPrank(liquidityProvider);
tokenB.approve(address(thunderLoan), DEPOSIT_AMOUNT);
// Reverts because there is no pool corresponding to the token
// and getPriceOfOnePoolTokenInWeth() is called on pool (= address(0)) thus it is expected to be reverted
vm.expectRevert();
thunderLoan.deposit(tokenB, DEPOSIT_AMOUNT);
vm.stopPrank();
}

Impact

User can't call the function which uses the fees calculation as pool is used to calculate weth amount corresponding to that token and if there is no pool, then function call will always revert.

Tools Used

Manual Review, Foundry Tests

Recommendations

To have a check in setAllowedToken() function, that if there is no pool corresponding to the token then it should revert.

diff --git a/src/protocol/OracleUpgradeable.sol b/src/protocol/OracleUpgradeable.sol
index e38a6cc..0fc2bef 100644
--- a/src/protocol/OracleUpgradeable.sol
+++ b/src/protocol/OracleUpgradeable.sol
@@ -25,7 +25,7 @@ contract OracleUpgradeable is Initializable {
return getPriceInWeth(token);
}
- function getPoolFactoryAddress() external view returns (address) {
+ function getPoolFactoryAddress() public view returns (address) {
return s_poolFactory;
}
}
diff --git a/src/protocol/ThunderLoan.sol b/src/protocol/ThunderLoan.sol
index e67674e..81e6693 100644
--- a/src/protocol/ThunderLoan.sol
+++ b/src/protocol/ThunderLoan.sol
@@ -72,6 +72,7 @@ import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/I
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import { OracleUpgradeable } from "./OracleUpgradeable.sol";
import { Address } from "@openzeppelin/contracts/utils/Address.sol";
+import { IPoolFactory } from "../interfaces/IPoolFactory.sol";
contract ThunderLoan is Initializable, OwnableUpgradeable, UUPSUpgradeable, OracleUpgradeable {
error ThunderLoan__NotAllowedToken(IERC20 token);
@@ -83,6 +84,7 @@ contract ThunderLoan is Initializable, OwnableUpgradeable, UUPSUpgradeable, Orac
error ThunderLoan__ExhangeRateCanOnlyIncrease();
error ThunderLoan__NotCurrentlyFlashLoaning();
error ThunderLoan__BadNewFee();
+ error ThunderLoan__TokenHasNoPool();
using SafeERC20 for IERC20;
using Address for address;
@@ -229,6 +231,9 @@ contract ThunderLoan is Initializable, OwnableUpgradeable, UUPSUpgradeable, Orac
if (address(s_tokenToAssetToken[token]) != address(0)) {
revert ThunderLoan__AlreadyAllowed();
}
+ if (IPoolFactory(getPoolFactoryAddress()).getPool(address(token)) == address(0)) {
+ revert ThunderLoan__TokenHasNoPool();
+ }
string memory name = string.concat("ThunderLoan ", IERC20Metadata(address(token)).name());
string memory symbol = string.concat("tl", IERC20Metadata(address(token)).symbol());
AssetToken assetToken = new AssetToken(address(this), token, name, symbol);
Updates

Lead Judging Commences

0xnevi Lead Judge
over 1 year ago
0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Other
shikhar229169 Submitter
over 1 year ago
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.