The Standard

The Standard
DeFiHardhat
20,000 USDC
View results
Submission Details
Severity: low
Valid

Liquidations can be frontran to unfairly obtain fee rewards

Summary

The distributeFee() mechanism is a stepwise reward jump for both pendingStakes and matured positions. There is a one day maturation period for pendingStakes before transitioning into proper positions. That was so people cannot frontrun and stake right before a liquidation or fee distribution. However, this is still possible because the fees are also distributed to pendingStakes too, and not just positions.

When liquidationPoolManager::runLiquidation() is called, it liquidates a vault and then distributes fees rewarded based on the amount of TST a user has staked. A TST whale, or anyone, can frontrun every liquidation. He makes a massive stake of TST by calling increasePosition() which creates his pendingStake right before the runLiquidation() call, and then waits one day for the pendingStake to mature into a Position, so he can withdraw it if he wants with decreasePosition(). Importantly, the EUROs earned through frontrunning in the user's pendingStakes gets transferred over to their positions when their pending stake matures via consolidatePendingStake().

Note: liquidationPoolManager::distributeFees() is also frontrunable directly, but may be a moot point or lesser issue if the contract has very small fees or zero fees outside of liquidations.

Impact

  • Frontrunners can guarantee profit with these one day stakes because frontrunning allows their pending stake to always have at least one liquidation reward for it (since they frontran a liquidation and got the rewards).

  • All normal users with pending stakes get their liquidation rewards reduced by people frontrunning every liquidation and increasing the tstTotal.

Tools Used

Manual Review

Recommendations

Do not give pendingStakes any kind of rewards from liquidations/distributions.

function distributeFees(uint256 _amount) external onlyManager {
uint256 tstTotal = getTstTotal();
if (tstTotal > 0) {
IERC20(EUROs).safeTransferFrom(msg.sender, address(this), _amount); // safeTransfers the 5e18 to the pool
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;
- }
}
}
Updates

Lead Judging Commences

hrishibhat Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

frontrun-distrubutefees

hrishibhat Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

frontrun-feedist-low

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.