Reproduced Exploit

BOB Staking — Delegating to address(0) empties contract via alterGovernanceDelegatee

1. First non-zero delegation moves the staker's tokens contract → surrogate. 2. Setting delegatee to address(0) is allowed; tokens move to a zero surrogate. 3. Re-delegating to a non-zero address sees governanceDelegatee == 0 and treats it as a first delegation. 4. The contract transfers amountStak…

Oct 2025Otherlogic3 min read

Chain

Other

Category

logic

Date

Oct 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: 63720-h-02-delegating-to-address0-empties-contract-via-altergovern. Standalone Foundry PoC and full write-up: 63720-h-02-delegating-to-address0-empties-contract-via-altergovern_exp in the evm-hack-registry mirror.


Vulnerability classes: vuln/logic/missing-check · direct-drain · vote-delegation-loop

Reproduction: a self-contained Foundry PoC that compiles & runs in an isolated project with only forge-std — no fork, no RPC, no anvil_state. Full trace: output.txt. PoC: test/63720-h-02-delegating-to-address0-empties-contract-via-altergovern_exp.sol.

AuditVault taxonomy: severity/high · sector/staking · sector/governance · platform/pashov · missing · direct-drain · reward-accounting · vote-delegation-loop


Key info#

ImpactHIGH — attacker drains staking-contract balance (rewards / other stakes)
ProtocolBOB Staking
Vulnerable codeBobStaking.alterGovernanceDelegatee — allows address(0) and re-pulls from contract
Bug classMissing zero-address reject + wrong "first delegation" branch after undelegate
FindingPashov BOB-Staking security review 2025-10-18 · #63720
ReportPashov BOB-Staking review
SourceAuditVault
StatusAudit finding — resolved per report (disable address(0)). Reproduced as a reduced local synthetic.
Compiler^0.8.24 (PoC)

TL;DR#

  1. First non-zero delegation moves the staker's tokens contract → surrogate.
  2. Setting delegatee to address(0) is allowed; tokens move to a zero surrogate.
  3. Re-delegating to a non-zero address sees governanceDelegatee == 0 and treats it as a first delegation.
  4. The contract transfers amountStaked from its own balance (rewards) into the new surrogate — draining the vault. Repeat to empty it.

The vulnerable code#

SOLIDITY
function alterGovernanceDelegatee(address newDelegatee) external {
    // FIX: revert if newDelegatee == address(0)
    DelegationSurrogate newSurrogate = _fetchOrDeploySurrogate(newDelegatee);

    if (staker.governanceDelegatee == address(0)) { // @> VULN after prior 0-delegate
        IERC20(stakingToken).safeTransfer(address(newSurrogate), staker.amountStaked);
    } else {
        // move between surrogates
    }
    staker.governanceDelegatee = newDelegatee;
}

Root cause#

address(0) is both the "not yet delegated" sentinel and an allowed target. After a real undelegate-to-zero, the user's tokens sit in the zero surrogate, but the next re-delegate reuses the first-time path that pulls from the main contract.

Preconditions#

  • Attacker has non-zero stake.
  • At least one whitelisted non-zero delegatee.
  • Contract holds extra tokens (rewards or other stakers) ≥ amountStaked per cycle.

Attack walkthrough#

  1. Deposit 1000e18 rewards; attacker stakes 1e18.
  2. Delegate to a whitelisted address (stake leaves contract).
  3. Delegate to address(0) (tokens → zero surrogate).
  4. Delegate again to the non-zero address → pulls 1e18 from rewards into the surrogate.
  5. Contract rewards fall from 1000 → 999; repeat to empty.

Diagrams#

flowchart TD A["Stake + rewards in contract"] --> B["Delegate to D1"] B --> C["Tokens in D1 surrogate"] C --> D["Delegate to address 0"] D --> E["Tokens in zero surrogate"] E --> F["Re-delegate to D1"] F --> G["VULN: first-time path pulls from contract"] G --> H["Rewards drained into surrogate"]

Impact#

Any staker can siphon reward inventory (and eventually other users' undelegated stake) by cycling D → 0 → D. Full vault empty is possible with enough cycles.

Sources#


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.