All incidents

QWAStaking Rebase Capture

Share
Sep 05, 2023 12:42 UTCAttackLoss: 0.43 ETHPending manual check1 exploit txWindow: Atomic
Estimated Impact
0.43 ETH
Label
Attack
Exploit Tx
1
Addresses
3
Attack Window
Atomic
Sep 05, 2023 12:42 UTC → Sep 05, 2023 12:42 UTC

Exploit Transactions

TX 1Ethereum
0xa4659632a983b3bfd1b6248fd52d8f247a9fcdc1915f7d38f01008cff285d0bf
Sep 05, 2023 12:42 UTCExplorer

Victim Addresses

0x69422c7f237d70fcd55c218568a67d00dc4ea068Ethereum
0xc14f8a4c8272b8466659d0f058895e2f9d3ae065Ethereum
0xf5bf1f78eda7537f9cab002a8f533e2733ddfbbcEthereum

Loss Breakdown

0.426995ETH

Similar Incidents

Root Cause Analysis

QWAStaking Rebase Capture

1. Incident Overview TL;DR

On Ethereum mainnet block 18070349, attacker EOA 0x6ce9fa08f139f5e48bc607845e57efe9aa34c9f6 called helper contract 0x154863eb71de4a34f88ea57450840eab1c71aba6, which borrowed 5 WETH from the Balancer Vault, bought QWA on Uniswap V2, repeatedly alternated QWAStaking.stake and QWAStaking.unstake(..., true), sold the inflated QWA position back to WETH, repaid the flash loan, and kept the remainder as profit. The observed sender-side native balance delta was 426994715564316216 wei, already net of gas.

The root cause is that QWAStaking allowed overdue rebases to be consumed by any caller inside the public stake and unstake entrypoints, while never checking whether the position redeeming those rebases had actually been staked during the overdue epochs. Once the epoch cursor became 35 periods stale, a fresh entrant could harvest historical emissions that should have belonged only to prior stakers.

2. Key Background

QWAStaking at 0x69422c7f237d70fcd55c218568a67d00dc4ea068 wraps QWA token 0xc14f8a4c8272b8466659d0f058895e2f9d3ae065 and rebasing sQWA token 0xf5bf1f78eda7537f9cab002a8f533e2733ddfbbc. Its epoch state tracks four values: length, number, end, and distribute.

The verified staking source shows three properties that matter:

function stake(address _to, uint256 _amount) external {
    rebase();
    QWA.transferFrom(msg.sender, address(this), _amount);
    sQWA.transfer(_to, _amount);
}

function unstake(address _to, uint256 _amount, bool _rebase) external {
    if (_rebase) rebase();
    sQWA.transferFrom(msg.sender, address(this), _amount);
    QWA.transfer(_to, _amount);
}

function rebase() public {
    if (epoch.end <= block.timestamp) {
        sQWA.rebase(epoch.distribute, epoch.number);
        epoch.end = epoch.end + epoch.length;
        epoch.number++;
        uint256 balance = QWA.balanceOf(address(this));
        uint256 staked = sQWA.circulatingSupply();
        epoch.distribute = balance <= staked ? 0 : balance - staked;
    }
}

First, rebase() is public. Second, each call processes only one overdue epoch by incrementing epoch.end once. Third, neither stake nor unstake ties reward eligibility to stake age. If the keeper stops rebasing, historical rewards remain stored as future epoch.distribute values that can later be consumed step by step by whoever interacts first.

3. Vulnerability Analysis & Root Cause Summary

This is an ACT attack against staking reward accounting, not a privileged-access incident. The violated invariant is straightforward: rewards for past epochs should accrue only to stake that was already participating during those epochs. QWAStaking breaks that invariant because it lets newly entered capital call into the public rebase path before and during redemption. The contract keeps only a global epoch cursor and does not checkpoint when a given sQWA balance became eligible for rewards. Because rebase() advances only one overdue epoch at a time, a stale system can be harvested iteratively. By alternating stake and unstake(..., true), the attacker forces one overdue rebase before mint and another before burn, allowing fresh stake to absorb historical emissions. The exploit ends once epoch.end is pushed back into the future and the attacker exits to the reference asset with profit.

4. Detailed Root Cause Analysis

The exploitable pre-state was Ethereum mainnet immediately before tx 0xa4659632a983b3bfd1b6248fd52d8f247a9fcdc1915f7d38f01008cff285d0bf, with the helper already deployed and QWAStaking materially stale. The auditor’s ACT definition sets this at block 18070348, and the validator confirmed the same semantics. In the validator PoC pre-checks, the forked state at that block satisfies epoch.end <= block.timestamp, stale epochs are greater than one, and QWA.balanceOf(staking) > sQWA.circulatingSupply(), which is the exact condition needed for a catch-up rebase to increase claims.

