GambleCashless

Quorum Zero: How 0.000055% of Voting Power Moved Yapeswap's Routing Table

HasuFox • • News

The number that matters is not $21,900. It is 0.000055%.

SlowMist flagged the Yapeswap exploit this week. Losses landed near $21,900. The dollar figure is noise. The parameter is the signal. A governance proposal carrying roughly five hundred-thousandths of one percent of the voting supply was sufficient to relocate an administrative permission on YapeFactory — the factory contract that mints and indexes every trading pair on the protocol.

Contrary to popular belief, most protocol losses do not originate in exotic cryptographic breaks. They originate in unset boundary conditions. A require that was never written. A constant that shipped with a permissive default and never got revisited after the fork. Yapeswap is the second kind. No private key was compromised. No signature was forged. The contract executed exactly as specified — and the specification was empty.

To understand the failure you have to understand what a quorum actually is at the EVM level. It is not a security feature. It is a boolean gate. In a Compound-lineage governance module — and WorkersUnion is a thin fork of that lineage — quorumVotes is compared against forVotes before a proposal can be queued. Set quorumVotes to zero and the comparison is trivially satisfied. Any proposal, at any turnout, at any time, passes.

WorkersUnion shipped with that zero. That single storage slot is the entire attack surface.

The permission chain matters here. YapeFactory holds a feeToSetter role — the administrative key that points the protocol at its fee recipient and, transitively, at the registry that resolves which vault a pair should route deposits into. Whoever holds feeToSetter holds the routing table. Not the funds directly. The routing table. That distinction is what made the exploit quiet.

YapePair is the AMM primitive. YapeWrapper is the farm-facing adapter that reads vault state to decide where a user's deposit should land. YapeFactory sits above both, holding the pointer. Three contracts, one administrative slot, zero participation threshold.

Here is the sequence, parsed from the transaction trail.

Step one. The attacker submits a WorkersUnion proposal whose calldata is a single call: YapeFactory.setFeeToSetter(attacker). Voting power behind the proposal: approximately 0.000055% of supply. Not a whale. Not a flash-loan coalition. A rounding error with a wallet address.

Step two. Quorum is zero, so the proposal clears. No timelock worth the name. No veto window with teeth. The role transfers.

Step three. The attacker deploys a malicious Registry contract and repoints the factory's registry reference at it. This is where the design becomes interesting, because the fake registry is not a crude drainer. It is a precision instrument built around an inconsistency that already existed in the interface.

Step four. The malicious registry returns a forged address from latestVault(). Meanwhile vaults() — the enumerable view function — still returns the genuine vault. Two functions, two answers, one registry. The protocol's own read surface disagreed with itself, and nothing in the codebase enforced an invariant between them.

Step five. YapeWrapper trusts latestVault() for routing. Deposits intended for YapePair were routed and counted as farm assets — the accounting layer registered the position — but no redeemable shares were minted against it. Users saw a balance. The balance was a bookkeeping entry, not a claim.

The $21,900 is itself diagnostic. It is not a ceiling set by the attacker's ambition. It is the total value that happened to flow through the wrapper's routing path during the window. Higher TVL would have produced a larger number from the identical transaction. The exploit scales with whatever liquidity shows up.

I have modeled this class of attack before. When I decomposed the Lido stETH oracle proposal in late 2022, the entire exercise was arithmetic: what does it cost to move the reported rate, and what does the movement extract? The model only has meaning if the numerator is finite and the denominator is nonzero.

Yapeswap inverts that. The cost of governance capture was the gas to submit a proposal plus the spot price of a trivial token balance. Call it tens of dollars. The extracted value was roughly $21,900. Any economic security model with a zero denominator does not produce a risk score. It produces a division error.

And note what the attacker did not need. No reentrancy. No price manipulation. No flash loan. No mempool position. The exploit is deterministic, single-block, and reproducible by anyone with a funded wallet — precisely the profile that automated exploit bots scan for. Once the proposal passed, the remainder of the chain was a lookup table.

