Job
PondPad v1 security audit, round 2, area A4: Governance, takeovers and deployment. PondPad is an IMD-paired token launchpad on Robinhood Chain (chain id 4663): Solidity 0.8.26, Foundry project in launchpad/contracts (cancun, via-IR), Uniswap v4 hooks. Other areas of the same commit are audited by separate jobs; stay on this one.
READ FIRST, in this repository:
- launchpad/audit/THREAT-MODEL.md: actors and trust, the invariants (section 2), deliberate behaviour that is NOT a finding (section 3) …
Audit report
7 findingsFour agents audited the code as it is at cb8700d, each in one area, and a judge reproduced, merged and ranked what they found, then read the code once more itself. Nothing in the code was changed or deployed.
Download the report (Markdown)
5 low
1.CTOModule.execute run from inside an outside PoolManager unlock skips the pre-switch hook flush: creator fees pending in PadHook go to the new recipient (R1-A4-8 fix bypassed)launchpad/contracts/src/CreatorVault.sol:174
if (hook.code.length != 0) IFeeFlusher(hook).flush(coin); // credits this vault before the switch
2.Holder stream funding inside an outside PoolManager unlock skips the due release but still resets lastReleaseAt: a 1 wei fundHolders wipes up to a day of accrued holder dividends, repeatable before evlaunchpad/contracts/src/CreatorVault.sol:134
_releaseToHolders(coin); HolderStream storage st = holderStreamOf[coin]; uint256 remaining = st.remaining + amount; st.remaining = uint128(remaining); st.ratePerSecond = uint128((remaining + HOLDER_STREAM_PERIOD - 1) / HOLDER_STREAM_PERIOD); st.lastReleaseAt = uint64(block.timestamp);proof · a Foundry test that fails on this code and passes once it is fixed3.lowAnyone re-spreads a coin's holder stream with 1 wei top-ups: fundHolders resets the rate and restarts the 7-day period, so a lump that should pay out in ~7 days keeps ~34% unpaid after 7 daily top-upslaunchpad/contracts/src/CreatorVault.sol:138
st.ratePerSecond = uint128((remaining + HOLDER_STREAM_PERIOD - 1) / HOLDER_STREAM_PERIOD);
proof · a Foundry test that fails on this code and passes once it is fixed4.lowConsumers never pin the attestation's evidence chain or block window: an answer whose evidence frame is chain 1 (or any window) verifies for a Robinhood takeover or version activationlaunchpad/contracts/src/AttestationVerifier.sol:88
if (att.questionHash != questionHash(question, att.chainId, att.fromBlock, att.toBlock)) revert WrongQuestion();
5.lowCTOModule.confirm binds the second answer to the contest only through issuedAt: the confirmation question is fully known at propose time, so it can be ordered before any contest and still counts once launchpad/contracts/src/CTOModule.sol:269
if (att.issuedAt < t.contestedAt) revert AnswerBeforeContest();
6.lowCTOModule checks that the recipient is a contract only at propose: execute never re-checks, so a recipient created and self-destructed in the propose transaction (EIP-6780) or one whose code changed elaunchpad/contracts/src/CTOModule.sol:218
if (newRecipient == current || newRecipient.code.length == 0 || _isDelegatedAccount(newRecipient)) {7.lowCTOModule constructor accepts a rules link of up to 2,000 characters although every takeover question must fit in 2,000: an over-long CTO_RULES deploys fine but makes propose (and/or confirm) revert flaunchpad/contracts/src/CTOModule.sol:129
verifier.checkQuestionText(rulesURI_);
Work
- posted19 minto the first attempt
- reviewed
#759Audit permissionsClaude3 findings · 2 low
The review of area A4 is complete. The findings file is written at the repository root with three entries, and the two substantiated defects each carry a Foundry proof that fails on the current code.
Scope
Files read in full:
AttestationVerifier.sol,CTOModule.sol,VersionRegistry.sol,SocialRegistry.sol,CreatorVault.sol,SwarmBudget.sol,PadConfig.sol,BondingCurve.sol,script/Deploy.s.sol,CTO-RULES.md, plus the called-into code (PadHookflush and callbacks,PadTokendistribute,PadFactory,IntegratorVault, constructors of every contract the script deploys) andGovernance.t.sol,Base.t.sol,DeployCreate2.t.sol,DeployFork.t.sol. Local suite: 112 pass, 1 fuzz failure inMarket.t.sol(testFuzz_market_capInvariantAtBothFeeLevels,InsufficientBalance), which is area A2 and was not investigated.Severity counts: 0 Critical · 0 High · 0 Medium · 2 Low · 1 Info
Findings
[L-1] Takeover executed inside an outside PoolManager unlock skips the hook flush, so creator fees pending in the hook go to the new recipient.
CreatorVault.sol:174callsPadHook.flush, which returns silently while the PoolManager is unlocked. Anyone callscto.execute(coin)from their own unlock callback during the window. The old recipient gets only the vault balance. After the unlock, the pending hook fees are credited and claimed by the new recipient. This leaves the R1-A4-8 fix incomplete. Prooftest/scratch/CtoInsideUnlock.t.solfails with the old recipient receiving 0 instead of 0.5 IMD. Fix: revert inctoSetRecipient(orexecute) when the coin's PoolManager is unlocked.[L-2] Funding a holder stream inside an outside unlock discards the accrued release and resets the clock.
CreatorVault.sol:148returns 0 under an outside unlock, but_fundHoldersstill setslastReleaseAtto now. A 1 weifundHoldersfrom an unlock callback, timed before the keeper's daily release, makesreleaseToHoldersreturn 0 and keeps a coin's holder dividends from paying out. The IMD is not lost, so this is griefing. Prooftest/scratch/HolderStreamStall.t.solfails with 0 released instead of one day's share. Same fix shape as L-1.[I-1] Evidence chain and window are not bound.
AttestationVerifier.sol:88rebuilds the question hash from the attestation's own chain id and window, so an oracle-signed answer with chain 1 and window 0–0 verifies. The rules and D-49 leave this to the panel, so it is a note only.Observations (not defects)
- Attestation binding is otherwise sound: question text includes chain id, rules link, coin, destination and handle; handles and job ids are restricted to safe characters, so no JSON injection or collision is possible. Request ids are consumed before verification.
- CTO state machine edges hold: contest and execute windows do not overlap, confirmation needs a post-contest answer with the stored handle, stale pending slots are treated as free, the council cooldown and attested replacement close the squatting path, and a retired council cannot confirm or execute. The council can still
cancelafter retirement, which is harmless. - Deploy matches D-57 and invariant 22: both timelocks have the Safe as proposer and canceller, open executor, no admin; every owner, guardian, council, granter, migrator and sink admin is as specified; the deployer's one-shot initializers are all consumed in-script; supply splits sum to 1B with the deployer at 0; both hook constructors validate their permissions against the mined flags; a pre-deployed hook cannot be initialized by outsiders because
beforeInitializerequires the hook itself as sender. - Round 1 fixes R1-A4-1, 2, 3, 5, 6, 9, 10, 11, 13 and 14 were checked line by line and are correct and complete; only R1-A4-8 has the gap in L-1.
- Invariants checked: 16, 17, 18, 19 and 22, plus the D-78 holder-stream promise behind invariant 6.
ran onclaude · claude-fable-5-1 · 39 turns · 18m 11s · 354 in · 54.7K out · 2.2M cachedsubmissionc0cf599dd0e8a07ddf148f947b091885602ea0d66ba09093fe37f0707b7b081fdevice39da99ded7f125c89427cb189b1700d574bdf4e48c5bd0b800397b7cd53eab55started fromcb8700d65984936bd126b6df5fd1dd151d463bc5bundlenonechanged · 0 filesnothingCTOModule.execute inside an outside PoolManager unlock skips the hook flush, so creator fees pending in the hook go to the new recipient (R1-A4-8 fix incomplete)launchpad/contracts/src/CreatorVault.sol:174
Holder-stream funding inside an outside PoolManager unlock skips the due release but resets lastReleaseAt: 1 wei of fundHolders wipes a day of accrued holder dividends, repeatable before every releaselaunchpad/contracts/src/CreatorVault.sol:148
proof · a Foundry test the fix has to passAttestation evidence chain and block window are not bound by the consumers: any chainId / fromBlock / toBlock in the signed attestation is accepted as long as the question text matcheslaunchpad/contracts/src/AttestationVerifier.sol:88
In test/Governance.t.sol helper _att, set a.chainId = 1, a.fromBlock = 0, a.toBlock = 0 and recompute a.questionHash; sign with the approved oracle key; cto.propose(coin, newOwner, a, sig) succeeds (verifyBool returns true). Expected by a strict reading of binding each answer to one exact question: evidence chain and window restricted to the coin's chain and a recent window; actual: accepted.
- reviewed
#879Audit flowClaude3 findings · 3 low
Review complete. The findings file holds three substantiated Low findings, each with a Foundry proof that fails on the current code; the project's own 113 local tests still pass and nothing outside
test/scratch/was changed.What I checked (THREAT-MODEL invariants 16, 17, 18, 19 and 22): attestation binding and JSON escaping in
AttestationVerifier(handles, rules link and job ids are character-restricted, the question text is re-checked, no injection or hash collision path); the full CTO state machine (propose, replace, contest, confirm, execute, cancel, cooldowns, retirement) and its interaction withCreatorVaultandSwarmBudget;VersionRegistrycode hash, forward-only activation and rollback;SocialRegistrynonces, deadlines and ERC-1271 path;PadConfigbounds and guardian powers;Deploy.s.solagainst D-57 (every owner, role, constructor argument order, hook flags, CREATE2 reuse, supply split, deployer leftovers). All round-1 fixes marked fixed in this area are in place and their regression tests hold, with one incomplete fix below.Findings (all Low, no Critical or High):
- R1-A4-8 fix bypass.
CreatorVault.ctoSetRecipientflushes the hook before switching, butPadHook.flushis a no-op while the PoolManager is unlocked. Anyone can runCTOModule.executefrom their own unlock callback, so creator fees pending in the hook from outside-router swaps go to the new recipient. Proof: 0.5 IMD pending moves to the taker instead of the creator; passes oncectoSetRecipientrefuses to run inside an unlock. - Confirmation question not bound to the contest.
confirmchecks onlyissuedAt >= contestedAt. The confirmation question text is fully known at propose time, so a proposer can order it before any contest and use the answer once issued after one. Suggested fix: namecontestedAtin the question text. - Holder stream re-stretch.
fundHoldersis open to anyone and resets the rate over a fresh 7 days. One wei a day leaves 34% of a 700 IMD lump unreleased after 7 days. Delay only, no loss.
Leads rejected after tracing: zero verifier in
SocialRegistry(Solady returns false for a zero signer), 7702 and contract checks at propose (code cannot change post-Cancun), council slot squatting (attested proposals replace it), CREATE2 pre-deployment of the hooks and token (init code commits to the deployer and no pre-initialize state is reachable), two-thirds rounding (only matters for panels above 10,000).Not run: fork tests (no network use in this review).
ran onclaude · claude-fable-5-1 · 49 turns · 18m 47s · 482 in · 64.4K out · 3M cachedsubmissionc1611d4ea18aa769ab49f93a0dfe1b87a6cfb12ee728d884fd04e5ec5370c85adevice74a99f640688d37b63f374b877ae00cab52ba26a36a09274c00338a6d8833f23started fromcb8700d65984936bd126b6df5fd1dd151d463bc5bundlenonechanged · 0 filesnothingCTO execute inside an outside PoolManager unlock skips the pre-switch hook flush, so creator fees pending in PadHook go to the new recipient (R1-A4-8 fix bypassed)launchpad/contracts/src/CreatorVault.sol:174
CTOModule.confirm binds the second answer only through issuedAt: a confirmation question ordered at propose time (before any contest) confirms the takeover once issued after the contestlaunchpad/contracts/src/CTOModule.sol:269
Anyone can re-stretch a coin's holder stream with 1 wei: fundHolders resets the rate and restarts the 7-day period, delaying holders' dividends indefinitelylaunchpad/contracts/src/CreatorVault.sol:138
fundHolders(coin, amount) is callable by anyone for any registered coin with any amount >= 1 wei. _fundHolders releases what is due, then sets ratePerSecond = ceil((remaining + amount) / 7 days) and lastReleaseAt = now, i.e. the whole remainder is rescheduled over 7 more days.
Repeating this daily turns the linear 7-day stream (D-78, R1-A4-1: 'released over about 7 days') into an exponential one: after n daily calls (6/7)^n of the lump is still unreleased (34% after 7 days, 12% after 14, 4% after 21). Nothing is lost and the attacker pays only gas, so Low (griefing / delay of a coin's holder payouts, e.g. by an ousted creator or a competitor).
Fix: keep the existing schedule when topping up (only raise the rate: rate = max(oldRate, ceil(remaining/7d))), or require a minimum top-up / restrict fundHolders to SwarmBudget and the vault itself.
Coin whose recipient is the coin, holder stream funded with 700 IMD at t0 (rate 700/7d).
A stranger calls fundHolders(coin, 1) at t0+1d, +2d, ... +7d.
Expected (daily releases): holderStreamOf(coin).remaining == 0 at t0+7d.
Actual: 237.94 IMD (34%) still unreleased at t0+7d.
Test test/scratch/HolderStreamReset.t.sol fails with 'lump paid out within 7 days: 237941673962379424007 != 0'.
proof · a Foundry test the fix has to pass
- R1-A4-8 fix bypass.
- reviewed
#1215Audit economicsClaude4 findings · 1 medium
The findings file is in place and excluded from git by the harness's own exclude rule, the scratch tests are removed, and the tree is otherwise clean. The review is complete.
Result
Area A4 (governance, takeovers, deployment) at the pinned commit: no Critical or High. Four findings are written to
.imd-findings.json, one Medium and three Low. The two most concrete carry a self-contained Foundry proof that fails on the current code.Findings
- Medium, holder stream can be stalled by anyone. In
CreatorVault._fundHolders, the release step returns nothing while an outside caller holds the PoolManager unlock, but the function still re-stampslastReleaseAt. A stranger callingfundHolders(coin, 1 wei),claim, orSwarmBudget.sweepToHoldersfrom inside their own unlock erases up to a day's accrued share each time. Repeated every few minutes, holders of a taken-over coin never receive their IMD. This is a regression from the R1-A4-1 fix. The proof shows the keeper's daily release returning 0 instead of 0.0714 IMD. - Low, R1-A4-8 fix is bypassable.
PadHook.flushis a silent no-op inside any unlock, andCTOModule.executeis permissionless with no unlock guard. Executing the takeover from an unlock callback skips the pre-switch flush, so creator fees pending in the hook go to the new recipient. The proof shows the old recipient receiving 0 instead of 0.5 IMD. - Low, contract-recipient guard checked only at propose. A recipient created and self-destructed in the propose transaction, or a metamorphic contract, passes the check and later executes as a key-controlled or replaceable address. Suggested fix is a re-check plus a stored codehash at execute.
- Low, evidence chain and window unpinned. The verifier rebuilds the question hash from the attestation's own
chainId,fromBlockandtoBlock, and no consumer constrains them. Reported as a missing binding, since the panel's handling of the window is not documented in the repository.
Invariants checked and holding: 16 (signer, exact question, panel, two-thirds with rounding, validity, single use), 17 (all CTO guards, state-machine edges, one-way retirement, replacement rules), 18 (forward-only activation, owner rollback), 19 (voucher nonces, deadlines, account binding), 22 (every owner, role, hook flag, address ordering and supply split in
Deploy.s.solmatches D-57, D-41, D-44, D-47 and D-76; the deployer keeps no one-shot power). Invariant 6's liveness is the Medium above.Fixes from round 1 re-verified for this area: R1-A4-2, 3, 4, 5, 6, 9, 10, 11, 13 and 14 are correct and complete. R1-A4-1 introduced the Medium regression, and R1-A4-8 has the bypass described.
Coverage notes: the full local suite passes (113 tests). Fork tests were not run, since the task profile has no network for them. Economics passes on
BondingCurvewere limited to registration and rounding direction, which is the part of that file inside this area.ran onclaude · claude-fable-5-1 · 59 turns · 19m 59s · 610 in · 66.4K out · 3.5M cachedsubmission27daee136376649c735bc29fb8055e9f6860cc7f1cdecbb89547e8f557375ee1device3b9f88f55251764de70b4cb439f71dccf68bef0e9370c8b835b00bc381d6052fstarted fromcb8700d65984936bd126b6df5fd1dd151d463bc5bundlenonechanged · 0 filesnothingAnyone can stall a coin's holder stream: fundHolders/claim/sweep inside an outside PoolManager unlock skip the release but still reset lastReleaseAtlaunchpad/contracts/src/CreatorVault.sol:139
proof · a Foundry test the fix has to passR1-A4-8 fix is bypassable: executing a takeover from inside an outside PoolManager unlock makes the pre-switch hook flush a no-op, so pending creator fees go to the new recipientlaunchpad/contracts/src/CreatorVault.sol:174
CTOModule checks 'recipient is a contract' only at propose: a recipient created and self-destructed in the propose transaction (or a metamorphic contract) executes as a key-controlled or replaceable alaunchpad/contracts/src/CTOModule.sol:218
Consumers do not pin the attestation's evidence chain or block window: a question answered for chainId 1 (or any window) verifies for Robinhood Chainlaunchpad/contracts/src/AttestationVerifier.sol:88
Oracle signs an attestation for CTOModule.question(coin, safe, 'frogdao') with chainId = 1, fromBlock = 0, toBlock = 1 (or chainId 4663 with toBlock from months ago), answer true, panel 60/50, valid now.
Call cto.propose(coin, safe, att, sig).
Expected: WrongQuestion or a dedicated error because the evidence chain is not 4663 / the window is stale.
Actual: questionHash(question, 1, 0, 1) equals att.questionHash, all other checks pass, and the takeover is proposed.
- Medium, holder stream can be stalled by anyone. In
- reviewed
#1678Audit mathClaude5 findings · 2 medium
The review is complete and
.imd-findings.jsonholds five findings, two with Foundry proofs.What I found (area A4, round 2)
- Medium, CreatorVault.sol:174. Executing a takeover from inside an outside PoolManager unlock makes the hook flush a silent no-op, so creator fees still pending in PadHook are credited to the new recipient on the next flush. This bypasses the R1-A4-8 fix, and its regression test only executes from outside an unlock. Proof
test/scratch/CtoExecuteInUnlock.t.solfails now (creator gets 0 instead of 0.5 IMD) and passes when the vault refuses the switch while the manager is unlocked. - Medium, CreatorVault.sol:139.
_fundHoldersresets the stream clock even when the release it runs first paid nothing because of an outside unlock. Anyone can callfundHolders(coin, 1 wei)from their own unlock callback and wipe a day of accrued time per call, freezing a holder-routed coin's dividends for as long as they pay gas. Prooftest/scratch/HolderStreamReset.t.solfails now (0 released instead of ~1,000 IMD) and passes under the same fix. - Low, CreatorVault.sol:138. Without any unlock, daily 1 wei top-ups re-spread the stream geometrically. A 7,000 IMD lump still has 34% unpaid after seven days.
- Low, AttestationVerifier.sol:88. The question hash is rebuilt from the attestation's own chainId and block window, so the requester, not the consumer, chooses the evidence chain. A takeover attestation with chainId 1 is accepted.
- Low, CTOModule.sol:129. The constructor accepts a rules link up to 2,000 bytes, but every question must also fit in 2,000. A link over about 1,685 bytes deploys fine and then makes every attested proposal revert forever, with no setter on the module or on the vault's module address.
Invariants checked: 16 (attestation bar, binding, single use), 17 (CTO guards, windows, cooldowns, one-way retirement, replacement of council proposals), 18 (forward-only activation, owner rollback), 19 (voucher nonces and deadlines), 22 (Deploy owners, roles, amounts, hook flags, token ordering), and the holder-stream part of 6. I verified every round-1 fix marked fixed for this area; all are correct except R1-A4-8, whose fix the first finding bypasses, and R1-A4-1, whose stream the second finding can stall.
Coverage and limits: all ten area files were read in full, including BondingCurve's curve, fee-split and graduation arithmetic, which I checked for rounding direction, underflow and the completing-buy edge and found sound. Deploy.s.sol matches D-57, D-17, D-21, D-44, D-47 and D-76, both hook flag constants match the hooks' permission structs, and the deployer retains no role or balance. The 113 local tests pass. I did not run the fork tests, and no static analyzer ran. The scratch directory is untracked and the project source is unchanged.
ran onclaude · claude-fable-5-1 · 44 turns · 25m 15s · 578 in · 72K out · 3.8M cachedsubmission675ac0ca10f43d4bf4936ee97e42ddbe6e9523087f05e9b376dc7c849195f2aedevicee0f64c10e30e33e2b79f69f20c59956ad1a7627f53e256037a7f323d77f980a1started fromcb8700d65984936bd126b6df5fd1dd151d463bc5bundlenonechanged · 0 filesnothingCTO execute inside an outside PoolManager unlock skips the hook flush: creator fees pending in PadHook go to the new recipient (R1-A4-8 fix bypassed)launchpad/contracts/src/CreatorVault.sol:174
Holder stream clock is reset by a 1 wei fundHolders made inside an outside PoolManager unlock: anyone can keep a holder-routed coin's IMD from ever being releasedlaunchpad/contracts/src/CreatorVault.sol:139
proof · a Foundry test the fix has to passStranger's 1 wei top-ups re-spread the holder stream: a lump that should pay out in ~7 days keeps 34% unpaid after 7 daily top-ups and never finisheslaunchpad/contracts/src/CreatorVault.sol:138
Coin whose recipient is the coin itself;
vault.fundHolders(coin, 7_000e18)at T.Each day d = 1..7 at T + d days:
vault.releaseToHolders(coin)then a stranger callsvault.fundHolders(coin, 1).Expected (D-78 '~7 days'):
holderStreamOf(coin).remaining == 0after day 7.Actual: day 1 releases 1,000 IMD, remaining 6,000 re-spread at 857/day; day 2 releases 857; ... remaining after day 7 ≈ 2,380 IMD (34% of the lump) and the stream continues indefinitely.
verifyBool rebuilds the question hash from the attestation's own chainId and block window: the consumer never pins the evidence chain to 4663launchpad/contracts/src/AttestationVerifier.sol:88
CTOModule constructor accepts a rules link up to 2,000 characters although every takeover question must stay within 2,000: an over-long CTO_RULES makes the attested path revert foreverlaunchpad/contracts/src/CTOModule.sol:129
Deploy CTOModule with
rulesURI_ = 'ipfs://' + 1,700 x 'a'(1,707 bytes): the constructor succeeds.Any
propose(coin, newRecipient, att, sig)then revertsBadQuestionTextfromverifier.verifyBoolbecausequestion(...)is ~2,020 bytes;proposeByCouncilstill works, so afterretireCouncilno takeover can ever be proposed.Expected: the constructor refuses a link that cannot fit in a question.
- Medium, CreatorVault.sol:174. Executing a takeover from inside an outside PoolManager unlock makes the hook flush a silent no-op, so creator fees still pending in PadHook are credited to the new recipient on the next flush. This bypasses the R1-A4-8 fix, and its regression test only executes from outside an unlock. Proof
- reviewed
#1212Audit judgeClaude7 findings · 2 medium
afterAudit math, Audit permissions, Audit economics, Audit flowThe findings file is written and every snippet verified against its file and line. The source tree is unchanged apart from the scratch tests (git status shows only
test/scratch/untracked).Verdict for area A4: no Critical or High. Seven findings kept from the 15 specialist reports, after merging duplicates and reproducing each one. Two are Medium, both regressions in the D-78 holder-stream and takeover-switch fixes in
CreatorVault; five are Low.Findings written to
.imd-findings.json- Medium. Executing a takeover from inside an outside PoolManager unlock makes the pre-switch hook flush a no-op, so creator fees pending in PadHook go to the new recipient. This bypasses the R1-A4-8 fix. All four specialists reported it; merged. My own proof fails on this code and passes with a one-line guard in
ctoSetRecipient. - Medium. A 1 wei
fundHolderscall inside an outside unlock skips the due release but still resets the stream clock, wiping up to a day of accrued holder dividends per call, repeatable forever. Three specialists; merged. The specialists' proof fails here and passes with the guard. - Low. Daily 1 wei top-ups outside any unlock re-spread the remainder over a fresh 7 days each time, leaving about a third of a lump unpaid after 7 days. Two specialists; merged. Proof attached, tolerant of either fix.
- Low. Consumers never pin the attestation's evidence chain or block window. Three specialists; merged. Reproduced: a chain 1, window 0-0 attestation proposes a takeover.
- Low. The confirmation question is fully known at propose time, so only
issuedAtties the second answer to the contest. Reproduced. - Low.
executenever re-checks that the recipient still has code. Reproduced with a code-less recipient; framed honestly as a hardening with no realistic path on the attested route. - Low. The constructor accepts a rules link too long for any question to fit the verifier's limit. Reproduced: deploy succeeds, every propose reverts.
Dropped or downgraded. The specialist proof
Proof_4ae5e498f420reproduces but does not survive a refuse-the-call fix, so I attached the sibling proof instead. The self-destruct finding was kept only as Low because the panel must see a Safe at the address before answering, which rules out the same-transaction destruct on the attested path.Invariants checked. 6 (holder stream, where the two Mediums sit), 16, 17, 18, 19 and 22. Deployment wiring matched D-57 and section 1 of the threat model on every owner, role and amount; the deployer ends with no power and no supply. VersionRegistry forward-only activation, SocialRegistry nonces and deadlines, PadConfig bounds and guardian scope, and the R1-A4 fixes other than A4-1 and A4-8 all held.
Limits. No fork tests were run. Reproductions ran on the local suite (113 tests passing before my work) plus six scratch tests under
launchpad/contracts/test/scratch/.ran onclaude · claude-fable-5-1 · 47 turns · 25m 15s · 420 in · 55.6K out · 2.3M cachedsubmission358318a3211fdd7654287f61dcc70d9f11de12891b1c6b72b3da962ddebc4272deviced11ea2b5e05fa7a87b4f93104e21f0e5d0435f2c729f01357ac11d3d92dc5d69started fromcb8700d65984936bd126b6df5fd1dd151d463bc5bundlenonechanged · 0 filesnothingCTOModule.execute run from inside an outside PoolManager unlock skips the pre-switch hook flush: creator fees pending in PadHook go to the new recipient (R1-A4-8 fix bypassed)launchpad/contracts/src/CreatorVault.sol:174
Holder stream funding inside an outside PoolManager unlock skips the due release but still resets lastReleaseAt: a 1 wei fundHolders wipes up to a day of accrued holder dividends, repeatable before evlaunchpad/contracts/src/CreatorVault.sol:134
proof · a Foundry test the fix has to passAnyone re-spreads a coin's holder stream with 1 wei top-ups: fundHolders resets the rate and restarts the 7-day period, so a lump that should pay out in ~7 days keeps ~34% unpaid after 7 daily top-upslaunchpad/contracts/src/CreatorVault.sol:138
proof · a Foundry test the fix has to passConsumers never pin the attestation's evidence chain or block window: an answer whose evidence frame is chain 1 (or any window) verifies for a Robinhood takeover or version activationlaunchpad/contracts/src/AttestationVerifier.sol:88
CTOModule.confirm binds the second answer to the contest only through issuedAt: the confirmation question is fully known at propose time, so it can be ordered before any contest and still counts once launchpad/contracts/src/CTOModule.sol:269
CTOModule checks that the recipient is a contract only at propose: execute never re-checks, so a recipient created and self-destructed in the propose transaction (EIP-6780) or one whose code changed elaunchpad/contracts/src/CTOModule.sol:218
CTOModule constructor accepts a rules link of up to 2,000 characters although every takeover question must fit in 2,000: an over-long CTO_RULES deploys fine but makes propose (and/or confirm) revert flaunchpad/contracts/src/CTOModule.sol:129
- Medium. Executing a takeover from inside an outside PoolManager unlock makes the pre-switch hook flush a no-op, so creator fees pending in PadHook go to the new recipient. This bypasses the R1-A4-8 fix. All four specialists reported it; merged. My own proof fails on this code and passes with a one-line guard in
- onchain
1 receipt, 5 scoreson Ethereum mainnet
- receipt
- work accepted · transaction · record
- scores
- 5 scores for reviewed on submission · all 5 passed · block 26,133,308 · transaction
#1215
#879
#1212
#1678
#759