Thunder Loan

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

[M-04] Exchange Rate Updated Before Token Transfer — Fee Applied on Stale State

[M-04] Exchange Rate Updated Before Token Transfer — Fee Applied on Stale State

Scope

  • ThunderLoan.sol

Description

In flashloan(), updateExchangeRate(fee) is called before tokens are transferred out and before the callback executes. This means the exchange rate reflects a fee that hasn't been collected yet. If a redeem() occurs during the callback, it uses an inflated exchange rate based on uncollected fees.

// ThunderLoan.sol — flashloan()
@> assetToken.updateExchangeRate(fee); // Fee "applied" BEFORE collection
emit FlashLoan(receiverAddress, token, amount, fee, params);
s_currentlyFlashLoaning[token] = true;
assetToken.transferUnderlyingTo(receiverAddress, amount); // Tokens leave AFTER rate update
receiverAddress.functionCall(...); // Fee collected in callback

Risk

Likelihood: Medium

  • Occurs on every flash loan. Exploitable when redeem() is called during the flash loan callback by a third party or by the receiver itself.

Impact: Medium

  • LPs can redeem at an inflated exchange rate before the fee is actually collected, extracting value that doesn't exist yet.

Severity: Medium

Proof of Concept

An LP who observes a flash loan in the mempool can sandwich it: call redeem() during the callback (or front-run the flash loan with a redeem() that settles at the pre-fee rate, then back-run with a deposit() at the post-fee rate).

function test_exchange_rate_premature_update() public {
// Deposit 100 tokens
thunderLoan.deposit(tokenA, 100e18);
uint256 rateBefore = assetToken.getExchangeRate();
// Exchange rate is updated BEFORE tokens leave
// If redeem() were called at this point, it would use the inflated rate
thunderLoan.flashloan(address(receiver), tokenA, 50e18, "");
uint256 rateAfter = assetToken.getExchangeRate();
assertGt(rateAfter, rateBefore); // Rate increased by fee amount before fee was collected
}

Recommended Mitigation

Move the exchange rate update to after the repayment verification:

function flashloan(...) external {
uint256 startingBalance = IERC20(token).balanceOf(address(assetToken));
uint256 fee = getCalculatedFee(token, amount);
- assetToken.updateExchangeRate(fee);
// ... transfer and callback ...
uint256 endingBalance = token.balanceOf(address(assetToken));
if (endingBalance < startingBalance + fee) { revert; }
+ assetToken.updateExchangeRate(fee); // Update AFTER fee is confirmed collected
s_currentlyFlashLoaning[token] = false;
}
Updates

Lead Judging Commences

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