The contrarian read is that this was not a code vulnerability at all — which is exactly why it survived audit.

Auditors review logic. They trace value flow through functions. They check access control on privileged entry points. Very few audit scopes include a line-item inventory of governance constants, because constants are configuration, not code, and configuration is presumed to have been chosen deliberately. It was not. It was inherited.

The standard is a ceiling, not a foundation. A fork that copies a governance module inherits its permissive defaults along with its bytecode, and the defaults are the part nobody diffs.

There is a second blind spot, quieter. latestVault() and vaults() describing different states is a detectable condition. It is a two-call invariant that any indexer could evaluate every block for a few dollars of RPC. Almost nobody runs that check, because the two functions were never formally specified as required to agree. Code does not lie, but it often omits context — and the omitted context here is the implicit contract between a getter and its enumerator.

Parsing the chaos to find the deterministic core: the Yapeswap loss is $21,900, but the exploitable pattern is a storage slot set to zero, replicated across every WorkersUnion fork that shipped without revisiting the parameter.

The forward-looking question is not who audits Yapeswap next. It is how many deployed factories currently return quorumVotes() == 0 while holding a feeToSetter-equivalent role. That is a one-line eth_call. The answer is probably not one.

Market Prices

Coin Price 24h
BTC Bitcoin
$77,491.2 -2.89%
ETH Ethereum
$2,218.53 -3.43%
SOL Solana
$95.36 -4.08%
BNB BNB Chain
$714.92 -4.13%
XRP XRP Ledger
$1.23 -5.00%
DOGE Dogecoin
$0.0752 -3.07%
ADA Cardano
$0.1898 -3.26%
AVAX Avalanche
$8.35 -2.93%
DOT Polkadot
$0.9229 -4.57%
LINK Chainlink
$10.72 -5.17%

Fear & Greed

30

Fear

Market Sentiment

Event Calendar

{{年份}}
12
05
halving BCH Halving

Block reward halving event

18
03
unlock Sui Token Unlock

Team and early investor shares released

28
03
unlock Arbitrum Token Unlock

92 million ARB released

08
04
upgrade Solana Firedancer

Independent validator client goes live on mainnet

10
05
upgrade Ethereum Pectra Upgrade

Raises validator limit and account abstraction

30
04
upgrade Celestia Mainnet Upgrade

Improves data availability sampling efficiency

15
04
halving Bitcoin Halving

Block reward reduced to 3.125 BTC

22
03
unlock Optimism Unlock

Circulating supply increases by about 2%

Tools

All →

Altseason Index

41

Bitcoin Season

BTC Dominance Altseason

Gas Tracker

Ethereum 28 Gwei
BNB Chain 3 Gwei
Polygon 42 Gwei
Arbitrum 0.5 Gwei
Optimism 0.3 Gwei

Market Cap

All →
# Coin Price
1
Bitcoin BTC
$77,491.2
1
Ethereum ETH
$2,218.53
1
Solana SOL
$95.36
1
BNB Chain BNB
$714.92
1
XRP Ledger XRP
$1.23
1
Dogecoin DOGE
$0.0752
1
Cardano ADA
$0.1898
1
Avalanche AVAX
$8.35
1
Polkadot DOT
$0.9229
1
Chainlink LINK
$10.72

🐋 Whale Tracker

🟢
0xc493...1e12
12m ago
In
4,236,233 DOGE
🔴
0x49a8...2514
12m ago
Out
690.66 BTC
🟢
0xa7aa...05c4
1h ago
In
7,640 SOL

💡 Smart Money

0x7b1a...b2dd
Early Investor
+$2.2M
90%
0xb24e...89c1
Arbitrage Bot
+$4.3M
87%
0xfd16...bcc3
Arbitrage Bot
+$4.3M
85%