The Standard

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

DOS in claimRewards Function Due to Fee-on-Transfer Token Handling in LiquidationPool Contract

Summary

Fee-on-transfer tokens may fail the claimRewards function in the LiquidationPool contract and DOS the reward claiming process.

Vulnerability Details

After a position gets liquidated, stakers will buy the liquidated assets at a discounted rate, this process is performed through the distributeAssets() function. The amount of bought liquidated assets is added is then added to the rewards mapping.

https://github.com/Cyfrin/2023-12-the-standard/blob/91132936cb09ef9bf82f38ab1106346e2ad60f91/contracts/LiquidationPool.sol#L227

rewards[abi.encodePacked(_position.holder, asset.token.symbol)] += _portion;

Users can redeem these accrued "rewards" by utilizing the claimRewards function. This function systematically examines each token registered in the protocol, identifying and processing any available claimable rewards for users.
The issue at hand is that this function is not adapted for fee on transfer tokens and the contract may not have have have the claimable balance. This will revert the whole claiming process.

Let's consider this scenario:

_token is given out as a reward. It is a fee-on-transfer token with a fee of 2%
User A holds 98% of the total supply of claimable rewards.
User B holds 1%
User C holds 1%

User A is the first to claim his/her reward and gets 98% of the reward, leaving 0 wei of the _token left (since the other 2% was already taken as a fee by the token itself)
User B tries to claim and the call reverts since there's no balance left.
Same outcome for User C.

Impact

The impact can be considered medium to high as if one call reverts the whole loop of claim calls reverts as well.

Tools Used

Manual review

Recommendations

Check if the contract has sufficient balance to cover the call , if not just skip the call. Although not optimal the user will be able to claim his rewards when more assets available.

function claimRewards() external {
ITokenManager.Token[] memory _tokens = ITokenManager(tokenManager).getAcceptedTokens();
for (uint256 i = 0; i < _tokens.length; i++) {
ITokenManager.Token memory _token = _tokens[i];
uint256 _rewardAmount = rewards[abi.encodePacked(msg.sender, _token.symbol)];
@> if (_rewardAmount > 0 && IERC20(_token.addr).balanceOf(this(address)) > _rewardAmount) {
delete rewards[abi.encodePacked(msg.sender, _token.symbol)];
if (_token.addr == address(0)) {
(bool _sent,) = payable(msg.sender).call{value: _rewardAmount}("");
require(_sent);
} else {
IERC20(_token.addr).transfer(msg.sender, _rewardAmount);
}
}
}
}
Updates

Lead Judging Commences

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

fee-on-transfer

hrishibhat Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Out of scope
Assigned finding tags:

fee-on-transfer

Support

FAQs

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