Reproduced Exploit
Stakehouse Protocol — transferring GiantMevAndFeesPool tokens leaves claimed[] high, DoS-ing the sender and orphaning future rewards
1. claimed[user][token] records how much a user has already been paid for their LP. 2. On GiantLP transfer, rewards are settled for the sender, balances move, then afterTokenTransfer sets claimed[to] = max so the recipient does not inherit past rewards.
Chain
Other
Category
logic
Date
Nov 2022
Source
AuditVault
EVM Playground
Source-level debugger — step opcodes and Solidity in sync
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.
Source & credit. Reproduction of a public audit finding curated by AuditVault — the original finding: 43034-h-12-sender-transferring-giantmevandfeespool-tokens-can-afte. Standalone Foundry PoC and full write-up: 43034-h-12-sender-transferring-giantmevandfeespool-tokens-can-afte_exp in the
evm-hack-registrymirror.
Vulnerability classes: vuln/logic/reward-calculation · vuln/logic/dos-resistance · misassumption/math-is-safe
Reproduction: a self-contained Foundry PoC that compiles & runs in an isolated project with only
forge-std— no fork, no RPC, noanvil_state. Full trace: output.txt. PoC: test/43034-h-12-sender-transferring-giantmevandfeespool-tokens-can-afte_exp.sol.
AuditVault taxonomy: lang/solidity · sector/staking · sector/token · platform/code4rena · has/github · has/poc · severity/high · novelty/known-pattern · genome: reward-calculation · known-pattern · reward-theft · dos-resistance · integer-bounds · frontrun-exposure · reward-accounting · timestamp-dependence
Key info#
| Impact | HIGH — after transferring away part of their GiantLP, a user can no longer transfer/claim/preview (underflow DoS); later rewards owed to them are permanently orphaned in the pool |
| Protocol | Stakehouse Protocol — GiantMevAndFeesPool / GiantLP transfer hooks |
| Vulnerable code | GiantMevAndFeesPool.afterTokenTransfer — adjusts claimed[to] but never reduces claimed[from] |
| Bug class | Incomplete transfer accounting for cumulative claimed ledger |
| Finding | Code4rena — Stakehouse Protocol, 2022-11 · #43034 (H-12) · reporter JTJabba |
| Report | 2022-11-stakehouse |
| Source | AuditVault |
| Status | Audit finding — confirmed by the Stakehouse team. Reproduced here as a standalone local PoC. |
| Compiler | ^0.8.24 (PoC); real code ^0.8.13 |
This is an audit finding, not a historical on-chain incident. The PoC keeps the after-transfer claimed[] policy (only recipient is set to max) that the report blames, and demonstrates both the immediate underflow DoS and the subsequent orphaning of rewards.
TL;DR#
claimed[user][token]records how much a user has already been paid for their LP.- On GiantLP transfer, rewards are settled for the sender, balances move, then
afterTokenTransfersetsclaimed[to] = maxso the recipient does not inherit past rewards. - Nothing reduces
claimed[from]to match the sender's new lower balance. - After transferring half their LP,
claimed[from]can exceed the max entitlement for remaining shares → next settle underflows (Solidity 0.8) → transfer/claim/preview all revert (DoS). - When more rewards later arrive and entitlement catches up, the user sees
due = 0for rewards they should have earned — permanently orphaned.
The vulnerable code#
function afterTokenTransfer(address from, address to, uint256 /* amount */) external {
require(msg.sender == address(lpTokenETH), "Only LP");
// @> VULN: only `to` is adjusted - claimed[from] is left unchanged.
_setClaimedToMax(to);
// FIX: also scale down claimed[from] proportional to the transferred share
}
Root cause#
claimed[] is a cumulative absolute amount, not a per-share rate. When LP
balance drops, the absolute claimed figure must be scaled down (or the design
must use per-share claimed). Leaving it high makes
due = entitlement(balance) - claimed[user]
underflow whenever entitlement(newBalance) < claimed.
Preconditions#
- User has claimed rewards at least once (so
claimed[] > 0). - User then transfers away enough LP that remaining entitlement is below claimed.
- No special role required: any GiantLP transfer triggers the hook.
Attack walkthrough#
From output.txt:
- userA deposits 8 ETH, receives 8 GiantLP.
- 2 ETH rewards land; userA claims them (
claimed[userA] = 2 ETH). - userA transfers 4 LP to userB. First transfer succeeds;
claimed[userA]stays 2 ETH while balance is now 4 LP (max entitlement 1 ETH). - Any further transfer/preview for userA reverts (underflow DoS).
- Another 2 ETH of rewards arrive. userB claims 1 ETH. userA's preview returns 0 — their 1 ETH share of round 2 is orphaned.
Diagrams#
Impact#
Affected users cannot transfer remaining tokens or claim rewards until enough new rewards accumulate that entitlement exceeds the stale claimed[] — and even then, intermediate rewards are lost forever. Confirmed by the Stakehouse team (issue #178).
Sources#
- AuditVault finding #43034
- Code4rena report 2022-11-stakehouse
- Reduced source: code-423n4/2022-11-stakehouse
GiantMevAndFeesPool.afterTokenTransfer/ GiantLP transfer hooks
Sources & further analysis#
Reproductions & code
- Standalone PoC + full trace: 43034-h-12-sender-transferring-giantmevandfeespool-tokens-can-afte_exp (evm-hack-registry mirror).
- AuditVault finding: 43034-h-12-sender-transferring-giantmevandfeespool-tokens-can-afte.
Alerts & third-party analyses
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.