A Denial of Service (DoS) vulnerability exists in the withdrawFees function of the RockPaperScissors contract. A malicious or careless admin can set the adminAddress (via setAdmin) to a contract designed to revert upon receiving Ether (e.g., through its receive/fallback function). When withdrawFees is subsequently called, the low-level .call{value: ...} to the reverting admin address fails. This failure causes the require(success, "Fee withdrawal failed") check within withdrawFees to trigger, reverting the entire withdrawal transaction. Consequently, the accumulated protocol fees become inaccessible and cannot be withdrawn by the currently configured admin address, effectively locking them from that specific admin. While the owner might be able to recover by setting a new admin, this presents a griefing vector for the admin role.
The vulnerability exists within the withdrawFees() function located in src/RockPaperScissors.sol. This function is responsible for transferring accumulated protocol fees to the address stored in the adminAddress state variable.
The function utilizes a low-level external call for the ETH transfer and checks for its success:
The adminAddress can be updated via the setAdmin() function, typically callable by the current admin or potentially a separate owner role.
The vulnerability arises because an admin can intentionally or accidentally set the adminAddress to point to a contract whose receive() or fallback() function prevents the successful completion of the .call. This occurs if the receiving contract:
Explicitly reverts (e.g., receive() external payable { revert("Blocked"); }).
Consumes all forwarded gas (less likely with default .call behavior but possible).
If the external .call to adminAddress returns success = false due to such a revert or gas issue in the receiver, the require(success, "Fee withdrawal failed") check within withdrawFees() will trigger. This causes the entire withdrawFees transaction to revert.
As a result, if the adminAddress is set to such a non-cooperative contract, the withdrawFees function becomes unusable for withdrawing fees to that specific address. Any attempt to call withdrawFees will fail, preventing the extraction of accumulated fees via this mechanism as long as that address remains the admin.
PoC:
PoC Result:
The direct impact of this vulnerability is a Denial of Service (DoS) on the fee withdrawal mechanism. If the adminAddress is set to a contract that reverts upon receiving ETH, the withdrawFees function will consistently fail, preventing the withdrawal of any accumulated protocol fees by that admin.
Temporarily Locked Fees: The fees remain locked in the contract and inaccessible via the withdrawFees function as long as the adminAddress points to the non-cooperative contract.
Admin Griefing: A malicious admin could exploit this to deliberately prevent fee withdrawals. Alternatively, a careless admin could accidentally achieve the same result by setting an incompatible contract address.
Owner Recovery (Mitigation): The overall impact is significantly mitigated if the contract owner (the address that initially deployed the RockPaperScissors contract, assuming ownership wasn't transferred and the owner role retains setAdmin capability) can call setAdmin. The owner could appoint a new, valid adminAddress, thereby restoring the functionality of withdrawFees and allowing the accumulated fees to eventually be recovered. If owner recovery is not possible, the fees would be permanently locked from withdrawal via this function.
No Direct User Impact: This vulnerability primarily affects the protocol's ability to collect its fees via the designated admin and does not directly endanger player funds or game integrity.
Manuel code rview
Forge Foundry
The primary issue causing the Denial of Service is that a failure in the external ETH transfer to the adminAddress causes the entire withdrawFees function to revert due to the require(success, ...) check. To mitigate this specific DoS vector:
Consider Removing the require(success) Check in withdrawFees:
Modify the withdrawFees function by removing the line:
Effect: If the .call to adminAddress fails (returns success = false), the withdrawFees function will no longer revert. The state change (accumulatedFees -= amountToWithdraw) will still persist, but the ETH transfer for that specific attempt will simply fail silently (or with an event if added). The fees remain in the contract, available for withdrawal in a subsequent transaction (e.g., after the owner assigns a valid adminAddress).
Rationale: This approach places the responsibility for providing a valid, payable address on the admin. If they provide an address that cannot receive ETH, the transfer fails, but it doesn't block the function execution or state updates. This prevents the DoS on the function call itself.
2.Ensure Robust Owner Recovery for setAdmin:
Verify that the setAdmin function includes necessary access control allowing a designated owner role (potentially separate from the operational adminAddress) to forcibly change the adminAddress if the current one becomes unusable or is set maliciously. This provides a crucial recovery path to restore fee withdrawal functionality.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.