Reproduced Exploit

EYWA — ProposalManager.CLOCK returns the block number

ProposalManager treats EscrowManager as the voting token and asks it for CLOCK(). The escrow has no such function. The call fails, and the catch returns block.number.

Nov 2024•Ethereum•governance•3 min read

Chain

Ethereum

Category

governance

Date

Nov 2024

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: 44336-incorrect-proposalmanagerclock-behaviour-mixbytes-none-eywa. Standalone Foundry PoC and full write-up: 44336-incorrect-proposalmanagerclock-behaviour-mixbytes-none-eywa_exp in the evm-hack-registry mirror.


Vulnerability classes: vuln/governance/timelock-bypass · vuln/logic/wrong-condition

Key info#

ProtocolEYWA DAO
FindingMixBytes, November 2024, AuditVault 44336
Vulnerable codeProposalManager.CLOCK catch returns block.number
HarmSnapshot is 172900 and the proposal is Defeated. The timestamp snapshot 1700172800 is still Pending.
Chainlocal synthetic (audited Ethereum contracts)

TL;DR#

ProposalManager treats EscrowManager as the voting token and asks it for CLOCK(). The escrow has no such function. The call fails, and the catch returns block.number.

votingDelay is 2 days. On block 100 at timestamp 1700000000 the snapshot is 100 + 172800 = 172900. The voting period is one week, so the deadline is 777700. That is already behind the current timestamp, and state is Defeated. A timestamp clock would snapshot at 1700172800 and the proposal would still be Pending.

The vulnerable code#

SOLIDITY
try IClock(address(token)).CLOCK() returns (uint48 timepoint) {
    return timepoint;
} catch {
    // @> VULN: the catch returns block.number instead of block.timestamp.
    return uint48(block.number);
}

The report's fix is to return block.timestamp from that catch.

Root cause#

The Governor-style clock helper assumes that a token without CLOCK() is block-number mode. EYWA's escrow is timestamp mode and simply does not implement the function. The fallback therefore mixes a block number with a delay that was configured as 2 days of seconds. The vote window is placed in 1970 and is already closed.

Preconditions#

Block number 100. Timestamp 1700000000. Both are fixed for the whole transaction. votingDelay is 2 days and votingPeriod is 1 week. The token has no CLOCK().

Attack walkthrough#

  1. propose calls CLOCK().
  2. The escrow call reverts. The catch returns 100.
  3. proposalSnapshot is 172900. proposalDeadline is 777700.
  4. state is 2, Defeated, because the timestamp is past the deadline.
  5. fairSnapshot is 1700172800. fairState is 0, Pending.

Diagrams#

flowchart TD A["propose calls CLOCK on the escrow"] --> B["Escrow has no CLOCK"] B --> C["VULN: catch returns block number 100"] C --> D["Snapshot is 172900"] D --> E["Deadline 777700 is already past"] E --> F["Proposal is Defeated, fair state is Pending"]

Impact#

A proposal created through this clock cannot be voted. Its snapshot and deadline are block-number-sized. The same proposal on a timestamp clock would not open for another 2 days.

Remediation#

Return block.timestamp from the catch, or implement CLOCK() on the escrow so the try succeeds.

How to reproduce#

BASH
cd 44336-incorrect-proposalmanagerclock-behaviour-mixbytes-none-eywa_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.