Thunder Loan

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

# `updateFlashLoanFee` permits a fee of up to 100% of the borrowed value

updateFlashLoanFee permits a fee of up to 100% of the borrowed value

Severity: Low · Impact: Low · Likelihood: Low

Description

  • The flash-loan fee is a fraction scaled by s_feePrecision (1e18); the default is 3e15 (0.3%). A sane upper bound should be well below 100%.

  • updateFlashLoanFee only rejects values strictly greater than s_feePrecision, so s_flashLoanFee can be set exactly to 1e18 — a 100% fee — which is nonsensical and would make every flash loan cost the full borrowed value.

function updateFlashLoanFee(uint256 newFee) external onlyOwner {
@> if (newFee > s_feePrecision) { // allows newFee == s_feePrecision (100%)
revert ThunderLoan__BadNewFee();
}
s_flashLoanFee = newFee;
}

Risk

Likelihood:

  • Requires the owner to set an unreasonable fee (or fat-finger a value near 1e18). Owner-only, so likelihood is Low, but there is no guardrail against a value that breaks the product.

Impact:

  • A fee at or near 100% makes flash loans unusable and lets a misconfigured/compromised owner capture the entire borrowed value as fees. Bounded to owner action, so Low.

Proof of Concept

Save the block below as test/PocL3.t.sol and run forge test --mt test_L3_fee_can_be_set_to_100_percent -vv. updateFlashLoanFee(1e18) is accepted and the fee becomes 100% of the borrowed value.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import { Test, console } from "forge-std/Test.sol";
import { ThunderLoan } from "../src/protocol/ThunderLoan.sol";
import { ERC20Mock } from "@openzeppelin/contracts/mocks/ERC20Mock.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { MockPoolFactory } from "./mocks/MockPoolFactory.sol";
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
contract PocL3 is Test {
function test_L3_fee_can_be_set_to_100_percent() public {
ThunderLoan impl = new ThunderLoan();
MockPoolFactory factory = new MockPoolFactory();
ERC20Mock token = new ERC20Mock();
factory.createPool(address(token));
ERC1967Proxy proxy = new ERC1967Proxy(address(impl), "");
ThunderLoan tl = ThunderLoan(address(proxy));
tl.initialize(address(factory));
tl.setAllowedToken(IERC20(address(token)), true);
// updateFlashLoanFee only rejects newFee > 1e18, so 1e18 (100%) is accepted.
tl.updateFlashLoanFee(1e18);
assertEq(tl.getFee(), 1e18);
// The fee now equals 100% of the borrowed value (price = 1e18).
uint256 borrow = 100e18;
uint256 fee = tl.getCalculatedFee(IERC20(address(token)), borrow);
console.log("fee for borrowing 100e18:", fee); // 100e18
assertEq(fee, borrow); // 100% fee
}
}

Recommended Mitigation

Bound the fee to a sensible maximum well under 100% (e.g. a few percent), and make the comparison inclusive.

+ uint256 private constant MAX_FEE = 1e17; // 10%
function updateFlashLoanFee(uint256 newFee) external onlyOwner {
- if (newFee > s_feePrecision) {
+ if (newFee > MAX_FEE) {
revert ThunderLoan__BadNewFee();
}
s_flashLoanFee = newFee;
}
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge about 2 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!