FOOM Lottery verifier parameter collapse enables arbitrary collect drains
Exploit Transactions
Victim Addresses
0x239af915abcd0a5dcb8566e863088423831951f8Ethereum0xdb203504ba1fea79164af3ceffba88c59ee8aafdBaseLoss Breakdown
Similar Incidents
Veil01ETH forged-proof drain on Base
32%V3Utils Arbitrary Call Drain
30%Tara Light Client Quorum-Check Ordering Bug Enables Forged Bridge Finalization and WETH Drain
29%Spectra Router KYBERSWAP arbitrary call drains SdCrvCompounder
29%C87C Router tokenReceived Authentication Bypass Drains Victim-Approved USDT
29%SorraV2 staking withdraw bug enables repeated SOR reward drain
29%Root Cause Analysis
FOOM Lottery verifier parameter collapse enables arbitrary collect drains
1. Incident Overview TL;DR
Two seed transactions on Ethereum and Base drained FOOM from FOOM Lottery contracts by forging withdraw proofs and repeatedly calling collect.
- Ethereum seed tx:
0xce20448233f5ea6b6d7209cc40b4dc27b65e07728f2cbbfeb29fc0814e275e48(block24539650) - Base seed tx:
0xa88317a105155b464118431ce1073d272d8b43e87aba528a24b62075e48d929d(block42650623)
The root cause is malformed Groth16 verifier constants in WithdrawG16Verifier: delta is set equal to gamma. This removes soundness for the intended statement and allows attacker-computed proofs for attacker-chosen public signals. FoomLottery.collect trusts withdraw.verifyProof(...) and computes payout directly from attacker-supplied rewardbits, so forged proofs become direct token outflows.
2. Key Background
FOOM Lottery payout redemption path requires a Groth16 proof through withdraw.verifyProof(...) and uses public signals:
_root_nullifierHash_rewardbits- recipient/relayer/fee/refund fields
The contract requires:
- unused nullifier,
- known Merkle root,
- verifier acceptance,
then transfers FOOM reward to recipient.
Groth16 verifier correctness requires independent gamma and delta terms in the pairing equation. If delta == gamma, adversarial construction with verifier constants (A=alpha, B=beta, C=-vk_x) satisfies the pairing equation for arbitrary valid-field public signals.
3. Vulnerability Analysis & Root Cause Summary
This is an ATTACK-class vulnerability in verifier parameterization and downstream trust of verifier output in a high-value payout path. The verifier constants on both chains hardcode identical points for gamma and delta. That collapse allows forged proof acceptance without a legitimate witness tied to an actual winning ticket statement. The lottery redemption function then accepts attacker-selected rewardbits, nullifier, and recipient once verifier returns true. The exploit therefore does not require privileged roles or private keys and is executable by an unprivileged actor using normal on-chain calls. Deterministic trace evidence shows repeated successful collect calls in one transaction per chain and matching FOOM balance transfers from lottery to attacker EOAs.
Representative verifier constant evidence (Ethereum verifier source):
uint256 constant gammax1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634;
uint256 constant gammax2 = 10857046999023057135944570762232829481370756359578518086990519993285655852781;
uint256 constant gammay1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531;
uint256 constant gammay2 = 8495653923123431417604973247489272438418190587263600148770280649306958101930;
uint256 constant deltax1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634;
uint256 constant deltax2 = 10857046999023057135944570762232829481370756359578518086990519993285655852781;
uint256 constant deltay1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531;
uint256 constant deltay2 = 8495653923123431417604973247489272438418190587263600148770280649306958101930;
4. Detailed Root Cause Analysis
4.1 Invariant and breakpoint
Safety invariant:
- only a valid witness for the intended withdrawal statement should satisfy
verifyProoffor supplied public signals.
Code-level breakpoint:
WithdrawG16Verifierhardcodesdelta == gamma.FoomLottery.collectuses verifier result as the decisive gate and computes reward directly from_rewardbits.
Representative redemption logic (Ethereum lottery source):
require(nullifier[_nullifierHash] == 0, "Incorrect nullifier");
nullifier[_nullifierHash] = 1;
uint reward = uint(betMin) * (
(_rewardbits & 0x1 > 0 ? 1 : 0) * 2**betPower1 +
(_rewardbits & 0x2 > 0 ? 1 : 0) * 2**betPower2 +
(_rewardbits & 0x4 > 0 ? 1 : 0) * 2**betPower3
);
require(roots[_root] > 0, "Cannot find your merkle root");
require(withdraw.verifyProof(_pA, _pB, _pC, [
_root, _nullifierHash, _rewardbits, uint(uint160(_recipient)),
uint(uint160(_relayer)), _fee, _refund
]), "Invalid withdraw proof");
4.2 On-chain execution evidence
Ethereum trace shows repeated forged redemption pattern:
FoomLottery::collect(... rewardbits=7 ...)
WithdrawG16Verifier::verifyProof(...) [staticcall]
emit LogWin(... recipient: 0x46c403e3DcAF219D9D4De167cCc4e0dd8E81Eb72)
Base trace shows the same pattern:
0xdb203504ba1fea79164AF3CeFFBA88C59Ee8aAfD::collect(... rewardbits=7 ...)
0x02c30D32A92a3C338bc43b78933D293dED4f68C6::verifyProof(...) [staticcall]
emit LogWin(... param2: 0x73f55A95D6959D95B3f3f11dDd268ec502dAB1Ea)
Observed call counts from trace logs:
- Ethereum seed tx: 30
LogWinemissions - Base seed tx: 10
LogWinemissions
4.3 ACT exploit conditions
The exploit requires only:
- existing root in
rootsmapping, - FOOM balance available in lottery,
- unused nullifier values,
- unprivileged transaction submission.
These conditions are publicly observable and permissionless.
5. Adversary Flow Analysis
Adversary-related accounts in seed flow:
- Ethereum EOA:
0x46c403e3dcaf219d9d4de167ccc4e0dd8e81eb72 - Ethereum helper contract:
0x256a5d6852fa5b3c55d3b132e3669a0bde42e22c - Base EOA:
0x73f55a95d6959d95b3f3f11ddd268ec502dab1ea - Base helper contract:
0x005299b37703511b35d851e17dd8d4615e8a2c9b
Execution sequence per chain:
- EOA deploys one-shot helper contract.
- Helper constructs forged proof components on-chain.
- Helper calls
collectrepeatedly with unique nullifiers andrewardbits=7. - Verifier accepts forged proofs.
- Lottery transfers FOOM to attacker EOA.
This sequence is permissionless, reproducible on canonical state, and matches ACT criteria.
6. Impact & Losses
Deterministic balance diffs show:
- Ethereum lottery (
0x239af915abcd0a5dcb8566e863088423831951f8) lost19695576757802192910518134117126FOOM. - Base lottery (
0xdb203504ba1fea79164af3ceffba88c59ee8aafd) lost4588196709631799956807157941481FOOM. - Corresponding attacker EOAs gained exactly those amounts.
Representative balance-diff evidence:
{
"token": "0xd0d56273290d339aaf1417d9bfa1bb8cfe8a0933",
"holder": "0x46c403e3dcaf219d9d4de167ccc4e0dd8e81eb72",
"delta": "19695576757802192910518134117126"
}
{
"token": "0x02300ac24838570012027e0a90d3feccef3c51d2",
"holder": "0x73f55a95d6959d95b3f3f11ddd268ec502dab1ea",
"delta": "4588196709631799956807157941481"
}
FOOM-denominated fee treatment is deterministic:
- reference asset: FOOM
- exploit gas fees are paid in native ETH
- therefore
fees_paid_in_reference_asset = 0
Receipt-backed native gas fees:
- Ethereum seed tx fee:
423890852910344wei - Base seed tx fee:
19644923790540wei
7. References
- Ethereum seed tx trace:
0xce20448233f5ea6b6d7209cc40b4dc27b65e07728f2cbbfeb29fc0814e275e48 - Base seed tx trace:
0xa88317a105155b464118431ce1073d272d8b43e87aba528a24b62075e48d929d - Ethereum verifier:
0xc043865fb4d542e2bc5ed5ed9a2f0939965671a6(WithdrawG16Verifier.verifyProof) - Base verifier:
0x02c30d32a92a3c338bc43b78933d293ded4f68c6(WithdrawG16Verifier.verifyProof) - Ethereum lottery:
0x239af915abcd0a5dcb8566e863088423831951f8(FoomLottery.collect) - Base lottery:
0xdb203504ba1fea79164af3ceffba88c59ee8aafd(FoomLottery.collect) - Ethereum balance diff artifact for seed tx
- Base balance diff artifact for seed tx
- Deterministic fee summary:
/workspace/session/artifacts/auditor/iter_2/tx_fee_summary.json