A malicious actor can grief legitimate users from depositing into a vault using vaultrouterbranch::deposit by directly sending asset tokens to the vault contract, effectively “filling” the vault from the outside and preventing new deposits. Although this does not result in stolen funds, it creates a denial of service for depositors trying to use the vault normally.
VaultRouterBranch::deposit contains the following line which allows users deposit into a vault:
This calls ZlpVault::deposit which in turns calls ERC4626Upgradeable::deposit:
The bug is located in ZlpVault::maxDeposit :
ZlpVault::maxDeposit() function in the vault contract, which calculates the remaining allowable deposit based on depositCap - totalAssets().
The vault’s logic only checks totalAssets() against depositCap, but does not handle externally transferred tokens. Attackers can bypass the normal deposit() flow by sending tokens directly to the vault contract. Once the vault’s actual balance (totalAssets()) meets or exceeds depositCap, maxDeposit() returns 0, blocking legitimate depositors from adding new assets.
Denial-of-Service: Users are unable to deposit once an attacker inflates the vault’s balance, rendering the deposit function unusable. This directly undermines the vault’s usability and could erode user trust in the protocol’s deposit cap mechanism.
Manual Review, Foundry
Recommendation: Implement a Change-Detection Mechanism for ERC4626Upgreadeable::totalAssets()
Introduce a variable (e.g., ZlpVault::lastTotalAssets) that stores the vault’s previously observed total assets. Whenever ZlpVault::maxDeposit() is called:
Compute Current totalAssets(): Call ERC4626Upgreadeable::totalAssets() to get the current balance.
Compare totalAssets() with ZlpVault::lastTotalAssets.
If there is a difference, check whether the vault’s total share supply (e.g., totalSupply()) has also changed by a corresponding amount based on the share price formula. If the share supply does not reflect this change, assume the variance in totalAssets() is caused by an external transfer (i.e., not due to a legitimate deposit transaction).
If an external transfer is detected, ignore the externally inflated amount by using lastTotalAssets for deposit cap calculations (so malicious tokens sent directly to the contract do not reduce the user’s allowable deposit). Otherwise, use the newly computed ERC4626Upgradeable::totalAssets() for correct deposit cap tracking.
Once a legitimate deposit is verified (i.e., the share supply changed accordingly), update lastTotalAssets to the new totalAssets() value. This approach ensures that only valid deposit transactions (those which increase both assets and shares) will raise the vault’s internal “cap usage,” while malicious external transfers are effectively disregarded when calculating maxDeposit().
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.