We do not have a reliable USD price for the recorded assets yet.
0x239af915abcd0a5dcb8566e863088423831951f8Ethereum0xdb203504ba1fea79164af3ceffba88c59ee8aafdBaseTwo seed transactions on Ethereum and Base drained FOOM from FOOM Lottery contracts by forging withdraw proofs and repeatedly calling collect.
0xce20448233f5ea6b6d7209cc40b4dc27b65e07728f2cbbfeb29fc0814e275e48 (block 24539650)0xa88317a105155b464118431ce1073d272d8b43e87aba528a24b62075e48d929d (block 42650623)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.
FOOM Lottery payout redemption path requires a Groth16 proof through withdraw.verifyProof(...) and uses public signals:
_root_nullifierHash_rewardbitsThe contract requires:
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.
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;
Safety invariant:
verifyProof for supplied public signals.Code-level breakpoint:
WithdrawG16Verifier hardcodes delta == gamma.FoomLottery.collect uses 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");
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:
LogWin emissionsLogWin emissionsThe exploit requires only:
roots mapping,These conditions are publicly observable and permissionless.
Adversary-related accounts in seed flow:
0x46c403e3dcaf219d9d4de167ccc4e0dd8e81eb720x256a5d6852fa5b3c55d3b132e3669a0bde42e22c0x73f55a95d6959d95b3f3f11ddd268ec502dab1ea0x005299b37703511b35d851e17dd8d4615e8a2c9bExecution sequence per chain:
collect repeatedly with unique nullifiers and rewardbits=7.This sequence is permissionless, reproducible on canonical state, and matches ACT criteria.
Deterministic balance diffs show:
0x239af915abcd0a5dcb8566e863088423831951f8) lost 19695576757802192910518134117126 FOOM.0xdb203504ba1fea79164af3ceffba88c59ee8aafd) lost 4588196709631799956807157941481 FOOM.Representative balance-diff evidence:
{
"token": "0xd0d56273290d339aaf1417d9bfa1bb8cfe8a0933",
"holder": "0x46c403e3dcaf219d9d4de167ccc4e0dd8e81eb72",
"delta": "19695576757802192910518134117126"
}
{
"token": "0x02300ac24838570012027e0a90d3feccef3c51d2",
"holder": "0x73f55a95d6959d95b3f3f11ddd268ec502dab1ea",
"delta": "4588196709631799956807157941481"
}
FOOM-denominated fee treatment is deterministic:
fees_paid_in_reference_asset = 0Receipt-backed native gas fees:
423890852910344 wei19644923790540 wei0xce20448233f5ea6b6d7209cc40b4dc27b65e07728f2cbbfeb29fc0814e275e480xa88317a105155b464118431ce1073d272d8b43e87aba528a24b62075e48d929d0xc043865fb4d542e2bc5ed5ed9a2f0939965671a6 (WithdrawG16Verifier.verifyProof)0x02c30d32a92a3c338bc43b78933d293ded4f68c6 (WithdrawG16Verifier.verifyProof)0x239af915abcd0a5dcb8566e863088423831951f8 (FoomLottery.collect)0xdb203504ba1fea79164af3ceffba88c59ee8aafd (FoomLottery.collect)/workspace/session/artifacts/auditor/iter_2/tx_fee_summary.json