Originally published at Yapeswap Exploit: Zero Quorum Handed Over the Factory
On 25 October 2026 at 02:19:23 UTC, an attacker drained the Yearn-farmed reserves of Yapeswap, a small Uniswap V2-style DEX on Ethereum. SlowMist put the loss at about $21.9K. That is not a headline number, but DefiLlama data cited on X suggested Yapeswap held only around $25K in total value, so most of the protocol was gone in one transaction.
The mechanics are worth studying because nothing about them depends on size. A governance vote with almost no support transferred the factory's top admin role. From there, the attacker never had to touch a user's tokens directly. The pairs moved their own reserves into attacker contracts, because the factory told them to.
TL;DR
- Protocol: Yapeswap, an AMM whose pairs deposit part of their reserves into Yearn vaults to earn yield.
- Chain: Ethereum mainnet.
- Loss: about $21.9K per SlowMist; Oculr measured about $22.0K in the main transaction and about $39.7K across three attacker transactions. The attacker EOA held 10.52 ETH when we checked it.
-
Root cause: the WorkersUnion governance contract had a zero quorum, so a proposal backed by roughly 0.000055% of voting power could execute
setFeeToSetteron the YapeFactory. -
Amplifier: every YapePair exposed a public
updateRegistry()that copied the factory's registry address without validation, andrebalance()deposited reserves into whatever vault that registry returned.
How Yapeswap Was Designed
Yapeswap pairs work like Uniswap V2 pairs with one addition: idle reserves are "farmed" in Yearn. Each pair asks a registry for the latest Yearn vault for a token, and rebalance() moves reserves between the pair and that vault up to a configured farming ratio. Yield and fees are split, with a fee leg paid to the factory's feeTo address.
Control sits in the YapeFactory. As in Uniswap V2, the factory has a feeToSetter admin. In Yapeswap that admin can also set the factory's operator, registry and feeTo. Ownership of the factory was meant to belong to the Yapeswap DAO, which votes with VISION tokens locked in a voting-escrow contract and executes through a governance timelock.
In other words, the security of every pair's reserves reduced to one question: who can win a DAO vote?
Root Cause: A Vote Nobody Had to Win
SlowMist's alert states that WorkersUnion, Yapeswap's governance module, had a quorum of zero. A proposal therefore did not need a meaningful share of voting power, only more "for" than "against" votes. With no one else voting, a dust-sized lock was enough. SlowMist estimates the attacker's proposal carried about 0.000055% of voting power.
Oculr's trace reconstruction lays out the timeline. We did not re-derive the earlier setup transactions ourselves, so treat these dates as Oculr's:
-
16 October: the attacker swapped ETH for VISION, locked it in the voting escrow with
createLockUntil, delegated to itself, and calledproposeTxtargeting the YapeFactory. -
17 to 24 October: a further governance call, then
scheduleon the timelock. -
25 October, 02:15:47 UTC:
executeran the payloada2e74af6(attacker EOA).
We confirmed that 0xa2e74af6 is the selector for setFeeToSetter(address). A read of the factory after the attack returns the attacker EOA as feeToSetter, the attack contract as operator and feeTo, and the attacker-deployed contract 0xc620...b5a7 as registry.
A timelock existed, and it ran for days. It did not help, because nobody was watching the queue and the vote itself could not fail.
The Second Bug: Pairs That Trust Any Registry
Owning the factory gave the attacker the right to change settings. Turning settings into stolen funds needed one more weakness, which Oculr locates in YapePair.sol:
-
updateRegistry()is public and simply copiesfactory.registry()into the pair's storage. -
setFarmingRatio()is restricted to the factoryoperator, which the attacker now controlled. -
rebalance()withdraws from the real Yearn vault and deposits intoregistry.latestVault(token).
The fake registry was built to look normal. SlowMist notes that vaults() still listed the genuine Yearn vaults, while latestVault() returned attacker vaults. Any off-chain check that only listed vaults would have seen nothing wrong.
The sketch below is our own simplified illustration of the pattern, not verbatim Yapeswap code:
// Simplified illustration of the vulnerable pattern (not verbatim Yapeswap code)
contract YapePairSketch {
IYapeFactory public factory;
IRegistry internal _registry;
uint256 public maxFarmingRatio; // e.g. 8000 = 80%
// Anyone can sync the registry from the factory, with no validation
function updateRegistry() public {
_registry = IRegistry(factory.registry());
}
function setFarmingRatio(uint256 minR, uint256 maxR) external {
require(msg.sender == factory.operator(), "not operator");
maxFarmingRatio = maxR; // attacker raised this to 100%
}
function rebalance(address token) external {
// 1. Pull the pair's assets out of the genuine Yearn vault
_withdrawFromCurrentVault(token);
// 2. Deposit into whatever the registry calls "latest"
IVault target = IVault(_registry.latestVault(token));
IERC20(token).approve(address(target), type(uint256).max);
target.deposit(_farmAmount(token), address(this));
// 3. Pay the fee leg to factory.feeTo()
_payFee(token, factory.feeTo());
}
}
Each piece looks reasonable alone. Syncing configuration from a factory is common. Letting an operator tune a ratio is common. Depositing into the "latest" vault is how Yearn integrations are usually written. Together, they mean whoever controls the factory controls every pair's reserves.
Attack Walkthrough
We confirmed the main transaction, its sender, block 26,266,653 and timestamp against a public Ethereum node. The step details below come from SlowMist and Oculr.
Step 1: Take the factory
The timelock executed the attacker's proposal, making the attacker EOA the factory's feeToSetter.
Step 2: Install the fake plumbing
In block 26,266,649, the attacker deployed the attack contract 0x327b...be01, which deployed a fake registry and four fake vaults for DAI, USDC, WETH and WBTC. The EOA then set the factory's operator, registry and feeTo to its own contracts.
Step 3: Loop over the pairs
Four blocks later, the attack contract iterated through allPairs. For each pair it called the public updateRegistry(), which, according to Oculr's storage diff, switched the pair's registry slot from the real Yearn registry to the attacker's. It then called setFarmingRatio as operator, raising the maximum from 8,000 to 10,000 basis points.
Step 4: Let rebalance do the theft
Each rebalance() redeemed the pair's real Yearn positions (unwinding through Yearn v2 and v3 strategies, Sky and Morpho along the way) and deposited the freed DAI, USDC, WETH and WBTC into the fake vaults. Fee legs went to feeTo, also the attacker. The fake vaults minted no redeemable shares, so the pairs' accounting still counted those assets as farmed while they were in fact gone.
Oculr counts 1,613 DAI, 1,607 USDC, 7,705 USDT, 4.205 WETH and 0.0141 WBTC reaching the attack contract in this transaction. A follow-up transaction repeated the loop for the remaining pairs and a third swept the vaults. The proceeds ended as 10.52 ETH in the attacker EOA, which still held them when we checked.
Why This Matters Beyond $22K
Earlier we wrote about the X100 Senate takeover, where a flash loan rented a governance majority for one transaction. Yapeswap is the slow, cheap version of the same lesson. The attacker did not need to rent anything. It needed governance that would accept a proposal no one supported, and a protocol where governance could rewire custody of funds.
Small and older protocols are the most exposed. Token holders drift away, votes go quiet, and parameters that were "fine at launch" stay untouched. A zero quorum might have been convenient when the team was the only voter. Years later it is an open admin key.
How to Prevent This
Set a real quorum and keep it meaningful
Quorum should be a meaningful share of circulating voting power, and it should be revisited as participation changes. Pair it with a minimum proposal threshold so that dust locks cannot even submit a proposal.
Treat role transfers as critical proposals
Any proposal that changes an owner, admin or feeToSetter should face a higher quorum, a longer delay, or a guardian veto. Changing who holds the keys is different from tuning a fee.
Monitor the timelock queue
A timelock only helps if someone reads it. Alert on every queued call that targets core contracts, especially role setters, and make sure the alert reaches people who can act.
Validate configuration in the contracts that hold funds
Pairs should not blindly accept a new registry. Restrict updateRegistry() to a trusted role, and check that returned vaults are on an allowlist or match the expected underlying asset and a known vault factory. Better still, pin the vault per token and require a delayed migration to change it.
Check balances, not just bookkeeping
After rebalance(), verify that the pair actually received vault shares worth the deposited assets. A deposit that returns zero shares should revert.
For a broader pre-launch list, see our smart contract security checklist and our overview of top smart contract security risks.
Key Addresses and Transactions
- Main exploit transaction:
0x4c741568...179e1e - Attacker EOA:
0x6e523866424220b3cb2cab88ba2195cfa5636b12 - Attack contract (also factory operator and feeTo):
0x327bf6dfc38a79393f78866c705193c39b04be01 - Fake registry:
0xc620ca5b2a63cdb792d462898010d2be4421b5a7 - YapeFactory:
0x46adc1c052fafd590f56c42e379d7d16622835a2 - Example victim pair (DAI/USDC):
0x23c1653e9e13eefe2e1b5954bfef00a2db0483b6
Sources
- SlowMist alert on X: loss estimate, zero-quorum root cause, fake registry behavior and addresses
- SlowMist Hacked database: incident summary and classification
-
Oculr report: trace reconstruction, governance timeline,
YapePairfunctions and per-token amounts - TokenPost report: independent summary of the governance attack
- PANews report: independent summary of SlowMist's findings
- Transaction status, block, timestamp, factory roles and attacker balance, read by us from a public Ethereum RPC node
Closing Thoughts
Yapeswap lost a small amount, but the pattern is the one that empties large protocols too: governance that cannot fail, controlling configuration that funds trust without checking. A quorum, a watched timelock or a registry allowlist would each have broken this chain.
Running a DAO-governed protocol, or integrating external vaults? Contact Panda Security for a governance and access-control review, or see our audit pricing.
Top comments (0)