Because pending stakes positions are allocated a proportion of distributed fees; user can add a large pending stake to earn a high amount of fees. Pending Stakes should need to wait a minimum periuod of time before they can earn fees; otherwise opportunists will front-run fee paying functions simply to earn fees, causing instability and volatility to the protocol.
In LiquidationPool::distributeFees()
fees are distributed to both holders
and pendingStakes
positions. Pending Stakes become holders after 24 hours but while they are still pending stakes they are entitled to any fees distributed during runLiquidation()
or when positions are increased / decreased in LiquidationPool
.
This creates an incentive for malicious users to front-run pending transactions in the mempool for LiquidationPoolManager::runLiquidation()
, LiquidationPool::increasePosition()
& LiquidationPool::decreasePosition()
.
It would take just a few 100 holders on top of the users already in the protocol for these functions to begin running out of gas or to become prohibitively expensive to be used.
To test, we need to update the code in 2 places:
In hardhat.config.js
update the code to config to increase the timeout:
Add this test to liquidationPoolManager.js
where we assume 600 holders and 200 and run:
Malicious users simply enter the system to gain fees and likely leave again. This will cause large swings in the amount of staked TST
and EUROs
in the protocol making other functionality behave inconsistently such as the rewards
which only holders are entitled to; their returns will be very volatile based on inflows and outflows of capital.
Another more serious issue is where many malicious users enter the system to take fees; their pending stakes can be added to their position
after 24 hours. This is likely to significantly increase the size of the holders
storage array. In multiple places throughout the protocol this array is looped through so the larger it gets the higher the likelihood of OOG issues and DOSing of certain protocol functionalities.
Even if functions don't run out of gas, the gas cost of carrying them out can become prohibitively expensive to be used.
Given their looping of the holders
array; functionality which would be seriously affected include runliquidation()
, increasePosition()
, decreasePosition()
, distributeFees()
, distributeAssets()
.
Manual Review
Hardhat Testing
Remove the ability of pendingStakes()
to earn fees completely or add a few hours delay so they can't front-run:
```diff
function distributeFees(uint256 _amount) external onlyManager {
uint256 tstTotal = getTstTotal();
if (tstTotal > 0) {
IERC20(EUROs).safeTransferFrom(msg.sender, address(this), _amount);
for (uint256 i = 0; i < holders.length; i++) {
address _holder = holders[i];
positions[_holder].EUROs += _amount * positions[_holder].TST / tstTotal;
}
- for (uint256 i = 0; i < pendingStakes.length; i++) {
- pendingStakes[i].EUROs += _amount * pendingStakes[i].TST / tstTotal;
- }
}
}
```
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.