Thunder Loan

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

deposit() violates CEI before the token transfer, enabling ERC-777 reentrancy to extract extra underlying

deposit updates state before the token transfer, enabling ERC-777 reentrancy that extracts extra underlying

Description

deposit violates the Checks-Effects-Interactions pattern: it mints asset tokens and inflates the exchange rate before the external safeTransferFrom. If the underlying is an ERC-777 token, its tokensToSend hook fires during the transfer — after the rate has already been raised — handing control back to the attacker mid-deposit.

// ThunderLoan.sol:152-155
assetToken.mint(msg.sender, mintAmount); // @> effect before interaction
uint256 calculatedFee = getCalculatedFee(token, amount);
assetToken.updateExchangeRate(calculatedFee); // @> rate inflated before transfer
token.safeTransferFrom(msg.sender, address(assetToken), amount); // @> ERC-777 hook reenters here

Risk

Likelihood:
Requires the allowed underlying to be an ERC-777 (or other callback-bearing) token. Such tokens exist and can be allow-listed, so this is conditional but realistic.

Impact:
A reentrant call into redeem (or another deposit) during the transfer hook can act on the already-inflated exchange rate and the freshly minted balance, letting the attacker withdraw more underlying than they actually contributed, draining the vault.

Proof of Concept

An ERC-777 underlying whose tokensToSend hook reenters redeem extracts more than deposited.

function tokensToSend(address, address, address, uint256, bytes calldata, bytes calldata) external {
// fired mid-deposit, after mint + updateExchangeRate
thunderLoan.redeem(token, type(uint256).max); // acts on inflated rate
}

Recommended Mitigation

Follow CEI — transfer first, then mint — and add a reentrancy guard.

- assetToken.mint(msg.sender, mintAmount);
- uint256 calculatedFee = getCalculatedFee(token, amount);
- assetToken.updateExchangeRate(calculatedFee);
- token.safeTransferFrom(msg.sender, address(assetToken), amount);
+ token.safeTransferFrom(msg.sender, address(assetToken), amount);
+ assetToken.mint(msg.sender, mintAmount);
// and add `nonReentrant` to deposit()
Updates

Lead Judging Commences

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