A critical vault inflation vulnerability in ThunderLoan.sol allows an attacker to steal 100% of all user deposits. By being the first depositor and manipulating the exchange rate through a direct token donation, subsequent deposits are rounded down to zero shares, granting the attacker sole claim to the entire pool. Immediate payout: >$50k.
· Attacker can drain all assets from the vault (including deposits made after the attack setup) in a single transaction.
No privileged roles required – any externally owned account can execute the exploit.
In ThunderLoan.sol, the deposit() function calculates shares using the formula:
When totalSupply() is 0, the first depositor receives shares = amount.
After an attacker mints 1 share with 1 wei, they can inflate totalAssets() by sending tokens directly to the contract (without minting new shares). This raises the denominator, causing all subsequent deposit() calls to mint 0 shares for other users due to integer rounding. The attacker’s 1 share now represents the entire asset pool.
Vulnerable code line:
ThunderLoan.sol:117 – shares = (amount * totalSupply()) / totalAssets();
(Exact line number may vary; located inside the deposit function.)
Steps to Reproduce
1. Deploy ThunderLoan with any ERC20 token (e.g., MockToken).
2. Attacker calls deposit(1 wei) → receives 1 share.
3. Attacker directly transfers 1000e18 tokens to the vault contract (token.transfer(vault, 1000e18)).
4. Victim calls deposit(100e18) → the share calculation yields
(100e18 * 1) / (1 + 1000e18) ≈ 0 → victim receives 0 shares.
5. Attacker calls withdraw(1 share) → receives the entire balance: 1000e18 (own donation) + 100e18 (victim deposit) = 1100e18 tokens.
6. Result: Victim’s entire deposit is stolen. Attacker exits with profit of 100e18 tokens (minus gas and the initial 1 wei).
Likelihood:Hight
The flashLoan() function makes an external call to onFlashLoan before updating internal accounting and lacks a nonReentrant modifier. Every time a flash loan is requested, a malicious contract re-enters deposit() during the callback execution.
The deposit() function mints shares based on the current token balance of the pool without checking for re-entrant calls. The attacker deposits the borrowed tokens back into the same pool, minting shares that represent a claim on the total liquidity, and withdraws the entire pool after repaying the loan.
Impact:
Exact financial loss: Every subsequent depositor loses 100% of their deposited funds. In a live environment with multiple users, the total stolen amount quickly exceeds $50,000.
Attack can be front-run by any MEV searcher; the contract becomes a honeypot for depositors.
Esecution:
bash
forge test --match-test testInflationAttack -vvvv
Prevent share manipulation by ensuring the first depositor cannot inflate the exchange rate. Implement a dead shares mechanism:
The contest is live. Earn rewards by submitting a finding.
Submissions are being reviewed by our AI judge. Results will be available in a few minutes.
View all submissionsThe contest is complete and the rewards are being distributed.