Liquid Staking

Stakelink
DeFiHardhatOracle
50,000 USDC
View results
Submission Details
Severity: medium
Invalid

Attacker can DOS all PerformUpkeep calls by sending dust mount if Newreard is slightly less than 0 .

Summary

An attacker can execute a Denial of Service (DoS) attack on all `PerformUpkeep` calls by sending a dust amount of tokens (To ensure that new reward is less than the reward threshold) to a splitter. This causes `Newreward` to be greater than 0 but less than the threshold, resulting in a DoS for subsequent `PerformUpkeep` calls

Vulnerability Details

`checkUpkeep` calculates whether `PerformUpkeep` should be triggered. Here is the relevant part of the code:

/**
* @notice Returns whether a call should be made to performUpkeep to split new rewards
* @return upkeepNeeded true if performUpkeep should be called, false otherwise
* @return performData abi encoded list of splitters to call
*/
function checkUpkeep(bytes calldata) external view returns (bool, bytes memory) {
bool[] memory splittersToCall = new bool[]();
bool overallUpkeepNeeded;
@audit>> 1. >> for (uint256 i = 0; i < splittersToCall.length; ++i) {
@audit>> 2. Find splitter that needs upkeep>> (bool upkeepNeeded, ) = splitters[accounts[i]].checkUpkeep("");
splittersToCall[i] = upkeepNeeded;
if (upkeepNeeded) overallUpkeepNeeded = true;
}
return (overallUpkeepNeeded, abi.encode(splittersToCall));
}

If `Newreward` is less than 0 or greater than or equal to the reward threshold, the upkeep is triggered.

/**
* @notice Returns whether a call should be made to performUpkeep to split new rewards
* @return upkeepNeeded true if performUpkeep should be called, false otherwise
*/
function checkUpkeep(bytes calldata) external view returns (bool, bytes memory) {
int256 newRewards = int256(lst.balanceOf(address(this))) - int256(principalDeposits);
@audit>> 1. >> if (newRewards < 0 || uint256(newRewards) >= controller.rewardThreshold())
@audit>> 2. >> return (true, bytes(""));
return (false, bytes(""));
}

However, an attacker can exploit this by donating a small dust amount of `lst` tokens to just one splitter. This ensures that `newRewards` is greater than 0 but less than the threshold after we have checked the checkupkeep.

In the following code, `performUpkeep` loops through each splitter, but if any splitter has insufficient rewards (less than the threshold), it reverts the entire process:

/**
* @notice splits new rewards between receivers
* @param _performData abi encoded list of splitters to call
*/
function performUpkeep(bytes calldata _performData) external {
bool[] memory splittersToCall = abi.decode(_performData, (bool[]));
bool splitterCalled;
@audit>> 1. loop >> for (uint256 i = 0; i < splittersToCall.length; ++i) {
@audit>> 2. >> if (splittersToCall[i] == true) {
@audit>> 3. revert >> splitters[accounts[i]].performUpkeep("");
splitterCalled = true;
}
}
if (splitterCalled == false) {
revert InvalidPerformData();
}
}

In this case, the attacker can cause the `PerformUpkeep` to revert by ensuring that the rewards in one of the splitters are less than the threshold, causing the entire `PerformUpkeep` call to fail. This can be done by sending a small amount of tokens to just one splitter.

/**
* @notice Splits new rewards between fee receivers
*/
function performUpkeep(bytes calldata) external {
int256 newRewards = int256(lst.balanceOf(address(this))) - int256(principalDeposits);
if (newRewards < 0) {
principalDeposits -= uint256(-1 * newRewards);
@audit>> 1. revert >> } else if (uint256(newRewards) < controller.rewardThreshold()) {
@audit>> 2. revert >> revert InsufficientRewards();
} else {
_splitRewards(uint256(newRewards));
}
}

Impact

This vulnerability allows an attacker to DoS all `PerformUpkeep` calls by sending a dust amount of tokens. By exploiting this, the attacker can prevent all maintenance and reward splitting, severely impacting the functionality of the system.

Tools Used

Manual Code Review

Recommendations

1. **Error Handling in `PerformUpkeep`**: Implement `try/catch` blocks to handle failed splitter calls without reverting the entire `PerformUpkeep` process. This will ensure that even if one splitter fails due to insufficient rewards, the other splitters can still proceed with reward splitting.

2. **Manual Split Rewards Handling**: If a call to a splitter fails, emit an event logging the failure and attempt to call `splitRewards` manually later as this was created to handle cases where the limits should be bypassed. This would prevent a single splitter with insufficient rewards from causing the whole process to revert.

Updates

Lead Judging Commences

inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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