Thunder Loan

AI First Flight #7
Beginner FriendlyFoundryDeFiOracle
EXP
View results
Submission Details
Impact: low
Likelihood: high
Invalid

ExecuteOperation in IFlashLoanReceiver return a bool but contract does not

Root + Impact

Description

  • The `IFlashLoanReceiver` interface defines `executeOperation()` as returning a `bool`. Flash loan receivers are expected to implement this interface and communicate the outcome of their callback logic through this return value.

  • `ThunderLoan::flashloan()` never decodes or validates the return value of the `executeOperation()` callback — the `bool` is silently discarded. This creates a misleading contract with integrators, who may rely on `return false` as a graceful failure mechanism rather than using `revert`, unknowingly bypassing any error signaling they intended to implement.

// SPDX-License-Identifier: AGPL-3.0
pragma solidity 0.8.20;
import {IThunderLoan} from "./IThunderLoan.sol";
/**
* @dev Inspired by Aave:
* https://github.com/aave/aave-v3-core/blob/master/contracts/flashloan/interfaces/IFlashLoanReceiver.sol
*/
interface IFlashLoanReceiver {
function executeOperation(
address token,
uint256 amount,
uint256 fee,
address initiator,
bytes calldata params
@> ) external returns (bool);
}

Risk

Likelihood:

  • This issue manifests any time a flash loan receiver implements error-handling logic based on `return false` instead of `revert`, a pattern explicitly suggested by the interface signature itself.

  • The likelihood increases as third-party integrators familiar with Aave's flash loan standard port their receivers to ThunderLoan, expecting the same return value validation behavior.

Impact:

  • Receivers that rely on `return false` to signal a failed operation will have their error logic silently ignored, causing the flash loan to proceed as if the callback succeeded, with the only safety net being the ending balance check.

  • The misleading interface creates a false sense of security for integrators, potentially masking bugs in receiver logic that would otherwise be caught through proper return value validation.

Proof of Concept


// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
contract ReturnValueIgnoredPoC is IFlashLoanReceiver {
IThunderLoan private thunderLoan;
IERC20 private token;
bool private s_returnValue;
constructor(address _thunderLoan, address _token) {
thunderLoan = IThunderLoan(_thunderLoan);
token = IERC20(_token);
}
function executeOperation(
address _token,
uint256 amount,
uint256 fee,
address initiator,
bytes calldata params
) external returns (bool) {
// Repay the flash loan to pass the balance check
IERC20(_token).approve(address(thunderLoan), amount + fee);
thunderLoan.repay(_token, amount + fee);
// Signal failure via return false — should revert the flashloan
// but ThunderLoan will ignore this and complete successfully
return false;
}
function requestFlashLoan(uint256 amount) external {
// Initiate flash loan — expected to revert due to return false
// but it will complete successfully, proving the bool is ignored
thunderLoan.flashloan(address(this), token, amount, "");
}
}

This contract request a flashLoan and return a false boolean that should revert

// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
contract ReturnValueIgnoredTest is ThunderLoanTest {
ReturnValueIgnoredPoC poc;
function setUp() public override {
super.setUp();
poc = new ReturnValueIgnoredPoC(
address(thunderLoan),
address(tokenA)
);
// Fund the poc contract to cover fees
tokenA.mint(address(poc), AMOUNT);
}
function testReturnValueIsIgnored() public {
uint256 amount = 1e18;
// The poc returns false from executeOperation
// If the bool were checked, this should revert
// Instead it completes successfully — proving the finding
vm.expectRevert(); // ← this will FAIL, proving bool is ignored
poc.attack(amount);
}}

Running the test above demonstrates that testReturnValueIsIgnored() fails
because no revert occurs. The flash loan completes successfully despite
executeOperation() returning false, confirming that the return value is
never validated by the protocol.

Recommended Mitigation

There are two option:
1) updating `ThunderLoan::flashloan()` to explicitly decode and validate the return value of the `executeOperation()` callback, reverting if the receiver signals a failed operation.
2) Alternatively, if the protocol does not intend to support graceful failure via return value, the `bool` return type should be removed from the `IFlashLoanReceiver` interface to accurately reflect the actual behavior and avoid misleading integrators

receiverAddress.functionCall(
- abi.encodeWithSignature(
- "executeOperation(address,uint256,uint256,address,bytes)",
- address(token),
- amount,
- fee,
- msg.sender,
- params
- )
- );
+ bytes memory returnData = receiverAddress.functionCall(
+ abi.encodeWithSignature(
+ "executeOperation(address,uint256,uint256,address,bytes)",
+ address(token),
+ amount,
+ fee,
+ msg.sender,
+ params
+ )
+ );
+ bool success = abi.decode(returnData, (bool));
+ if (!success) revert ThunderLoan__ExecuteOperationFailed();
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 3 hours ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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

Give us feedback!