All incidents

HATE Rebase-Before-Burn Exploit

Share
Sep 05, 2023 09:57 UTCAttackLoss: 7.84 WETHPending manual check2 exploit txWindow: 5h 36m
Estimated Impact
7.84 WETH
Label
Attack
Exploit Tx
2
Addresses
2
Attack Window
5h 36m
Sep 05, 2023 09:57 UTC → Sep 05, 2023 15:33 UTC

Exploit Transactions

TX 1Ethereum
0xe28ca1f43036f4768776805fb50906f8172f75eba3bf1d9866bcd64361fda834
Sep 05, 2023 09:57 UTCExplorer
TX 2Ethereum
0x8e1b0ab098c4cc5f632e00b0842b5f825bbd15ded796d4a59880bb724f6c5372
Sep 05, 2023 15:33 UTCExplorer

Victim Addresses

0x8ebd6c7d2b79ca4dc5fbdec239a8bb0f214212b8Ethereum
0x738dab4af8d21b7aafb73545d79d3b4831ee79daEthereum

Loss Breakdown

7.84WETH

Similar Incidents

Root Cause Analysis

HATE Rebase-Before-Burn Exploit

1. Incident Overview TL;DR

Heavens Gate’s staking system was exploitable by any unprivileged actor. The attacker flash-borrowed HATE from the public HATE/WETH Uniswap V2 pair, staked it into HATEStaking, then repeatedly called unstake(..., true) so the contract rebased before burning the attacker’s sHATE. That ordering let the attacker reclaim full HATE principal while keeping rebased sHATE residue, which was compounded across loops and finally sold for WETH and unwrapped to ETH.

The primary realization occurred in tx 0xe28ca1f43036f4768776805fb50906f8172f75eba3bf1d9866bcd64361fda834, with a second realization in tx 0x8e1b0ab098c4cc5f632e00b0842b5f825bbd15ded796d4a59880bb724f6c5372.

2. Key Background

sHATE is a rebasing token that tracks balances internally in gons and exposes user balances through _gonsPerFragment. A positive rebase increases _totalSupply and decreases _gonsPerFragment, so a fixed gon position maps to more visible fragments after rebase. The exploit relied on public liquidity in the live HATE/WETH pair at block 18069527, plus open access to HATEStaking.stake and HATEStaking.unstake.

3. Vulnerability Analysis & Root Cause Summary

The vulnerable invariant is straightforward: if a user stakes HATE and then unstakes the same displayed sHATE amount, the protocol should burn the full ownership claim before returning principal. Verified HATEStaking source breaks that invariant. Its unstake(address,uint256,bool) path executes if (_rebase) rebase(); before sHATE.transferFrom(msg.sender, address(this), _amount); and HATE.transfer(_to, _amount);. The rebase() function itself calls sHATE.rebase(epoch.distribute, epoch.number) before updating epoch distribution state. On the token side, sHATE.transferFrom computes gonValue = gonsForBalance(value) using the post-rebase _gonsPerFragment. Because _gonsPerFragment is smaller after a positive rebase, the burn consumes fewer gons than the attacker originally received for that displayed fragment amount. The attacker therefore exits unstake with full HATE principal plus a residual rebased sHATE claim.

Representative victim-code snippet:

function unstake(address _to, uint256 _amount, bool _rebase) external {
    if (_rebase) rebase();
    sHATE.transferFrom(msg.sender, address(this), _amount);
    require(_amount <= HATE.balanceOf(address(this)), "Insufficient HATE balance in contract");
    HATE.transfer(_to, _amount);
}

function rebase() public {
    if (epoch.end <= block.timestamp) {
        sHATE.rebase(epoch.distribute, epoch.number);
        // epoch updates follow only after the rebase
    }
}

Representative token-side snippet:

function rebase(uint256 amount_, uint256 epoch_) public onlyStakingContract returns (uint256) {
    _totalSupply = _totalSupply + rebaseAmount;
    _gonsPerFragment = TOTAL_GONS / _totalSupply;
}

function transferFrom(address from, address to, uint256 value) public returns (bool) {
    uint256 gonValue = gonsForBalance(value);
    _gonBalances[from] = _gonBalances[from] - gonValue;
    _gonBalances[to] = _gonBalances[to] + gonValue;
}

4. Detailed Root Cause Analysis

The seed trace for tx 0xe28ca1f43036f4768776805fb50906f8172f75eba3bf1d9866bcd64361fda834 shows the attack in the exact order required by the root cause. The helper contract borrows 907615399181304 HATE from pair 0x738dab4af8d21b7aafb73545d79d3b4831ee79da, then stakes that HATE into HATEStaking at 0x8ebd6c7d2b79ca4dc5fbdec239a8bb0f214212b8. During the first unstake, the trace shows sHATE::rebase(917916307600019, 72) before sHATE::transferFrom(..., 907615399181304) and then HATE::transfer(..., 907615399181304).

That ordering matters because the visible fragment amount being burned stays the same while the conversion rate changes underneath it. After the positive rebase, the same displayed sHATE amount corresponds to fewer gons, so the attacker surrenders less true ownership claim than they originally received. The protocol nonetheless transfers back the full HATE principal. The attacker then stakes the reclaimed HATE again, repeats the favorable rebase-before-burn path, and compounds the leftover value.

The same transaction shows three stake/unstake cycles, after which the helper holds 1481247433788967 HATE before repaying only 910346438496795 HATE to the pair. The residual HATE is swapped through the public Uniswap V2 router for 6450216218901384581 WETH-equivalent in the incident trace. The attacker EOA’s native balance increases from 20.337217947488035628 ETH to 26.769390685463141950 ETH. Receipt-derived gas data shows 1244129 gas used at 14502901971 wei effective gas price, for an exact fee of 0.018043480926278259 ETH, matching the reported net gain of 6.432172737975106322 ETH.

5. Adversary Flow Analysis

The adversary cluster consists of EOA 0x6ce9fa08f139f5e48bc607845e57efe9aa34c9f6 and helper contracts 0x8faa53a742fc732b04db4090a21e955fe5c230be and 0x38702e5c98ba4ad4b786d5a075a5c74694cd616d. The execution flow is:

1. Flash-swap HATE from the public HATE/WETH pair.
2. Stake borrowed HATE into HATEStaking.
3. Call unstake with rebase enabled so HATEStaking rebases before burn.
4. Restake the redeemed HATE and repeat to compound residual value.
5. Repay flash-swap principal plus fee in HATE.
6. Sell surplus HATE for WETH and unwrap to ETH.
7. Transfer ETH proceeds back to the attacker EOA.

The attack is ACT because no privileged roles or attacker-private artifacts are required. Liquidity is public, staking entrypoints are public, and the vulnerable accounting transition is fully observable on-chain.

6. Impact & Losses

Across the two observed exploit realizations, the HATE/WETH pair lost 7843597105891581235 wei of WETH, or 7.843597105891581235 WETH. The attacker EOA realized 7.686027440426626042 ETH net profit across the two transactions. The loss source is not an authorization failure but an accounting failure that allowed protocol rebase value to be extracted into transferable HATE and then externalized through the market.

7. References

  • Seed index covering both exploit transactions.
  • Primary exploit tx 0xe28ca1f43036f4768776805fb50906f8172f75eba3bf1d9866bcd64361fda834.
  • Secondary exploit tx 0x8e1b0ab098c4cc5f632e00b0842b5f825bbd15ded796d4a59880bb724f6c5372.
  • Verified HATEStaking source for the vulnerable unstake and rebase ordering.
  • Verified sHATE source for rebase and transferFrom.
  • Primary transaction balance diff and receipt-based fee artifact.