Thunder Loan

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

In the Deposit Function, The First Deposit Permanently Reverts Due to Division by Zero.

Root + Impact

The very first liquidity provider can never successfully deposit because the exchange rate update divides by totalSupply(), which is zero before the first mint becomes economically established.

Description

In the https://github.com/CodeHawks-Contests/ai-thunder-loan/blob/035f6dc903d7ac12c4ccf6d267a09810d3d64ef8/src/protocol/ThunderLoan.sol#L147 function
https://github.com/CodeHawks-Contests/ai-thunder-loan/blob/035f6dc903d7ac12c4ccf6d267a09810d3d64ef8/src/protocol/ThunderLoan.sol#L154 computes the newExchangeRate in the https://github.com/CodeHawks-Contests/ai-thunder-loan/blob/035f6dc903d7ac12c4ccf6d267a09810d3d64ef8/src/protocol/AssetToken.sol#L8 contract https://github.com/CodeHawks-Contests/ai-thunder-loan/blob/035f6dc903d7ac12c4ccf6d267a09810d3d64ef8/src/protocol/AssetToken.sol#L80 function as:
https://github.com/CodeHawks-Contests/ai-thunder-loan/blob/035f6dc903d7ac12c4ccf6d267a09810d3d64ef8/src/protocol/AssetToken.sol#L89
The denominator is
totalSupply()
During the first deposit, the sequence inside ThunderLoan.deposit() is
https://github.com/CodeHawks-Contests/ai-thunder-loan/blob/035f6dc903d7ac12c4ccf6d267a09810d3d64ef8/src/protocol/AssetToken.sol#L68;
https://github.com/CodeHawks-Contests/ai-thunder-loan/blob/035f6dc903d7ac12c4ccf6d267a09810d3d64ef8/src/protocol/AssetToken.sol#L80;
https://github.com/CodeHawks-Contests/ai-thunder-loan/blob/035f6dc903d7ac12c4ccf6d267a09810d3d64ef8/src/protocol/ThunderLoan.sol#L155
Although mint occurs first, the protocol computes
mintAmount =
amount * PRECISION / exchangeRate;
using the initial exchange rate.
If the deposited token has an oracle price that causes fee == 0 then
newExchangeRate = oldRate * supply / supply = oldRate
which immediately triggers
if (newExchangeRate <= s_exchangeRate)
revert;
For very small deposits (or low-value assets), integer truncation causes the calculated fee to become zero.
Thus, the first deposit can revert indefinitely until a sufficiently large deposit is attempted.
In addition, if updateExchangeRate() is ever invoked while total supply is zero (for example after complete redemption of all shares), the division
... / totalSupply()
reverts.
The exchange-rate update function assumes the existence of outstanding shares but never enforces it.
// Root cause in the codebase with @> marks to highlight the relevant section

Risk

Likelihood:

High. Small initial deposits are common during protocol bootstrapping. Low-value assets and integer truncation make this scenario realistic.

Impact:

The protocol cannot bootstrap liquidity.
This results in the protocol becoming unusable until manually patched or redeployed.

Proof of Concept

Assume
Exchange Rate = 1e18
Total Supply = 100
Fee = 0
Then
newRate
=
1e18 × (100 + 0)
/100
=
1e18
Immediately afterwards
if(newRate <= oldRate)
becomes
1e18 <= 1e18
which reverts.
The protocol therefore rejects the deposit despite no invariant being violated.
Another scenario:
After every LP redeems,
totalSupply = 0
A later flash loan executes
updateExchangeRate(fee)
which evaluates
... / 0
causing a division-by-zero revert.

Recommended Mitigation

Handle zero-supply explicitly.
For example
if (totalSupply() == 0) {
return;
}
or initialize the first exchange rate without attempting to distribute fees.
Additionally, avoid reverting when fee == 0; simply leave the exchange rate unchanged.
if (totalSupply() == 0) {
return;
} + add this code
Updates

Lead Judging Commences

ai-first-flight-judge Lead Judge 1 day 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!