Assessment reports>Polygon Staking>Discussion>The LSTRewardsSplitter's splitting is not path-independent, which may have cross-protocol implications

The LSTRewardsSplitter's splitting is not path-independent, which may have cross-protocol implications

The splitRewards function in the LSTRewardsSplitter does the actual splitting referenced in the contract name:

function splitRewards() external {
    int256 newRewards = int256(lst.balanceOf(address(this))) - int256(principalDeposits);
    if (newRewards < 0) {
        principalDeposits -= uint256(-1 * newRewards);
    } else if (newRewards == 0) {
        revert InsufficientRewards();
    } else {
        _splitRewards(uint256(newRewards));
    }
}
// [...]
function _splitRewards(uint256 _rewardsAmount) private {
    for (uint256 i = 0; i < fees.length; ++i) {
        Fee memory fee = fees[i];
        uint256 amount = (_rewardsAmount * fee.basisPoints) / 10000;

        if (fee.receiver == address(lst)) {
            IStakingPool(address(lst)).burn(amount);
        } else {
            lst.safeTransfer(fee.receiver, amount);
        }
    }

    principalDeposits = lst.balanceOf(address(this));
    emit RewardsSplit(_rewardsAmount);
}

Each time the function is called, at the end, the principalDeposits state variable becomes the balance of the contract. So, at the start, it is the previous split's ending balance. If the balance has dropped since the previous split, then the principal is lowered without any splitting logic. If the balance has increased, then the increase in balance causes a fee to be sent out to all the receivers.

This is notably not a path-independent splitting process. If the balance of the address increases and then decreases by the same quantity or a matching percentage, then the net behavior of the contract will be nothing if no call to splitRewards was done in the middle, but fees will be sent out if it was called in the middle.

Depending on the specific implementation of the underlying token and the associated other protocols, it is possible that profit-seeking attackers can have the ability to reentrantly call splitRewards while the balance of the contract is ephemerally high or low. Since this would be read-only reentrancy, reentrancy locks on the external contract are not likely to cause calls to balanceOf to revert.

For instance, if the underlying token is a rebasing token whose current value depends on its own valuation of several assets held by a vault, and the vault has open orders that attackers can permissionlessly fill trades on, then, during the order fill, the balance of the LSTRewardsSplitter would be either temporarily higher or lower than the correct value after the fill is complete. In this case, using the LSTRewardsSplitter to split this asset would not be safe, because the path dependency of the splitRewards logic will let anyone cause more or less principal to be distributed than originally intended.

Zellic © 2025Back to top ↑