Liquid Staking

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

Permanent Fund Lock in splitRewards Function Due to Incorrect Negative Balance Handling

Details

The splitRewards function, which allows for manual triggering of reward distribution, has a flaw in its logic for handling cases where the contract's balance falls below the recorded principalDeposits.

Code Snippet

function splitRewards() external {
int256 newRewards = int256(lst.balanceOf(address(this))) - int256(principalDeposits);
if (newRewards < 0) {
principalDeposits -= uint256(-1 * newRewards); // Incorrect adjustment of principalDeposits
} else if (newRewards == 0) {
revert InsufficientRewards();
} else {
_splitRewards(uint256(newRewards));
}
}

When the contract's LST balance drops below the recorded principalDeposits (due to events like slashing, forced transfers, or errors in other parts of the code), the newRewards calculation results in a negative value.

In this case, the function adjusts the principalDeposits downwards to match the current balance. However, it fails to distribute the remaining balance as rewards.

This creates a situation where the remaining funds become inaccessible. The contract effectively "forgets" about these funds, as future splitRewards calls will calculate newRewards as 0 (since the adjusted principalDeposits now matches the balance).

Impact

  • Permanent Loss of Funds: The undistributed funds become permanently locked in the contract, unavailable for withdrawal or future reward distributions.

  • Inconsistent State: The contract's internal accounting (principalDeposits) becomes misaligned with the actual funds held, leading to potential inconsistencies and unexpected behavior.

  • Denial of Service: This vulnerability can be exploited to create a denial-of-service (DoS) scenario, preventing any further reward distribution.

scenario

  • A user deposits 1000 LST tokens into the contract (principalDeposits = 1000).

  • Due to an external event, 100 tokens are lost, leaving the contract with a balance of 900 LST.

  • A user calls splitRewards.

  • The function calculates newRewards as -100 and adjusts principalDeposits to 900. However, it doesn't distribute the remaining 900 tokens.

  • These 900 tokens are now permanently locked, as future splitRewards calls will see no new rewards.

Fix

To address this vulnerability, modify the splitRewards function to handle the case where the current balance is less than principalDeposits by distributing the remaining balance before adjusting the principal:

function splitRewards() external {
uint256 currentBalance = lst.balanceOf(address(this));
int256 newRewards = int256(currentBalance) - int256(principalDeposits);
if (newRewards <= 0) {
if (currentBalance > 0) {
_splitRewards(currentBalance); // Distribute remaining balance
}
principalDeposits = 0; // Reset principalDeposits
} else {
_splitRewards(uint256(newRewards));
principalDeposits = currentBalance - uint256(newRewards);
}
if (principalDeposits == 0 && currentBalance == 0) {
revert InsufficientRewards();
}
}

This corrected version ensures that all remaining funds are distributed even when the balance falls below the expected principal, preventing any permanent loss of funds and maintaining the consistency of the contract's accounting.

Updates

Lead Judging Commences

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.