Reproduced Exploit

Statusl — global MP cap broken on unstake causes permanent DoS

1. Protocol assumes totalMPAccrued totalMaxMP.

Jan 2026Otheruntagged2 min read

Chain

Other

Category

untagged

Date

Jan 2026

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: 65329-global-mp-cap-invariant-can-be-broken-on-unstake-causing-ari. Standalone Foundry PoC and full write-up: 65329-global-mp-cap-invariant-can-be-broken-on-unstake-causing-ari_exp in the evm-hack-registry mirror.


Vulnerability classes: underflow · permanent · dos-resistance · integer-bounds · reward-accounting

Reproduction: self-contained Foundry PoC with only forge-std. Full trace: output.txt. PoC: test/65329-global-mp-cap-invariant-can-be-broken-on-unstake-causing-ari_exp.sol.

AuditVault taxonomy: lang/solidity · sector/staking · platform/cyfrin · has/github · has/poc · severity/high · vuln/arithmetic/underflow · impact/dos/permanent · genome: underflow · permanent · dos-resistance · integer-bounds · reward-accounting · timestamp-dependence


Key info#

ImpactHIGH — permanent DoS of stake/unstake/rewards once time advances
ProtocolStatus Network / Statusl
Vulnerable code_unstake global MP reduction + _totalMP clamp subtraction
Bug classBroken invariant totalMPAccrued <= totalMaxMP → underflow
FindingCyfrin Statusl2 v2.0, 2026-01-05 · #65329 · Samuraii77
ReportCyfrin report
SourceAuditVault
StatusFixed in 56a7b64
Compiler^0.8.24 (PoC)

TL;DR#

  1. Protocol assumes totalMPAccrued <= totalMaxMP.
  2. Unstake reduces accrued and max MP proportionally to the vault's local values.
  3. An unsaturated vault at a globally saturated moment removes more max than accrued.
  4. Invariant flips: totalMPAccrued > totalMaxMP.
  5. _totalMP does totalMaxMP - totalMPAccrued and underflows → DoS.

The vulnerable code#

SOLIDITY
totalMPAccrued -= _deltaMpTotal; // @> VULN: asymmetric with deltaMax
totalMaxMP     -= _deltaMpMax;
// ...
accruedMP = totalMaxMP - totalMPAccrued; // @> VULN: underflows when accrued > max

Fix: clamp early when totalMPAccrued >= totalMaxMP before subtracting.


Root cause#

Local vault MP gap (maxMP > mpAccrued) is projected onto global aggregates without preserving the global cap invariant.


Preconditions#

  • Global MP at cap (totalMPAccrued == totalMaxMP).
  • A vault that is not fully saturated unstakes.

Attack walkthrough#

  1. Saturated vault B fills global accrued to the global max.
  2. Unsaturated vault A unstakes fully.
  3. deltaMax > deltaTotal → global accrued exceeds global max.
  4. Next updateGlobalState after time advances reverts on underflow.

Diagrams#

flowchart TD CAP["totalMPAccrued == totalMaxMP"] --> UNS["unstake unsaturated vault"] UNS --> BRK["accrued > max"] BRK --> UPD["updateGlobalState"] UPD --> REV["underflow revert / DoS"]

Impact#

All flows calling _updateGlobalState (stake, unstake, lock, rewards) permanently revert once time moves — protocol-level liveness failure.


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.