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

Funds can be lost forever for a particular token, if it is blocked by owner via ThunderLoan::setAllowedToken()

Summary

Owner has the privilege to block a token whenever they want, and this will lead to permanent block of funds, and liquidity providers can't redeem back their tokens.

Vulnerability Details

If the protocol owner block a token by the ThunderLoan::setAllowedToken(), then all the liquidity providers who deposited their tokens for the corresponding Asset Token, will not be able to redeem their tokens back.

Impact

Lock of funds of liquidity providers for a blocked token by the owner.

Tools Used

Manual Review

Recommendations

To have a timelock facility, when a token is blocked by the owner then a timelock will be set for x units, and after x units of time has passed, no more functions can be called for that blocked token.

diff --git a/src/protocol/ThunderLoan.sol b/src/protocol/ThunderLoan.sol
index e67674e..1085d7c 100644
--- a/src/protocol/ThunderLoan.sol
+++ b/src/protocol/ThunderLoan.sol
@@ -83,6 +83,8 @@ contract ThunderLoan is Initializable, OwnableUpgradeable, UUPSUpgradeable, Orac
error ThunderLoan__ExhangeRateCanOnlyIncrease();
error ThunderLoan__NotCurrentlyFlashLoaning();
error ThunderLoan__BadNewFee();
+ error ThunderLoan__DurationLessThanMinimum();
+ error ThunderLoan__TimelockAlreadySet();
using SafeERC20 for IERC20;
using Address for address;
@@ -92,9 +94,16 @@ contract ThunderLoan is Initializable, OwnableUpgradeable, UUPSUpgradeable, Orac
//////////////////////////////////////////////////////////////*/
mapping(IERC20 => AssetToken) public s_tokenToAssetToken;
+ /**
+ * @notice If a token is blocked then it will still remain functional for some deadline stored in this mapping
+ * @notice If the timelock is zero and there is a asset token for a token, then it is a allowed token
+ */
+ mapping(IERC20 => uint256) public s_timelock;
+
// The fee in WEI, it should have 18 decimals. Each flash loan takes a flat fee of the token price.
uint256 private s_feePrecision;
uint256 private s_flashLoanFee; // 0.3% ETH fee
+ uint256 private constant MIN_DEADLINE_DURATION = 2 days;
mapping(IERC20 token => bool currentlyFlashLoaning) private s_currentlyFlashLoaning;
@@ -177,7 +186,7 @@ contract ThunderLoan is Initializable, OwnableUpgradeable, UUPSUpgradeable, Orac
assetToken.transferUnderlyingTo(msg.sender, amountUnderlying);
}
- function flashloan(address receiverAddress, IERC20 token, uint256 amount, bytes calldata params) external {
+ function flashloan(address receiverAddress, IERC20 token, uint256 amount, bytes calldata params) external revertIfNotAllowedToken(to
ken) {
AssetToken assetToken = s_tokenToAssetToken[token];
uint256 startingBalance = IERC20(token).balanceOf(address(assetToken));
uint256 startingBalance = IERC20(token).balanceOf(address(assetToken));
@@ -224,20 +233,29 @@ contract ThunderLoan is Initializable, OwnableUpgradeable, UUPSUpgradeable, Orac
token.safeTransferFrom(msg.sender, address(assetToken), amount);
}
- function setAllowedToken(IERC20 token, bool allowed) external onlyOwner returns (AssetToken) {
+ function setAllowedToken(IERC20 token, bool allowed, uint256 deadlineDuration) external onlyOwner returns (AssetToken) {
if (allowed) {
- if (address(s_tokenToAssetToken[token]) != address(0)) {
+ if (isAllowedToken(token)) {
revert ThunderLoan__AlreadyAllowed();
}
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);
s_tokenToAssetToken[token] = assetToken;
+ s_timelock[token] = 0;
emit AllowedTokenSet(token, assetToken, allowed);
return assetToken;
} else {
+ if (s_timelock[token] != 0) {
+ revert ThunderLoan__TimelockAlreadySet();
+ }
+
+ if (deadlineDuration < MIN_DEADLINE_DURATION) {
+ revert ThunderLoan__DurationLessThanMinimum();
+ }
+
+
AssetToken assetToken = s_tokenToAssetToken[token];
- delete s_tokenToAssetToken[token];
+ s_timelock[token] = block.timestamp + deadlineDuration;
emit AllowedTokenSet(token, assetToken, allowed);
return assetToken;
}
@@ -258,7 +276,7 @@ contract ThunderLoan is Initializable, OwnableUpgradeable, UUPSUpgradeable, Orac
}
function isAllowedToken(IERC20 token) public view returns (bool) {
- return address(s_tokenToAssetToken[token]) != address(0);
+ return address(s_tokenToAssetToken[token]) != address(0) && (s_timelock[token] == 0 || s_timelock[token] >= block.timestamp);
}
function getAssetFromToken(IERC20 token) public view returns (AssetToken) {
Updates

Lead Judging Commences

0xnevi Lead Judge
over 1 year ago
0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Admin Input/call validation
0xnevi Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

centralized owners can brick redemptions by unallowing a token

Support

FAQs

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