Smart contract unable to operate due to lack of token funds
Direct theft of any user funds, whether at-rest or in-motion, other than unclaimed yield
Description
Brief/Intro
A user’s position that has the internal _sync() function triggered at specific times loses less collateral during redemptions. This happens because the amount of collateral removed depends on when _sync() is called. Users who trigger it more often (directly or indirectly through poke()) can end up with more collateral than others who hold the same position. Over multiple redemptions, this creates an imbalance that leads to protocol insolvency — the recorded collateral in user positions becomes greater than the actual tokens held by the contract.
Vulnerability Details
When collateral is updated inside _sync(), the function calculates how much collateral to remove based on global weights:
Here, _collateralWeight is a global value that tracks how much collateral decay has occurred, and account.lastCollateralWeight is the user’s last recorded checkpoint. The problem is that this checkpoint is only updated when _sync() runs, so the amount of collateral a user loses depends on when their position gets synced.
If a user’s position has _sync() triggered right before a redemption is claimed, their lastCollateralWeight will catch up to the most recent value — meaning they’ll lose less collateral compared to positions that haven’t been synced recently. This gives unfair advantage to those who interact more often and causes an increasing mismatch between total user balances and actual collateral in the protocol.
In the test below, both beef and dad deposited and borrowed the same amount, but only beef had their position synced (via poke()) before each redemption:
As shown, total user collateral (150.4032e18) exceeded the actual tokens in the contract (150e18), proving that syncing timing allows value to be created out of thin air.
Impact Details
This issue causes protocol insolvency over time. Since users with more frequent _sync() updates lose less collateral, the system records more total collateral than it actually holds. Eventually, when users withdraw, there won’t be enough tokens to cover all recorded balances. This leads to unequal treatment between users, loss of fairness, and a potential full insolvency of the protocol’s collateral pool. Furthermore, the user beef can withdraw more than they should actually stealing from other users, as the last user withdrawing would not be able to withdraw their full collateral, as it will not be available in the contract.
Proof of Concept
Proof of Concept
Add this test testPokeToIncreaseCollateralValue() in AlchemistV3.t.sol