The increasePosition function in LiquidationPool.sol allows any stake amount, leading to the creation of new stakes in the pendingStakes[] array. These pending stakes undergo processing the day after staking through consolidatePendingStakes(). The function is triggered on every increasePosition, decreasePosition, and distributeAssets call. Exploiting this, a malicious actor can repeatedly transfer minuscule amounts (1 wei) of EUROs / TST tokens, causing an infinite increase in the length of the pending stakes array. This manipulation results in excessive gas consumption or reverts during subsequent interactions.
Vulnerability Details
Attacker mints 1 EUROs token from Vault.
Calls increasePosition with 1 wei, repeating the process multiple times (e.g., 1^18 times).
The consolidatePendingStakes function, which is gas-intensive, processes the pending stakes.
Iterates through pending stakes.
Deletes processed pending stakes by left-shifting elements in the array.
Interactions with decreasePosition, distributeAssets, or increasePosition on the following day lead to reverts or significant gas consumption.
Impact
The vulnerability results in failed or excessively gas-consuming interactions, affecting the liquidity pool.
Tools Used
Manual Review
Recommendations
Implement checks to ensure that stake amounts are within reasonable limits, preventing abuse with extremely small values.
M-01. Risk of reward loss due to deletions in the holders array, impacting liquidity stake-holders
In the LiquidityPool contract, when a liquidity holder withdraws their entire staked amount from the pool, the corresponding position is deleted from the positions mapping, and the holder is removed from the holders array. However, a flaw in the logic exists: positions that are still pendingStake processing after this withdrawal action (entire stake removal) are not considered for reward distribution. This oversight results in potential loss of rewards for affected positions.
empty(): Checks for stake in the existing positions of the caller.
deleteHolder(): deletes the holder from holders array.
consolidatePendingStakes(): Never checks for holder existence in holders array. Which is essential to spread and claim reward tokens.
Vulnerability Details
Scenario:
User 1 opens a new position with 10 TST.
The position is processed the next day.
Scenario:
On the second day, User 1 opens several new positions by calling increasePosition, and they are sent to pendingStakes.
Assuming User 1 took another position with 10 TST.
User 1 wants to decrease the position by the amount of 10 TST (available to redeem as processed from pending to positions).
Outcome:
User 1 assumes that they held a position at LP and earned rewards.
But on the decrease position call, User 1 deleted their address from holders.
To receive and claim rewards, the code holds the logic to iterate through the holders array.
Result:
User 1 never acquired rewards for this holding.
Hardhat Test PoC
Add the below test code to the liquidation pool test
Sample Result:
Impact
This is an edge case where a stake-holder assumes to be receiving rewards on their stake but doesn't. Stake-holders fail to receive rewards until they call the increasePosition function which is as rare action as the cause to vulnerability.