The attacker flow uses only public components. The exploit tx metadata shows the sender EOA calling helper 0x154863...aba6 with parameters that include the victim staking contract, the QWA and sQWA token addresses, a loop count, and the 5 WETH flash-loan amount. The same EOA had deployed that helper in tx 0x55a4858c663267dd5e2ed6a5987873e6d5e22cb86955735bbbd9e0a09fe97508 at block 18070342, as confirmed by the fetched tx list.

The trace for the seed exploit tx shows the core breakpoint repeatedly. Representative lines:

QWAStaking::stake(0x154863..., 4292715376335)
  sQWA::rebase(354150689007, 36)
QWAStaking::unstake(0x154863..., 4292715376335, true)
  sQWA::rebase(357692195897, 37)
...
@ slot 2: 70 -> 71

This is the essential mechanism. A fresh QWA position is staked after one overdue epoch has just been applied, then the subsequent unstake(..., true) applies the next overdue epoch before redemption. The same newly created sQWA balance is therefore allowed to ride historical emissions that accrued before it existed. The trace repeats this pattern across epochs 36 through 70, and the final storage update shows the epoch number advancing from 70 to 71, which is consistent with consuming 35 stale epochs from the validated pre-state.

The trace also shows the attacker finishing with no residual staking position, for example after the later iterations:

QWAStaking::unstake(0x154863..., 6071103020278, true)
QuantumWealthAcceleratorToken::transfer(0x154863..., 6071103020278)
QWAStaking::stake(0x154863..., 6071103020278)

This confirms the exploit is not based on permanently locking assets in the victim contract. It is a rebase-capture loop that continually redeems QWA, re-stakes it, and repeats until the backlog is exhausted. After the staking loop, the helper swaps QWA back to WETH, repays Balancer in the same transaction, unwraps leftover WETH into ETH, and sends the remaining ETH to the attacker EOA.

5. Adversary Flow Analysis

The adversary lifecycle has three concrete stages.

First, the attacker deployed helper contract 0x154863eb71de4a34f88ea57450840eab1c71aba6 from EOA 0x6ce9fa08f139f5e48bc607845e57efe9aa34c9f6 in block 18070342. The tx list around the exploit block shows only three relevant txs for the cluster in the analysis window: deployment, one earlier helper call against a different target set, and the seed exploit call.

Second, in tx 0xa4659632...d0bf, the helper obtained flash liquidity from Balancer Vault 0xBA12222222228d8Ba445958a75a0704d566BF2C8, then bought QWA using Uniswap V2 Router 0x7a250d5630b4cf539739df2c5dacb4c659f2488d. This step removed the need for any privileged inventory or attacker-side artifacts beyond fresh deployment and public protocol calls.

Third, the helper looped public stake and unstake(..., true) calls against QWAStaking until the stale backlog was consumed. Each iteration pulled one more overdue epoch into the present and let the attacker’s newly entered position benefit from it. After catch-up, the helper sold the inflated QWA position back into WETH, repaid the flash loan, withdrew leftover WETH to ETH, and transferred the realized profit to the EOA.

The observed sender balance delta corroborates the outcome:

{
  "address": "0x6ce9fa08f139f5e48bc607845e57efe9aa34c9f6",
  "before_wei": "28932182029895228702",
  "after_wei": "29359176745459544918",
  "delta_wei": "426994715564316216"
}

Because this native delta already includes gas spending, it is a post-fee profit measurement. That is enough to satisfy the ACT success predicate.

6. Impact & Losses

The confirmed realized loss in the validated incident is 426994715564316216 wei of ETH-equivalent profit captured by the attacker EOA. In economic terms, the deeper impact is that 35 overdue staking rebases were reassigned from historical stakers to a late entrant. That means the protocol’s reward schedule and intended temporal distribution of emissions were broken, even aside from the single observed profit figure.

The loss record for workflow metadata is therefore:

[
  {
    "token_symbol": "ETH",
    "amount": "426994715564316216",
    "decimal": 18
  }
]

7. References

  1. Seed exploit tx 0xa4659632a983b3bfd1b6248fd52d8f247a9fcdc1915f7d38f01008cff285d0bf metadata confirming sender, helper target, block, calldata, and inclusion context.
  2. Seed trace for the same tx showing repeated QWAStaking::stake, QWAStaking::unstake(..., true), and sQWA::rebase calls through the stale epoch backlog.
  3. Verified QWAStaking source at 0x69422c7f237d70fcd55c218568a67d00dc4ea068, specifically stake, unstake, and rebase.
  4. Helper contract disassembly and tx history showing the attacker-controlled helper deployment and usage.
  5. Seed balance diff artifact showing the attacker EOA’s net native balance increase of 426994715564316216 wei.