Reproduced Exploit

Enjoyoors — rebase rewards sit above userSupply and cannot be withdrawn

The vault remembers the token amount it was given, not a share of the tokens it holds. A rebasing token can increase the vault's balance without a deposit. withdraw refuses any amount above userSupply, so the rebase cannot be paid out.

Dec 2024•Ethereum•dos•3 min read

Chain

Ethereum

Category

dos

Date

Dec 2024

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: 49876-stuck-rebase-rewards-due-to-internal-balance-tracking-mixbyt. Standalone Foundry PoC and full write-up: 49876-stuck-rebase-rewards-due-to-internal-balance-tracking-mixbyt_exp in the evm-hack-registry mirror.


Vulnerability classes: vuln/dos/frozen-funds · vuln/logic/reward-calculation

Key info#

ProtocolEnjoyoors
FindingMixBytes, December 2024, AuditVault 49876
Vulnerable code_deposit does userSupply[token][msg.sender] += amount
HarmA 100 deposit rebases by 25. Withdraw pays 100. The 25 stays in the vault. Share accounting pays 125.
Chainlocal synthetic (audited Ethereum contracts)

TL;DR#

The vault remembers the token amount it was given, not a share of the tokens it holds. A rebasing token can increase the vault's balance without a deposit. withdraw refuses any amount above userSupply, so the rebase cannot be paid out.

Deposit 100. Rebase 25 onto the vault. Cash is 125 and userSupply is 100. Withdraw of 100 succeeds and 25 remains. There is no later function that pays the residual. Share accounting, shares * cash / totalShares, pays 125 and leaves 0.

The vulnerable code#

The audited assignment is userSupply[token][msg.sender] += amount.

SOLIDITY
token.transferFrom(msg.sender, address(this), amount);
totalSupply[token_] += amount;
// @> VULN: userSupply[token][msg.sender] += amount ignores later rebases.
userSupply[token_][msg.sender] += amount;

The share withdrawal the report recommends:

SOLIDITY
paid = shares * cash / all;

Root cause#

userSupply is a sum of deposit amounts. The token balance is not that sum once the token rebases. The withdrawal ceiling is the sum, so the extra balance is unowned.

Preconditions#

The listed token rebases, the way stETH or an aToken does. This run uses an explicit rebase of 25 on a 100 deposit so the gap does not depend on a second block. No warp.

Attack walkthrough#

  1. Deposit 100. userSupply becomes 100.
  2. Rebase 25 onto the vault. Cash is 125. userSupply is still 100.
  3. Withdraw 100, the maximum the mapping allows.
  4. The depositor received 100. The vault still holds 25.
  5. The share payout of the same position is 125.

Diagrams#

flowchart TD A["Deposit 100"] --> B["userSupply stores 100"] B --> C["Token rebases 25 onto the vault"] C --> D["VULN: withdraw ceiling stays 100"] D --> E["25 remains in the vault"] E --> F["Share accounting pays 125"]

Impact#

The depositor cannot withdraw the 25 rebase units. They stay in the vault with no claim path. The share-based withdrawal pays 125 and leaves nothing behind.

Remediation#

Track shares of the vault's token balance, and pay shares * balance / totalShares on the way out. Or refuse rebasing tokens and use the wrapped asset.

How to reproduce#

BASH
cd 49876-stuck-rebase-rewards-due-to-internal-balance-tracking-mixbyt_exp
forge test -vv

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.