Last Man Standing

First Flight #45
Beginner FriendlyFoundrySolidity
100 EXP
View results
Submission Details
Impact: low
Likelihood: low
Invalid

[L-2] `Game::receive()` is empty and untracked

Root Cause

The contract defines a Game::receive() function but leaves it empty, without emitting an event or implementing logic to handle incoming ETH. This means that any ETH sent directly to the contract (outside designated game functions) will be accepted silently, without any trace in the contract logs.


Description

This allows the contract to accept plain ETH transfers (e.g., send(), transfer(), .call{value:}), but currently:

There is no event emitted to track who sent ETH and how much.

There is no logic in place to handle unexpected ETH transfers (e.g., refund or routing).

This can create confusion during auditing or operational monitoring, especially if someone mistakenly or maliciously sends ETH directly.

receive() external payable {}

Impact

  • This can reduce transparency and create ambiguity during audits, monitoring, or debugging. If ETH is not meant to be sent directly, this may become an unintended ETH sink. Although it doesn’t break core game logic, it introduces unnecessary surface area and reduces visibility into user interactions.


Proof of Concept (PoC)

This test simulates a scenario where a user (or bot) sends ETH directly to the contract using a low-level .call{value:}.

Since the Game::receive() function is present but empty, the ETH is accepted without any event emitted or state updated.

function test_SendingEthDirectToTheContract() public {
// Arrange
uint256 gameContractBalanceBefore = address(game).balance;
// Act: maliciousActor sends ETH directly to the contract
vm.startPrank(maliciousActor);
(bool success, ) = address(game).call{value: 1 ether}("");
require(success, "Tx Failed");
uint256 gameContractBalanceAfter = address(game).balance;
// Assert: Contract balance increased, proving ETH was accepted silently
assertGt(gameContractBalanceAfter, gameContractBalanceBefore, "ETH was not received");
}

Recommended Mitigation:

Option-1: If you want to track incoming ETH, emit an event:

event ETHReceived(address indexed sender, uint256 amount);
receive() external payable {
emit ETHReceived(msg.sender, msg.value);
}

Option-2: If you want to restrict ETH, you can revert:

receive() external payable {
revert("Direct ETH not allowed");
}

Severity: Low

  • Transparency: No way to know if ETH was sent directly unless node logs are inspected.

  • Maintainability: Future developers/auditors may wonder why ETH was accepted but not handled.

  • Security: Could be an unintended attack surface if ETH is not expected (e.g., griefing contract with dust ETH to bloat balance).

Updates

Appeal created

inallhonesty Lead Judge 10 days ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Direct ETH transfers - User mistake

There is no reason for a user to directly send ETH or anything to this contract. Basic user mistake, info, invalid according to CH Docs.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.