The Standard

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

Borrowing Fees Rewards are not distributed to EUROs Stakers

Summary

One of the intended purpose of providing EURO and TST to the LiquidationPool is for the users to get a portion of the borrowing fees as described in the README:

- **Stakers**: users adding TST and/or EUROs to the Liquidation Pool, in order to gain rewards from borrowing fees and vault liquidations

As confirmed by the sponsor the protocol address in SmartVaultManagerV5 is the LiquidationPoolManager address so each time a user mint or burn EUROs fees are sent to the LiquidationPoolManager.

The fee for the pool is split between the protocol and the Stakers based on poolFeePercentage that is set as a reward distribution ratio between stackers and the protocol. The amount left after distributeFees is sent to the protocol.

Unfortunately the current implementation only reward TST staker and not EUROs stacker

Vulnerability Details

As we can see bellow in the LiquidationPool contract borrowing Fees reward are not transfered and added to the current user position if they didn't Stake TST.

function getTstTotal() private view returns (uint256 _tst) {
for (uint256 i = 0; i < holders.length; i++) {
_tst += positions[holders[i]].TST;
}
for (uint256 i = 0; i < pendingStakes.length; i++) {
_tst += pendingStakes[i].TST;
}
}
function distributeFees(uint256 _amount) external onlyManager {
uint256 tstTotal = getTstTotal();
@> if (tstTotal > 0) {
//@audit transfer EUROs reward from the LiquidationPoolManager to the LiquidationPool contract
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;
}
}
}

Furthermore the protocol will get the total amount of the reward instead of sharing it with the users or leaving it in the LiquidationPoolManager:

function distributeFees() public {
IERC20 eurosToken = IERC20(EUROs);
uint256 _feesForPool = eurosToken.balanceOf(address(this)) * poolFeePercentage / HUNDRED_PC;
if (_feesForPool > 0) {
eurosToken.approve(pool, _feesForPool);
//@audit nothing is distributed if no TST are stacked
LiquidationPool(pool).distributeFees(_feesForPool);
}
//@audit the protocol got the full euro balance instead of the expected share address(this) - _feesForPool
@> eurosToken.transfer(protocol, eurosToken.balanceOf(address(this)));
}

Impact

This issue is reported as medium as there's a disruption of protocol functionality, the expected behavior for users staking TST and/or EUROs is to receive potentially BOTH rewards from borrowing fees and vault liquidations.

The current implementation doesn't incentivize users to stake EUROs which is a key mechanism.

Tools Used

Manual review

Recommendations

1 - Inform the protocol's users clearly that borrowers fees are only claimable by user staking TST but it could more interesting for the project tokenomics to allow user who stake EUROs to get rewarded with borrowers fees

2 - Or update the protocol logic to distribute rewards equally between TST and EUROs staker

function getStakeTotal() private view returns (uint256 _totalStaked) {
for (uint256 i = 0; i < holders.length; i++) {
_totalStaked += positions[holders[i]].TST;
_totalStaked += positions[holders[i]].EUROs;
}
for (uint256 i = 0; i < pendingStakes.length; i++) {
_totalStaked += pendingStakes[i].TST;
_totalStaked += pendingStakes[i].EUROs;
}
}
function distributeFees(uint256 _amount) external onlyManager {
uint256 totalStaked = getStakeTotal();
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 + position.[_holder].EUROs) / totalStaked;
}
for (uint256 i = 0; i < pendingStakes.length; i++) {
pendingStakes[i].EUROs += _amount * (pendingStakes[i].TST + pendingStakes[i].EUROs) / totalStaked;
}
}
}
Updates

Lead Judging Commences

hrishibhat Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Design choice
Assigned finding tags:

informational/invalid

Support

FAQs

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