Liquid Staking

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

Malicious actors can grief users calling to `performUpkeep` via front-running `splitRewards`, because all splitters' `performUpkeep` transaction reverted even if only one splitter's reward is zero.

Summary

LSTRewardsSplitterController contract has performUpkeep function that upkeeps splitters after checks upkeep-needed splitters.

performUpkeep` function has iteration for all upkeep-needed splitters, and it reverts if there are at least one splitter no needed to upkeep.

Therefore, after checkUpkeep , calling splitRewards reverts all performUpkeep transaction.

Vulnerability Details

LSTRewardsSplitterController#performUpkeep function can be reverted when any splitter hasn't sufficient rewards to split.

function performUpkeep(bytes calldata _performData) external {
bool[] memory splittersToCall = abi.decode(_performData, (bool[]));
bool splitterCalled;
@> for (uint256 i = 0; i < splittersToCall.length; ++i) {
if (splittersToCall[i] == true) {
@> splitters[accounts[i]].performUpkeep("");
splitterCalled = true;
}
}
if (splitterCalled == false) {
revert InvalidPerformData();
}
}

LSTRewardsSplitter#performUpkeep

function performUpkeep(bytes calldata) external {
int256 newRewards = int256(lst.balanceOf(address(this))) - int256(principalDeposits);
if (newRewards < 0) {
principalDeposits -= uint256(-1 * newRewards);
} else if (uint256(newRewards) < controller.rewardThreshold()) {
@> revert InsufficientRewards();
} else {
_splitRewards(uint256(newRewards));
}
}

splitterRewards function splits new rewards between fee receivers, so newRewards will be zero right after this call.

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));
}
}

POC

it('griefing attack to performUpkeep', async () => {
const { controller, token, splitter0, splitter1 } = await loadFixture(deployFixture)
await token.transfer(splitter0.target, toEther(90))
await token.transfer(splitter1.target, toEther(80))
assert.deepEqual(await controller.checkUpkeep('0x'), [
false,
ethers.AbiCoder.defaultAbiCoder().encode(['bool[]'], [[false, false]]),
])
await token.transfer(splitter1.target, toEther(20))
assert.deepEqual(await controller.checkUpkeep('0x'), [
true,
ethers.AbiCoder.defaultAbiCoder().encode(['bool[]'], [[false, true]]),
])
await token.transfer(splitter0.target, toEther(10))
assert.deepEqual(await controller.checkUpkeep('0x'), [
true,
ethers.AbiCoder.defaultAbiCoder().encode(['bool[]'], [[true, true]]),
])
await splitter1.splitRewards();
await controller.performUpkeep(
ethers.AbiCoder.defaultAbiCoder().encode(['bool[]'], [[true, true]])
)
assert.deepEqual(await controller.checkUpkeep('0x'), [
false,
ethers.AbiCoder.defaultAbiCoder().encode(['bool[]'], [[false, false]]),
])
})
  • Test command

npx hardhat test test/core/lst-rewards-splitter.test.ts --grep "griefing attack to performUpkeep" --network hardhat
  • Test Output

LSTRewardsSplitter
1) griefing attack to performUpkeep
0 passing (3s)
1 failing
1) LSTRewardsSplitter
griefing attack to performUpkeep:
Error: VM Exception while processing transaction: reverted with custom error 'InsufficientRewards()'
at LSTRewardsSplitter.performUpkeep (contracts/core/lstRewardsSplitter/LSTRewardsSplitter.sol:106)
at LSTRewardsSplitterController.performUpkeep (contracts/core/lstRewardsSplitter/LSTRewardsSplitterController.sol:99)
at EdrProviderWrapper.request (node_modules\hardhat\src\internal\hardhat-network\provider\provider.ts:446:41)
at async HardhatEthersSigner.sendTransaction (node_modules\@nomicfoundation\hardhat-ethers\src\signers.ts:125:18)
at async send (node_modules\ethers\src.ts\contract\contract.ts:313:20)
at async Proxy.performUpkeep (node_modules\ethers\src.ts\contract\contract.ts:352:16)
at async Context.<anonymous> (test\core\lst-rewards-splitter.test.ts:152:5)

Impact

Malicious actors can grief keepers to upkeep by front-running splitRewards for only one splitter, and it can be repeated several times, so protocol reputation can be reduced.

Tools Used

Manual Review

Recommendations

Don't revert if any splitter hasn't sufficient rewards or use custom error handling on LSTRewardsSplitterController#performUpkeep

Updates

Lead Judging Commences

inallhonesty Lead Judge 12 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Appeal created

0x1912 Submitter
12 months ago
inallhonesty Lead Judge
12 months ago
inallhonesty Lead Judge 12 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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