Summary (a known-winning shot can be held open to snipe a bigger pot)
revealShot() can legally be called any time from commitBlock + REVEAL_DELAY + 1 up to commitBlock + MAX_REVEAL_DELAY (up to 256 blocks later — contracts/EthShot.sol:141,378). The win/lose outcome is fully determined the moment blockhash(commitBlock + REVEAL_DELAY) exists, since every other input to _checkWin (the player's own secret, nonce, playerNonces[msg.sender], msg.sender) is either already known to the caller or is plain contract storage — private in Solidity only blocks other contracts from reading it, it does not hide it from off-chain inspection (eth_getStorageAt), and the whole computation can trivially be replicated off-chain / via a local eth_call simulation before ever broadcasting the real transaction.
So a player can:
commitShot
- Wait until
blockhash(commitBlock+1) exists (2 blocks)
- Simulate
revealShot(secret) off-chain (no gas, no state change) to see whether it wins
- If it wins, do nothing yet — just leave the pending shot open
- Wait while other players commit shots and inflate
currentPot (this doesn't touch nonce, so the already-determined win outcome doesn't change as long as nobody else reveals in the meantime — plausible in a low-traffic period)
- Call
revealShot right before the 256-block window closes, when the pot is much larger than it was at step 1
_handleWin (contracts/EthShot.sol:614 in the current code) pays out WIN_PERCENTAGE_BP of the live currentPot at the moment of the call, with no cap tied to what the pot was when the win was actually determined. So this lets a player extract value contributed by other players' shots that were committed after the winning outcome was already locked in — those later players never had a fair chance at that portion of the pot.
Note: PR #5 ("Auto-reveal pending shots") only added a client-side bot that reveals promptly for well-behaved users of the official frontend. It doesn't change the contract, so it does nothing to stop someone calling the contract directly (their own script, not the website) from holding a known winner open.
Impact
A sufficiently patient/automated player can systematically extract a larger-than-fair share of the pot at the expense of players who committed shots after the outcome was already secretly known to be a win, without the contract enforcing that the payout reflects the pot as of when randomness was actually fixed.
Fix (PR opened)
Snapshot the pot size right after each shot's own commit (potAtCommit) and cap the eventual payout to min(currentPot, potAtCommit). Any pot growth from shots committed after the winner's own commit stays in currentPot and rolls over to the next round instead of being awarded to a winner who could have strategically delayed their reveal.
Summary (a known-winning shot can be held open to snipe a bigger pot)
revealShot()can legally be called any time fromcommitBlock + REVEAL_DELAY + 1up tocommitBlock + MAX_REVEAL_DELAY(up to 256 blocks later —contracts/EthShot.sol:141,378). The win/lose outcome is fully determined the momentblockhash(commitBlock + REVEAL_DELAY)exists, since every other input to_checkWin(the player's ownsecret,nonce,playerNonces[msg.sender],msg.sender) is either already known to the caller or is plain contract storage —privatein Solidity only blocks other contracts from reading it, it does not hide it from off-chain inspection (eth_getStorageAt), and the whole computation can trivially be replicated off-chain / via a localeth_callsimulation before ever broadcasting the real transaction.So a player can:
commitShotblockhash(commitBlock+1)exists (2 blocks)revealShot(secret)off-chain (no gas, no state change) to see whether it winscurrentPot(this doesn't touchnonce, so the already-determined win outcome doesn't change as long as nobody else reveals in the meantime — plausible in a low-traffic period)revealShotright before the 256-block window closes, when the pot is much larger than it was at step 1_handleWin(contracts/EthShot.sol:614in the current code) pays outWIN_PERCENTAGE_BPof the livecurrentPotat the moment of the call, with no cap tied to what the pot was when the win was actually determined. So this lets a player extract value contributed by other players' shots that were committed after the winning outcome was already locked in — those later players never had a fair chance at that portion of the pot.Note:
PR #5("Auto-reveal pending shots") only added a client-side bot that reveals promptly for well-behaved users of the official frontend. It doesn't change the contract, so it does nothing to stop someone calling the contract directly (their own script, not the website) from holding a known winner open.Impact
A sufficiently patient/automated player can systematically extract a larger-than-fair share of the pot at the expense of players who committed shots after the outcome was already secretly known to be a win, without the contract enforcing that the payout reflects the pot as of when randomness was actually fixed.
Fix (PR opened)
Snapshot the pot size right after each shot's own commit (
potAtCommit) and cap the eventual payout tomin(currentPot, potAtCommit). Any pot growth from shots committed after the winner's own commit stays incurrentPotand rolls over to the next round instead of being awarded to a winner who could have strategically delayed their reveal.