# Protocol insolvent - Permanent freeze of funds

Cross-function reentrancy in withdrawETHForKnot lets a node runner stake a banned BLS key, permanently freezing savETH and MEV vault depositor funds.

- Payout: Contest reward
- Severity: high
- Program: Stakehouse Protocol
- Credited: 0xdeadbeef0x
- Disclosed: 2023-01-20
- Write-up quality: 8/10
- Original: https://github.com/code-423n4/2022-11-stakehouse-findings/issues/176
- Library page: https://bugbountylibrary.com/r/c4%3A2022-11-stakehouse%3AH-11
- Tags: domain:web3 impact:dos impact:funds-loss kind:contest platform:code4rena sev:high tech:evm tech:solidity technique:chained technique:source-review vuln:accounting-error vuln:business-logic vuln:reentrancy

**Target:** LiquidStakingManager.sol (withdrawETHForKnot / stake / _assertEtherIsReadyForValidatorStaking)
**Root cause:** withdrawETHForKnot sends 4 ETH to the node runner's smart wallet before setting bannedBLSPublicKeys, and _assertEtherIsReadyForValidatorStaking only checks the shared smart wallet balance (>=4 ETH) rather than per-key accounting, so a second key's deposit satisfies the check for the key being withdrawn.
**Steps:**
1. Node runner registers BLS key #1 and #2, paying 4 ETH each (8 ETH in shared smart wallet).
2. savETH users deposit 24 ETH and MEV/fees users deposit 4 ETH for key #1.
3. Node runner calls `withdrawETHForKnot(nodeRunner, blsPubKeyOne)`; smart wallet receives 4 ETH.
4. In `receive()`, node runner reenters `stake([blsPubKeyOne], ...)` while key #1 is not yet banned and status is INITIALS_REGISTERED.
5. `_assertEtherIsReadyForValidatorStaking` passes because smart wallet still holds 4 ETH from key #2; `_stake` pulls 24 ETH from savETH vault and 4 ETH from staking funds vault, registers validator, sets status DEPOSIT_COMPLETE.
6. Control returns to withdrawETHForKnot which sets `bannedBLSPublicKeys[blsPubKeyOne]`; key is now banned AND DEPOSIT_COMPLETE.
7. `mintDerivatives` reverts (banned), `burnLPToken` reverts (status DEPOSIT_COMPLETE), `rotateLPTokens` reverts (status not INITIALS_REGISTERED) — funds permanently frozen.
**How found:** Code review of withdrawETHForKnot noticed the external ETH transfer precedes the bannedBLSPublicKeys write (CEI violation), and that _assertEtherIsReadyForValidatorStaking checks only the shared smart wallet balance rather than per-key deposits — enabling a cross-function reentrancy into stake.
**Exploitation:** Node runner needs two registered BLS keys (8 ETH) and victim vault deposits (24 ETH savETH + 4 ETH staking funds) on key #1. Calls withdrawETHForKnot, reenters stake from the ETH-receiving fallback, causing the protocol to stake key #1 with vault funds while simultaneously banning it, leaving depositors unable to mint derivatives, burn LP tokens, or rotate.
**Escalation:** Chaining the reentrancy with the shared-wallet balance check turns a refund into a permanent protocol-wide insolvency affecting all savETH and MEV vault depositors.
**Insight:** When a contract shares one smart wallet across multiple logical positions, balance checks like `smartWallet.balance >= 4 ether` are not per-position accounting; look for state writes (ban flags, status) that occur after external transfers and reenter any function gated only by those flags.
