Ancient Vanilla Dog
Medium
In the loop of batchRefund
, each iteration processes a participation ID. The loop uses calldata for the array, which is efficient. But since it's loop over an array provided by the caller, there's a potential risk of gas limits if the array is too large. For example, if the array has hundreds or thousands of entries, the transaction could run out of gas and revert. This prevents any withdrawals, even when all launch groups are legitimately completed.
https://github.com/sherlock-audit/2025-02-rova/blob/main/rova-contracts/src/Launch.sol#L509
@>> for (uint256 i = 0; i < launchParticipationIds.length; i++) { // unbounded loops
@>> ParticipationInfo storage info = launchGroupParticipations[launchParticipationIds[i]];
_processRefund(launchGroupId, launchParticipationIds[i], info);
}
Now looking at how the ParticipationInfo
is retrieved. The code uses storage for the info struct. So each iteration accesses storage directly. Since storage operations are expensive, this could contribute to high gas costs, especially in large batches which can make the transacton reverts.
No response
No responseNo resp
No response
Funds become permanently stuck in the contract as withdrawals are blocked when all launch group are completed.
No response
Implement a maximum batch size.
+ uint256 MAX_BATCH_SIZE = 100;
function batchRefund(bytes32 launchGroupId, bytes32[] calldata launchParticipationIds)
external
onlyRole(OPERATOR_ROLE)
nonReentrant
whenNotPaused
onlyLaunchGroupStatus(launchGroupId, LaunchGroupStatus.COMPLETED)
{
+ require(launchParticipationIds.length <= MAX_BATCH_SIZE, "Batch too large"
for (uint256 i = 0; i < launchParticipationIds.length; i++) {
ParticipationInfo storage info = launchGroupParticipations[launchParticipationIds[i]];
_processRefund(launchGroupId, launchParticipationIds[i], info);
}
}