Reproduced Exploit

Hyperstable — anyone can consume an emission epoch and the minter then mints 0

epochEmission sets lastEpoch = currentEpoch and returns the epoch's emission. There is no caller check. The first call in an epoch returns (peg - ve) * 2 / 10000. With a peg supply of 1_000_000 and no ve supply that is 200. The stranger does not mint. The minter's later call sees currentEpoch == la…

May 2025•Other•access-control•3 min read

Chain

Other

Category

access-control

Date

May 2025

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: 55693-h-02-emissionschedulerepochemission-lacks-access-control-and. Standalone Foundry PoC and full write-up: 55693-h-02-emissionschedulerepochemission-lacks-access-control-and_exp in the evm-hack-registry mirror.


Vulnerability classes: vuln/access-control/missing-auth · vuln/logic/state-update

Key info#

ProtocolHyperstable EmissionScheduler
Finding0x52, AuditVault 55693
Vulnerable codeEmissionScheduler.epochEmission
HarmA stranger's call writes lastEpoch. The minter's call then returns (0, 0, 0) and 200 tokens are not minted. onlyMinter reverts the stranger and the minter still mints 200.
Chainlocal synthetic

TL;DR#

epochEmission sets lastEpoch = currentEpoch and returns the epoch's emission. There is no caller check. The first call in an epoch returns (peg - ve) * 2 / 10000. With a peg supply of 1_000_000 and no ve supply that is 200. The stranger does not mint. The minter's later call sees currentEpoch == lastEpoch and returns (0, 0, 0), so the epoch mints nothing. The scheduler that requires msg.sender == minter reverts the stranger, and the minter's call mints 200.

The vulnerable code#

SOLIDITY
if (currentEpoch == lastEpoch) {
    return (0, 0, 0);
}
// @> VULN: any caller writes lastEpoch, so the minter's later call returns zero
lastEpoch = currentEpoch;
uint256 toEmit = (_pegSupply - _veSupply) * 2 / EMISSION_PRECISION;

The report's fix is _onlyMinter on this function. That check is present on the control scheduler.

Root cause#

lastEpoch is both the "already emitted" flag and a value any external caller can write. The mint path trusts the returned tuple, so a consumed epoch mints zero.

Preconditions#

lastEpoch has not yet been set to the current epoch. Peg supply is 1_000_000 tokens and ve supply is 0. EMISSION_PRECISION is 10000. The stranger is not the minter. No time travel is required. Both calls see the same block.timestamp.

Attack walkthrough#

  1. The stranger calls epochEmission(1_000_000 ether, 0). It returns 200 and writes lastEpoch.
  2. No emission tokens are minted. The staker balance stays 0.
  3. The minter calls the same function. It hits currentEpoch == lastEpoch and returns (0, 0, 0).
  4. The mint is 0.
  5. On the gated scheduler the stranger's call reverts with only minter.
  6. The minter's call there returns 200 and mints 200 to stakers.

Diagrams#

flowchart TD A["Stranger calls epochEmission"] --> B["VULN: lastEpoch becomes this epoch"] B --> C["Return value is 200 and nothing is minted"] C --> D["Minter calls and gets 0, 0, 0"] D --> E["Stakers receive nothing"] E --> F["onlyMinter reverts the stranger and the minter still mints 200"]

Impact#

Stakers receive 0 emission tokens for the epoch instead of 200. The 200 is not paid to the stranger either. It is simply not minted. The gated scheduler still pays the 200, so the loss is the missing caller check and not the emission formula.

Remediation#

Require msg.sender == minter before writing lastEpoch.

How to reproduce#

BASH
cd 55693-h-02-emissionschedulerepochemission-lacks-access-control-and_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.