Reproduced Exploit

Souls.club — staking during the cooldown flag wins 100 PRIZE the attacker should not

The attacker joins with stake 1. The victim joins with stake 100. Both seats are filled, so cooldown becomes true in this block. The attacker then adds 1,000. Stake is now 1,001.

Nov 2025•Other•logic•3 min read

Chain

Other

Category

logic

Date

Nov 2025

Source

AuditVault

EVM Playground

Source-level debugger — step opcodes and Solidity in sync

evm-hack-analyzer

The attack is replayed in an in-browser EVM preloaded with the exact dumped fork state. The execution tree shows every call; step by Solidity line or by opcode across all depths — source, Stack, Memory, Storage, Balances (native / ERC-20 / NFT), Transient storage and Return value stay in sync. Click a tree node, opcode, or source line to jump. No backend, no live RPC.

Loading fork state…

Source & credit. Reproduction of a public audit finding curated by AuditVault — the original finding: 64739-h-01-stake-manipulation-during-cooldown-period-allows-probab. Standalone Foundry PoC and full write-up: 64739-h-01-stake-manipulation-during-cooldown-period-allows-probab_exp in the evm-hack-registry mirror.


Vulnerability classes: vuln/logic/reward-calculation · vuln/logic/wrong-condition

Reproduction: a self-contained Foundry PoC that compiles and runs offline with only forge-std — no fork and no RPC. Trace: output.txt. PoC: test/64739-h-01-stake-manipulation-during-cooldown-period-allows-probab_exp.sol.


Key info#

ImpactHIGH — the attacker wins 100 PRIZE on roll 50 after raising stake from 1 to 1,001 during cooldown
ProtocolSouls.club Wheel
Vulnerable codejoinDuringCooldown adds to stakeA while cooldown is already true
Bug classStake changes the odds after the round has locked
FindingShieldify — Souls.club Wheel, November 2025 · id 64739, H-01
ReportSouls.club Wheel review
SourceAuditVault finding
StatusReproduced here as a local synthetic. Cooldown is a flag. There is no warp.
Compilersolc 0.8.27, cancun, via IR

TL;DR#

The attacker joins with stake 1. The victim joins with stake 100. Both seats are filled, so cooldown becomes true in this block. The attacker then adds 1,000. Stake is now 1,001.

The wheel walks the attacker first. Roll 50 is not less than 1, so the frozen stake would lose. It is less than 1,001, so the boosted stake wins. The attacker receives 100 PRIZE.

Control reverts the extra stake. Stake stays 1. The same roll selects the victim. The attacker receives 0 and the prize stays in the wheel.

The vulnerable code#

SOLIDITY
function joinDuringCooldown(uint256 amount) external {
    require(cooldown, "cooldown");
    // @> VULN: cooldown is already set and the attacker stake still grows
    stakeA += amount;
}

The fix reverts instead of adding the amount.

Root cause#

joinGame keeps adding to stakeByPlayer after the session has activated. The winner is stake-weighted, so a deposit during that window changes who roll 50 hits.

Preconditions#

  • Attacker stake starts at 1 and victim stake is 100. The attacker is the first player in the walk.
  • cooldown is the flag set when both stakes are nonzero. The roll is the constant 50.
  • The wheel holds 100 PRIZE.

Attack walkthrough#

  1. Fund 100 PRIZE. Attacker joins with 1. Victim joins with 100. cooldown becomes true.
  2. joinDuringCooldown(1000) adds to stakeA. It is now 1001.
  3. spin uses roll 50. 50 < 1001 selects the attacker. 50 < 1 would have selected the victim.
  4. The wheel transfers 100 PRIZE to the attacker.

Control reverts joinDuringCooldown. The spin pays nobody on the attacker path. The winner is the victim. The prize balance of the wheel stays 100.

Diagrams#

flowchart TD A["Attacker stake is 1 and victim stake is 100"] --> B["Cooldown flag becomes true"] B --> C["Attacker adds 1000 while the flag is set"] C --> D["Roll 50 is now inside the attacker stake"] D --> E["Attacker receives 100 PRIZE"]

Impact#

The attacker wins 100 PRIZE that roll 50 would have given to the victim. The victim's 100 stake is no longer the winning range.

Remediation#

Reject stake increases once cooldown is true. Freeze the stakes that the roll is measured against.

Sources#

Taxonomy#

  • severity/high
  • sector/gaming
  • platform/shieldify

Sources & further analysis#

Reproductions & code

Alerts & third-party analyses

  • Web3Sec X hacked database: search.
  • Rekt leaderboard: search.
  • Solodit incident search: search.

These dashboards index community alerts tweets, post-mortems, and independent write-ups. Reach them through the protocol name above to cross-check this reproduction against other analyses.