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

potential reentrancy in flashloan, can drain tokens from the contract

Summary

The flashloan function in the ThunderLoan contract has a potential reentrancy vulnerability that can be exploited to drain tokens from the contract. This vulnerability occurs because the function does not enforce proper order of operations when handling external calls, allowing an attacker to create a reentrancy attack.

Vulnerability Details

  1. Lack of Correct Sequence: The function's improper order of operations, calculating a fee and updating exchange rates before verifying the user's contract (external), poses a reentrancy risk.

  2. Reentrancy Attack Vector: The vulnerability arises from executeOperation on receiverAddress, allowing malicious or unexpected behavior, potentially disrupting the flash loan process.

Impact

This vulnerability can lead to unauthorized reentrancy attacks, enabling malicious external contracts to disrupt the flash loan process and potentially drain assets from the ThunderLoan contract.

POC

function testReentrancyAttack() public setAllowedToken {
tokenA.mint(liquidityProvider, AMOUNT);
vm.startPrank(liquidityProvider);
tokenA.approve(address(thunderLoan), AMOUNT);
thunderLoan.deposit(tokenA, AMOUNT);
vm.stopPrank();
AssetToken asset = thunderLoan.getAssetFromToken(tokenA);
vm.startPrank(user);
tokenA.mint(address(mockFlashLoanReceiver), AMOUNT);
// Simulate a reentrancy attack by repeatedly calling the flashloan function
while (asset.balanceOf(address(thunderLoan)) != 0) {
thunderLoan.flashloan(address(mockFlashLoanReceiver), tokenA, AMOUNT, "");
}
vm.stopPrank();
// Ensure the attack drained the tokens from ThunderLoan
assertEq(asset.balanceOf(address(thunderLoan)), 0, "Tokens not drained");
}

Tools Used

  • Manual review

Recommendations

For removing the reentrancy risk, you should follow these steps:

1 - Calculate the fee and update the exchange rate before interacting with external contracts. This is critical to ensure the correct sequence of operations.

2 - Implement guard conditions to prevent reentrancy attacks by checking whether the function has already been called during the execution of a flash loan.

New code will look like this for preventing reentrancy risks:

function flashloan(address receiverAddress, IERC20 token, uint256 amount, bytes calldata params) external {
AssetToken assetToken = s_tokenToAssetToken[token];
uint256 startingBalance = IERC20(token).balanceOf(address(assetToken));
if (amount > startingBalance) {
revert ThunderLoan__NotEnoughTokenBalance(startingBalance, amount);
}
if (!receiverAddress.isContract()) {
revert ThunderLoan__CallerIsNotContract();
}
uint256 fee = getCalculatedFee(token, amount);
uint256 endingBalance;
// Update the exchange rate before any external interaction to prevent reentrancy attacks.
assetToken.updateExchangeRate(fee);
emit FlashLoan(receiverAddress, token, amount, fee, params);
s_currentlyFlashLoaning[token] = true;
// Transfer tokens to the receiver.
assetToken.transferUnderlyingTo(receiverAddress, amount);
// Set a flag to prevent reentrant calls
bool alreadyCalled = false;
// Execute the receiver's contract function.
(bool success, bytes memory result) = receiverAddress.call(
abi.encodeWithSignature(
"executeOperation(address,uint256,uint256,address,bytes)",
address(token),
amount,
fee,
msg.sender,
params
)
);
// Check the execution result.
if (!success) {
revert("Execution of receiver's contract function failed");
}
// Prevent reentrancy by requiring that it has not already been called
require(!alreadyCalled, "Reentrant call detected");
alreadyCalled = true;
// Calculate the ending balance after the receiver's operation.
endingBalance = token.balanceOf(address(assetToken);
s_currentlyFlashLoaning[token] = false;
if (endingBalance < startingBalance + fee) {
revert ThunderLoan__NotPaidBack(startingBalance + fee, endingBalance);
}
}

In this code, a bool variable alreadyCalled is introduced to ensure that the receiverAddress contract's executeOperation function is not called reentrantly within the same transaction. This helps prevent reentrancy attacks.

Updates

Lead Judging Commences

0xnevi Lead Judge
about 2 years ago
0xnevi Lead Judge about 2 years ago
Submission Judgement Published
Invalidated
Reason: Vague generalities

Support

FAQs

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