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 3 months 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!