At HackenProof, we believe that some of the most valuable security knowledge is created inside the hacker community itself. This belief is reflected in our ongoing series of guest articles, where security researchers from our community share practical insights, practical knowledge, and real-world lessons from their work in smart contract security, Web3 development, and bug bounty research. By publishing hacker-authored content, we aim to make expert-level security knowledge more accessible and to support continuous learning across the broader Web3 security ecosystem.
We regularly curate and publish the strongest technical articles based on their educational value, technical depth, and relevance to real security challenges. Authors whose work is published on the HackenProof blog receive the Star Author achievement, recognizing their contribution to knowledge sharing and community growth.
Read the article, explore the ideas, and share your thoughts with the community — and if you have expertise to share, this could be your first step toward becoming our next Star Author.
About the Author
This article was written by Flash007, a security researcher from Switzerland and a member of the HackenProof community since April 2026. In the short time since joining, Flash007 has already demonstrated sharp instincts for vault-level accounting vulnerabilities — the kind of edge cases that slip through automated tools and surface only under deep manual review. We’re glad to have this perspective in our community.
Introduction: The Subtle Art of Vault Accounting
In the world of DeFi, the ERC4626 Tokenized Vault Standard has become the backbone of yield-bearing assets. It provides a clean, standardized API for deposits and withdrawals. However, beneath this elegant interface lies a complex accounting engine where small edge cases can lead to catastrophic economic consequences.
As a smart contract security researcher, I recently encountered a fascinating vulnerability that highlights a dangerous blind spot in how vaults handle unvested rewards during a zero-supply state. While the specific protocol I audited remains confidential per bug bounty terms, the underlying mechanics are universal and serve as a critical lesson for any developer building yield-bearing vaults.
Background: How Vaults Price Shares
To understand the bug, we must first look at the core of ERC4626: the conversion between assets (USDC, ETH, etc.) and shares.

This logic is standard and correct. It states:
– If the vault is empty (supply == 0): 1 asset = 1 share.
– If the vault is not empty: Shares are priced proportionally to the current assets.
This “empty vault” path is designed for initialization. But what happens if a vault becomes empty again after operating for some time? Most protocols add a “minimum share” requirement (e.g., MIN_SHARES = 1 ether) to prevent the vault from fully emptying due to donation attacks or rounding errors.
But what if that minimum share protection fails?
The Vulnerability: The Stranded Reward Window
The protocol in question implemented a linear vesting mechanism for rewards. Imagine a staking vault where protocol rewards are sent to the contract but only become “recognized” in the totalAssets() calculation over an 8-hour period.
Step 1: The Reward Drop. The protocol’s rewarder role sends 100 ETH (or USDe) to the vault. The contract records this as pendingVestingAmount.
Step 2: The Silent Balance. The contract’s actual ETH balance is now 100, but the totalAssets() function subtracts the unvested amount, returning 0 (or a much lower number).
Step 3: The Last Exit. The final user in the vault withdraws their entire position. Due to the unvested rewards, totalSupply() drops to 0.
Step 4: The Empty Vault Trap. The vault now has totalSupply() == 0, totalAssets() == 0, but the raw ETH balance is still 100 ETH (unvested).
Step 5: The Exploit. An attacker monitors the mempool for this exact state. They deposit 0.001 ETH. Because totalSupply() is 0, the vault mints shares at a 1:1 ratio.
Step 6: The Vesting Completion. The 8-hour timer elapses. The unvestedAmount becomes 0. Suddenly, totalAssets() jumps to reflect the full 100 ETH balance.
Step 7: The Drain. The attacker redeems their shares. They now own the vast majority of the vault’s assets, having effectively stolen the yield intended for previous stakers.
Why Minimum Share Protection Didn’t Work
The protocol had a _checkMinShares() function designed to prevent the vault from going to zero. It looked something like this:

Notice the flaw? It only reverts if supply is between 1 and MIN_SHARES. It does not prevent supply from dropping all the way to exactly 0. Once the last user withdraws completely, the function passes, and the vault resets to an empty state while still holding the stranded, unvested rewards.
The Economic Impact: Who Loses?
This is not a case of “MEV” or “someone has to get the yield.” This is Theft of Unclaimed Yield.
– The Victim: The previous stakers (or the protocol treasury) who were supposed to accrue that 100 ETH over time.
– The Attacker: A bot that can turn a dust deposit (1 wei) into a full reward capture.
In a simulated fork test of the original protocol, an attacker was able to convert a 1 USDe deposit into a 100 USDe profit within a single vesting window.
Mitigation: How to Build Safer Vaults
If you are a developer building on ERC4626, here are the key takeaways to prevent this specific “Phantom Deposit” attack:
Block Zero Supply, Period. Modify the _checkMinShares() logic (or the withdrawal logic) to ensure that totalSupply() can NEVER become 0 as long as the contract holds any assets, vested or not.
// Safer check
1. require(totalSupply() > MIN_SHARES || totalSupply() == 0 && address(this).balance == 0, “Cannot fully drain vault with pending rewards”);
2. Pro-Rata Vesting Inclusion. A more advanced fix involves recognizing that unvested rewards *belong* to the current share distribution. If a user exits during the vesting period, they should receive a pro-rata claim on the unvested amount, or the vault should lock withdrawals until vesting completes.
3. Buffer the Empty State. Always maintain a permanent “dust” balance (e.g., 1 wei locked forever) to ensure the supply == 0 branch in convertToShares is **never** reachable after the vault has been bootstrapped.
Conclusion: The Importance of State Synchronicity
This vulnerability is a powerful reminder that actual balance (address(this).balance) and accounting balance (totalAssets()) are two different things. In the world of DeFi, vesting and time-locks create temporal gaps where these two values diverge. If an attacker can force a state transition (like a full exit) precisely within that gap, they can exploit the vault’s pricing mechanism.
For bug bounty hunters, this serves as a playbook: when you see a protocol using totalAssets() and linear vesting, always ask:
What happens if the vault empties out at the exact moment rewards are unvested?
The answer is often a high-severity finding.



