Summary
Chainlink automation Upkeep can not work properly because of wrong integration in contracts PriorityPool, WithdrawalPool
Vulnerability Details
From Chainlink's docs, the function checkUpkeep(bytes calldata checkData) external view override returns (bool upkeepNeeded, bytes memory performData). In case upkeepNeeded returned true, then the performData is used as input for function performUpkeep(bytes calldata performData).
Chainlink upkeep integration from contract PriorityPool: The function checkUpkeep() returns a abi-encoded of an uint256, when the function performUpkeep() tries to decode the input to bytes[]. With the returned values from checkUpkeep(), the function performUpkeep() will always revert.
function checkUpkeep(bytes calldata) external view returns (bool, bytes memory) {
uint256 strategyDepositRoom = stakingPool.getStrategyDepositRoom();
uint256 unusedDeposits = stakingPool.getUnusedDeposits();
if (poolStatus != PoolStatus.OPEN) return (false, "");
if (
strategyDepositRoom < queueDepositMin ||
(totalQueued + unusedDeposits) < queueDepositMin
) return (false, "");
return (
true,
@> abi.encode(
MathUpgradeable.min(
MathUpgradeable.min(strategyDepositRoom, totalQueued + unusedDeposits),
queueDepositMax
)
)
);
}
function performUpkeep(bytes calldata _performData) external {
@> bytes[] memory depositData = abi.decode(_performData, (bytes[]));
_depositQueuedTokens(queueDepositMin, queueDepositMax, depositData);
}
Similarly, the integration in contract WithdrawalPool is improper such that: checkUpkeep() returns empty data if upkeep is needed, when the function performUpkeep() tries to decode input to bytes[] which will always revert
function checkUpkeep(bytes calldata) external view returns (bool, bytes memory) {
if (
_getStakeByShares(totalQueuedShareWithdrawals) != 0 &&
priorityPool.canWithdraw(address(this), 0) != 0 &&
block.timestamp > timeOfLastWithdrawal + minTimeBetweenWithdrawals
) {
@> return (true, "");
}
return (false, "");
}
function performUpkeep(bytes calldata _performData) external {
uint256 canWithdraw = priorityPool.canWithdraw(address(this), 0);
uint256 totalQueued = _getStakeByShares(totalQueuedShareWithdrawals);
if (
totalQueued == 0 ||
canWithdraw == 0 ||
block.timestamp <= timeOfLastWithdrawal + minTimeBetweenWithdrawals
) revert NoUpkeepNeeded();
timeOfLastWithdrawal = uint64(block.timestamp);
uint256 toWithdraw = totalQueued > canWithdraw ? canWithdraw : totalQueued;
@> bytes[] memory data = abi.decode(_performData, (bytes[]));
priorityPool.executeQueuedWithdrawals(toWithdraw, data);
_finalizeWithdrawals(toWithdraw);
}
Impact
Automation tasks can not be done by Upkeep
Tools Used
Manual
Recommendations
Update integration to return and decode data properly