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