Thunder Loan

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

`IThunderLoan` interface uses `address` but `ThunderLoan.repay()` uses `IERC20`

Description

IThunderLoan.sol declares repay(address token, uint256 amount) while the actual implementation uses repay(IERC20 token, uint256 amount). Although both types encode identically in the ABI (both are address at the ABI level, so the function selector is the same), this creates a type-level mismatch that confuses integrators.

// src/interfaces/IThunderLoan.sol:5
interface IThunderLoan {
function repay(address token, uint256 amount) external;
// ^^^^^^^ address type
}
// src/protocol/ThunderLoan.sol:219
function repay(IERC20 token, uint256 amount) public {
// ^^^^^^ IERC20 type

Flash loan receivers that import IThunderLoan and call IThunderLoan(thunderLoan).repay(tokenAddress, amount) will work at runtime (same ABI encoding), but Solidity's type checker treats these as different types. A receiver written as:

import { IThunderLoan } from "../interfaces/IThunderLoan.sol";
// This compiles but the parameter type doesn't match the concrete contract
IThunderLoan(msg.sender).repay(address(token), amount);

This works because ABI encoding is identical, but any developer reading the interface assumes address while the contract expects IERC20. If the interface is used for type-safe internal routing or proxy delegation, the mismatch could cause subtle issues.

Risk

Likelihood: High — any developer building a flash loan receiver will encounter this mismatch when referencing the interface.

Impact: Low — no direct fund loss since ABI encoding is identical. But it violates Solidity type safety guarantees and creates confusion for integrators, potentially leading to bugs in receiver contracts.

Proof of Concept

// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import { Test } from "forge-std/Test.sol";
import { IThunderLoan } from "../../src/interfaces/IThunderLoan.sol";
import { ThunderLoan } from "../../src/protocol/ThunderLoan.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract InterfaceMismatchTest is Test {
function testInterfaceMismatch() public pure {
// Both selectors are identical because IERC20 encodes as address
bytes4 interfaceSelector = IThunderLoan.repay.selector;
// repay(address,uint256) selector
bytes4 expectedSelector = bytes4(keccak256("repay(address,uint256)"));
assertEq(interfaceSelector, expectedSelector);
// This proves both encode identically — but the Solidity types differ
}
}

Output: Both selectors match (0x...), confirming ABI compatibility. But the source-level type mismatch (address vs IERC20) remains, creating developer confusion.

Recommended Mitigation

Align the interface with the implementation:

// src/interfaces/IThunderLoan.sol
+ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IThunderLoan {
- function repay(address token, uint256 amount) external;
+ function repay(IERC20 token, uint256 amount) external;
}
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!