Missing message validation may allow griefing
Description
The finalizeWithdrawals
function does not check that the given message corresponds to an existing withdrawal request. Since the uninitialized values of the corresponding withdrawal data will be zero, the call to checkDisputePeriod
will pass:
function checkDisputePeriod(uint64 time, uint64 blockNumber) private view {
require(
block.timestamp > time + disputePeriodSeconds &&
(uint64(block.number) - blockNumber) * blockDurationMillis > 1000 * disputePeriodSeconds,
"Still in dispute period"
);
}
Impact
When messages do not correspond to existing withdrawals, they will cause a transfer of zero tokens to the zero address. In the case of USDC on Arbitrum, this will currently result in a revert. However, if this logic is reused for other ERC-20 tokens, there is no guarantee that such a call will be blocked.
Then, although the message does not correspond to an existing withdrawal, it will be marked as finalized, anyway:
finalizedWithdrawals[message] = true;
usdcToken.transfer(withdrawal.user, withdrawal.usdc);
Thus, any future attempts to finalize that message will fail. If an attacker is able to
predict upcoming nonces, or
front-run withdrawal requests,
they would be able to block real withdrawals from being finalized.
Recommendations
Consider checking that messages correspond to existing withdrawals during the finalization process. In the case of USDC, this has the additional benefit of improving the error message.
Remediation
This issue has been acknowledged by Hyperliquid, and a fix was implemented in commit 1c8d3333↗.