{"assessments":[],"deployments":[{"attestationHash":"1428ad2b6703a21961721f7d4d275ec455b9d842d0988b2f5a65e24bcff659cf","chainId":11155111,"contracts":[{"address":"0xe2470d2cc19043f7ff49cb55423eb82a7b632280","blockNumber":12077096,"name":"SqueekPredict","role":"other","txHash":"0x8714c38c38effc160d1d1c8f2affdc16644919d5a37221fa4c1c870f95b91381"},{"address":"0x8b1942e523aa6fb761b1c1019ba9b3f99213b887","blockNumber":12077096,"name":"SqueekMetrics","role":"other","txHash":"0x8714c38c38effc160d1d1c8f2affdc16644919d5a37221fa4c1c870f95b91381"}],"id":"929d27a7-83d8-4f79-a1d7-640aa6d834c2","manifestHash":"33d17a9a6bfd3a1f37f800834ec7d32fae14b2139e7cc3b46396f89fbfd9f5fd","status":"live"}],"fuzz":[],"identity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"interpretation":"Records acceptance and evidence. Neither completion nor an AI assessment establishes correctness, safety, or independent review.","jobId":"91b58f78-cc61-4b4f-81d9-ac69c93c4f33","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"1eb51910154f967dd73cbf6b7cb58add3a34c1e21ec2a145d71584c72bbbb632","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"08dd3c6a4270e50e493649040df6c17388711eab00209bb614e9d69e22560f28","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"9b6b77532c5e3ea3043558a2c76501128ef7a738bea88252e3816e1708a10869","dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"b89279670f5a00f5d26286edee00c97dd158b997031350a01cafe6e0329ad227","skillId":"audit-judge","subagents":4,"tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"b89279670f5a00f5d26286edee00c97dd158b997031350a01cafe6e0329ad227","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"ad2f814b91fa1f220f0bd55f6cfc44dc507ed059a9da11b8f6798d3428b12531","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"658194e87110c311f4fa0c8f69d767792da6f723fc34fe9cc747989d77723962","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"f05a174cee06025e3911caf5d07f63fb667393f2fa3b33a13eb7e8f7a62f5749","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"15b04fc8f60521936b9e2b37c09a804685ec689fc98cd5b04510d986d3106a15","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"15b04fc8f60521936b9e2b37c09a804685ec689fc98cd5b04510d986d3106a15","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"7b6341312de05194e9fdef31c1615763b3693c7d4ab20c73e5f03d44e71e5e63","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"a5566dfa71938509a70f3d7b23ff4e8e2d283585193a92be7e6036b44eca494e","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"68506fdcd4ed07ed9dcf40cddecfe9a7d7282e399586d1d75a902eca2f5d692b","skillId":"write-foundry-tests","subagents":4,"tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"68506fdcd4ed07ed9dcf40cddecfe9a7d7282e399586d1d75a902eca2f5d692b","skillId":"write-foundry-tests","state":"accepted"}],"objective":"Build a complete, professional Polymarket-style prediction market called Squeek Predict on Sepolia.\n\nCORE CONCEPT:\nA clean prediction market where users connect their wallet and bet on pre-existing markets. No user-created markets. Always 12 to 15 active markets. Everything is fully automatic.\n\nCONTRACT REQUIREMENTS:\n- Only 2-outcome markets (Yes/No or Over/Under)\n- Odds start at 50/50 and adjust dynamically based on the amount bet on each side (parimutuel model)\n- All markets must resolve automatically by reading on-chain data from SqueekCore (0x74665dAecA35A2303e767E76a2FF1FB6F93e119f) or other public Sepolia/Ethereum data\n- Always maintain between 12 and 15 active markets\n- When a market ends: automatically resolve it, pay winners proportionally from the pool, and create a new similar market so the count stays constant\n- Apply a 1% fee on every bet (fee stays in the contract)\n- Self-financing: no need for owner to inject liquidity\n- Deploy on Sepolia\n\nMARKET TYPES TO INCLUDE (all auto-resolvable):\n- Over/Under Squeek blocks in next 6 hours\n- Over/Under Squeek blocks in next 24 hours\n- Over/Under Squeek blocks in next 7 days\n- Longest continuous hamster run in next 24h\n- Morning vs Evening hamster activity\n- ETH price Over/Under in 24h\n- ETH price Over/Under in 7 days\n- BTC price Over/Under in 24h\n- Ethereum gas price Over/Under in 24h\n- Number of Ethereum blocks in next 24h\n- Other simple on-chain metrics that can be verified automatically\n\nWEBSITE REQUIREMENTS:\n- Modern, clean, professional dark theme inspired by Polymarket\n- Homepage displays all active markets as cards\n- Each card shows: question, current odds, two large buttons to bet, time remaining, volume\n- Users connect wallet and click a side to bet (simple modal)\n- Clear \"My Positions\" page\n- Mobile-friendly\n- Subtle hamster branding, but overall design must stay professional and serious\n- Fast and easy to understand\n\nDELIVER:\n- Deployed contracts on Sepolia\n- Live website\n- Clean README\n\nThe final product must be immediately usable, self-sustaining, and look like a real prediction market platform.","parentJobId":null,"planHash":"2fa5b6ca8cb1a90f5ca8ced1d0de61ade0e185ffc5413695a8302582f06f041b","previousHash":"e5d1decef07e57829776e33d7af14f032a513557f937cdc8427cd4d2ddcad703","projectId":"91b58f78-cc61-4b4f-81d9-ac69c93c4f33","publication":{"commit":"3e5139213be2c6896df4c60fa66b0e9a0145ad0f","deliveredAt":"2026-11-06T14:15:25.527Z","repoUrl":"https://github.com/identity-md-launches/launch-3047-build-complete-professional-polymarket-s"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[],"site":null,"snapshotHash":"8aca32d29d56606d00c87f25da14406a67ea6f3067754c596c57589138208cfd","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"f3b611cf8afb513f","findings":[{"citation":"resolved","description":"checkUpkeep answers needed=true for every Scheduled lane, but performUpkeep only reaches _tryOpen, which swallows the _open revert (CoreStale, StalePrice, feed/Core call failure) at src/SqueekPredict.sol:391 and emits ActivationDeferred. The query function and the operation therefore diverge: while the specified SqueekCore is more than six hours behind (its current Sepolia state, certified through 2026-10-26) or a Chainlink opening observation is older than two hours, every automation cycle is told to act and then changes no state. A Chainlink Automation or similar trigger wired to this pair would pay a full performUpkeep transaction every block indefinitely. Execution Trace: query function differs from the function used for the operation.","line":231,"path":"src/SqueekPredict.sol","reproduction":"State: Core.state()/configuration() unavailable or stale (test: core.setBroken(true)), feeds older than 2h. 1) warp 8 days, maintain() -> 11 lanes stay Scheduled. 2) checkUpkeep(\"\") returns (true, \"\"). 3) performUpkeep(\"\") succeeds, nextMarketId unchanged, no lane leaves Scheduled, only ActivationDeferred events. 4) repeat: checkUpkeep still true forever. Expected: checkUpkeep returns false (or a probe of _open's view-only preconditions) when no lane can actually be opened or rolled. Reproduced in test/scratch/Flow.t.sol testUpkeepNoProgressLoop (5 consecutive no-op cycles). Fix that keeps the design: in checkUpkeep, for Scheduled lanes replicate _open's view preconditions (metrics.window / requireLive, feed decimals + latestRoundData freshness) with try/catch on the external view calls, and only report needed when at least one would succeed or an Open lane has ended.","severity":"low","snippet":"            if (m.status == Status.Scheduled || (m.status == Status.Open && block.timestamp >= m.endsAt)) {","title":"checkUpkeep reports work while performUpkeep cannot make progress on lanes whose data source is unavailable"},{"citation":"resolved","description":"settleNative authenticates the boundary header against BLOCKHASH, which only covers the last 256 blocks (about 51 minutes on Sepolia). Once the boundary block is older than that, settleNative always reverts UnprovenHeader and the only terminal transition left is expire() at endsAt + 48h, which refunds net stakes. So for slots 8, 9 and 13 the effective resolution window is ~51 minutes, not the 48-hour RESOLUTION_GRACE that the other lanes get, and the brief's 'automatically resolve, pay winners' depends on a single keeper never being down for an hour across a market end. The author documents this in docs/OPERATIONS.md; it is reported because it is a liveness single point of failure that changes who gets paid (winners are repaid their stake rather than the pool) under an ordinary operational condition, and a design-preserving fix exists.","line":24,"path":"src/libraries/BlockHeader.sol","reproduction":"State: market 9 (EthBlocks 1d) Open with alice 1 ETH Yes and bob 1 ETH No; boundary block 8201 at endsAt+3. 1) roll to block 8201+257 and warp endsAt+257*12; setBlockhash(8201, keccak(target)). 2) settleNative(9, parent, target) -> reverts UnprovenHeader although the header is genuine. 3) warp endsAt+2 days; expire(9) -> status Refunded, alice gets 0.99 ETH back instead of 1.98 ETH. Reproduced in test/scratch/Flow.t.sol testNativeLaneRefundsAfter256Blocks (also shown by existing testForgedHeaderLaterSampleAndExpiredHashAreRejected at roll 8458). Fix: on Prague-enabled Sepolia read older hashes from the EIP-2935 history contract (8191-block window, ~27h) as a fallback when target.number is older than 256 blocks, or accept a bounded parentHash chain from a recent (BLOCKHASH-provable) header back to the boundary header.","severity":"low","snippet":"            target.number >= block.number || block.number - target.number > 256","title":"Native lanes (BaseFee, EthBlocks) can only be resolved within 256 blocks of expiry; a one-hour settler outage forces a refund instead of paying winners"},{"citation":"resolved","description":"_requireLive reverts CoreStale when the Core's certified frontier is more than six hours behind. window() calls it, so _open for the seven Squeek kinds (slots 0,1,2,3,4,10,11) reverts and those lanes remain Scheduled; bet() on them reverts too. docs/SOURCES.md records that the brief-specified Core (0x74665dAecA35A2303e767E76a2FF1FB6F93e119f) was certified only through 2026-10-26 16:20 UTC on 2026-11-06, so at deployment the product will expose at most 7 bettable markets, below the brief's 'always 12 to 15 active markets'. The fail-closed behaviour itself is correct (no stake can be taken against unknowable data); this is a brief-compliance gap the requester must decide on. The README states it openly.","line":60,"path":"src/SqueekMetrics.sol","reproduction":"State: core.state().settledThroughRound such that genesis + round*600 < block.timestamp - 6h (live Sepolia state today; in tests warp 6h+1s past the frontier). 1) maintain() -> ActivationDeferred for slots 0,1,2,3,4,10,11; getMarket(id).status == Scheduled. 2) bet{value:1 ether}(id,1) -> reverts CoreStale. Expected per brief: >=12 markets accepting bets. Actual: 7 (or 3 when Chainlink opening rounds are also >2h old, see test/scratch/Flow.t.sol testUpkeepNoProgressLoop: 11 Scheduled). Covered by existing testPausedAndStaleCoreRejectNewStakesButPreserveRefunds. Resolution needs a scope decision: either accept the reduced count while the source is stalled, or add more non-Squeek lanes so >=12 remain bettable without the Core.","severity":"low","snippet":"        if (certifiedAt > block.timestamp || block.timestamp - certifiedAt > MAX_REPORTING_DELAY) {","title":"Bettable market count falls below the required 12 whenever the specified SqueekCore is stale; currently 7 of 14 lanes cannot open"},{"citation":"resolved","description":"For Kind.MorningEvening, window(.., daily=true) sets startsAt to the next UTC midnight and closesAt == startsAt. The lane is then Open but rejects bets (BettingClosed) for the whole observed day, and the bettable period equals the time left until midnight at open. Opened at 23:50 UTC it accepts bets for 600 seconds and then displays as an active market that cannot be bet for 86,400 seconds. Correct for the stated semantics ('next complete UTC day') and no funds at risk; reported so the UI/time-remaining display and the 'active market' count treat Open-but-closed lanes explicitly.","line":383,"path":"src/SqueekPredict.sol","reproduction":"warp to midnight-600; deploy; open(5); getMarket(5): closesAt - now == 600, endsAt - closesAt == 86400. bet(5,..) at closesAt reverts BettingClosed while status == Open. Reproduced in test/scratch/Flow.t.sol testDailyLaneBettableWindow.","severity":"info","snippet":"        m.closesAt = kind == Kind.MorningEvening ? startsAt : startsAt + duration / 2;","title":"MorningEvening lane closes betting at its start, so it is unbettable for 24h of every cycle and only 600s when opened at 23:50 UTC"},{"citation":"resolved","description":"SqueekCore at the specified address is an EIP-1967 proxy (verified implementation 0x0a71c86a...) with guardian and reporter roles; checkpoints are whatever the reporter signs. _genesis() re-checks roundDuration and blockUnitsPerBlock but not genesisTimestamp or streamId, so an implementation upgrade or re-initialisation that moves genesis would silently shift which rounds a funded market measures (startsAt-genesis)/600. I verified against the Sourcify source that the current implementation's round numbering (round r covers [genesis+(r-1)*600, genesis+r*600)), nonzero-work checkpoints, 16-record pages and certifiedThroughRound semantics match SqueekMetrics.measure exactly, so this is a documented trust boundary, not a defect in the code as deployed. Low-cost hardening: capture configuration().genesisTimestamp in the market at open and pass it to measure, reverting (fail closed to refund) if it changed.","line":131,"path":"src/SqueekMetrics.sol","reproduction":"State: Core guardian upgrades implementation so configuration().genesisTimestamp differs from the value at open while roundDuration and blockUnitsPerBlock are unchanged. settleSqueek(id) -> measure maps startsAt/endsAt to different rounds; result reflects a different window; winners differ from the intended ones. Requires privileged third-party action; reported as a trust assumption only.","severity":"info","snippet":"        if (c.roundDuration != ROUND_SECONDS || c.blockUnitsPerBlock != BLOCK_UNITS) {","title":"Trust assumption: seven lanes resolve on data from a third-party upgradeable proxy whose semantics are only partially re-checked"}],"hash":"08dd3c6a4270e50e493649040df6c17388711eab00209bb614e9d69e22560f28","nodeId":"224ead5f-1d0e-492d-84a5-d3d653bf1ec6","outcome":"completed","summary":"Review complete. The findings file is written and every quoted snippet matches the tree. I changed no source files; only `.imd-findings.json` and scratch tests under `test/scratch/` were created.\n\n**Outcome: no medium-or-higher defects in the control-flow area.** The core state machine is sound. Each slot has exactly one unrolled market, so a roll triggered from a settlement never duplicates a replacement. Settlement and expiry windows are complementary with no overlap. Payout accounting is symmetric on the deferred-push path, and every external call sits behind the reentrancy guard with effects before interactions, so the slither and aderyn reentrancy leads are false positives.\n\n**External assumptions I verified rather than took on faith:**\n- SqueekCore's verified implementation on Sourcify numbers rounds from 1 with the same interval formula, stores checkpoints only for nonzero-work rounds, caps pages at 16 and stops certification just before the first omitted round. All of this matches what SqueekMetrics assumes.\n- Both Chainlink Sepolia feeds have a 1-hour heartbeat, so the 2-hour staleness gate has headroom.\n- Opening a lane costs roughly 100k gas with mocks, well inside the 300k self-call stipend.\n\n**Findings recorded (3 low, 2 info):**\n1. **Low.** `checkUpkeep` reports work for Scheduled lanes that `performUpkeep` cannot open while a source is stale. An automation trigger would pay for a no-op transaction every cycle. Reproduced with five consecutive no-progress cycles.\n2. **Low.** Native lanes can only be resolved inside the 256-block BLOCKHASH window, about 51 minutes on Sepolia. A one-hour settler outage turns a decided market into a net refund. The author documents this. A fix that keeps the design is to fall back to the EIP-2935 history contract.\n3. **Low.** With the brief-specified SqueekCore stale since late October, seven lanes stay Scheduled and reject bets, so the product cannot meet the \"12 to 15 active markets\" requirement at launch. The README is open about this. It needs a scope decision from the requester.\n4. **Info.** The Morning versus Evening lane closes betting at its own start, so it is unbettable for half of every cycle.\n5. **Info.** Trust assumption on the third-party upgradeable Core. Only round duration and block units are re-checked, not genesis.\n\n**Coverage:** all 11 listed entry points have a row, plus rows for the two invariants, both periphery modules and the static-analysis leads. None are marked unreached. No proof files were attached because no finding reached high severity.","treeHash":null,"usage":{"cachedInputTokens":1549031,"cost":{"source":"runtime","usdMicros":5753234},"inputTokens":450,"model":"claude-fable-5-1","outputTokens":50754,"runtime":"claude","turns":42,"wallClockMs":866912}},{"artifacts":[],"attempt":1,"bundleHash":"00cd5dc5f554b17beb04f0c407283fa22a248f77c3742477871ece6a30d4ebaa","device":"2c7ded3d2e6abf8e","findings":[],"hash":"0b0879f6d9158444c902ea990ea8230f51fa502177e4c5d67bd7ca2f4cf8576f","nodeId":"14106da7-262d-459f-be9d-361a2c2e78ba","outcome":"completed","summary":"Added failure-path, fuzz, and stateful invariant tests covering all 14 market templates, payouts, accounting, and oracle validation.\n\nVerified `forge build` and `forge test` with the required seed: **89 tests passed**, including **24,576 new invariant calls** with no unexpected reverts.\n\nAll changes are confined to `test/`.","treeHash":"73d9c7baeb1477f69e1859dfc00274b53e5dafaf","usage":{"cachedInputTokens":2218496,"cost":{"source":"estimated","usdMicros":5078596},"inputTokens":88145,"model":"gpt-6-astra","outputTokens":39573,"runtime":"codex","turns":8,"wallClockMs":1190070}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"d2d0460994f47014","findings":[{"citation":"resolved","description":"Every lane except MorningEvening closes betting halfway through its observation window (line 383) while bet() only checks block.timestamp < closesAt (line 244). For the cumulative metrics (SqueekBlocks slots 0/1/2, SqueekWork slot 10, ActiveRounds slot 11, LongestRun slot 3, EthBlocks slots 9/13) the partial result is public: SqueekMetrics.measure() reads the certified Core history for any sub-window, and the Core (live on Sepolia, verified with roundPage) certifies rounds continuously. Once the in-window count exceeds the strike before closesAt, the Over outcome is decided and cannot reverse (counts are monotone), yet the pool still prices it as an open question. A bettor who reads the certified history places a sized Over bet in the last second and is paid from the Under pool with zero risk, diluting the earlier Over bettors as well. The optimal stake against an Under pool U and Over pool O is s = sqrt(99*U*O)/0.99 - O/0.99 (fee 1%), giving a risk-free profit of about 0.80 ETH per 1 ETH of Under stakes when the sides are balanced. Threshold crossings before the cutoff are common for these lanes: ActiveRounds needs only 25 of the first 72 rounds to be active, LongestRun needs one 4-round streak in the first 12 hours, and the 100-block 6-hour lane crosses whenever the hamster is active early. README/SECURITY disclose that a threshold 'can become likely or certain before the halfway betting cutoff' but treat it as a user caveat; for a parimutuel market it is an extractable, repeatable transfer from uninformed bettors to anyone running the keeper's own read logic. Minimal fix that preserves the design: for cumulative lanes set closesAt = startsAt (as MorningEvening already does), or refuse bets once measure() over the certified part of the window already exceeds the strike.","line":383,"path":"src/SqueekPredict.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {SqueekPredict} from \"src/SqueekPredict.sol\";\nimport {SqueekMetrics} from \"src/SqueekMetrics.sol\";\nimport {ISqueekCore} from \"src/interfaces/ISqueekCore.sol\";\nimport {IAggregatorV3} from \"src/interfaces/IAggregatorV3.sol\";\n\n/// Minimal Core: a list of active-round checkpoints and a certified frontier.\ncontract ProofCore is ISqueekCore {\n    uint64 public genesis;\n    uint64 public frontier;\n    RoundCheckpoint[] private records;\n\n    constructor(uint64 genesis_) {\n        genesis = genesis_;\n    }\n\n    function setFrontier(uint64 f) external {\n        frontier = f;\n    }\n\n    function add(uint64 round, uint64 work, uint96 units) external {\n        LedgerPosition memory previous = _point(type(uint64).max);\n        records.push(\n            RoundCheckpoint(\n                round, previous.cumulativeWork + work, previous.cumulativeBlockUnits + units, 10_000, bytes32(0)\n            )\n        );\n    }\n\n    function configuration() external view returns (CoreConfiguration memory c) {\n        c.genesisTimestamp = genesis;\n        c.roundDuration = 600;\n        c.blockUnitsPerBlock = 1_000_000_000;\n        c.maxRoundsPerBatch = 16;\n    }\n\n    function state() external view returns (CoreState memory s) {\n        LedgerPosition memory p = _point(frontier);\n        s.settledThroughRound = frontier;\n        s.cumulativeWork = p.cumulativeWork;\n        s.cumulativeBlockUnits = p.cumulativeBlockUnits;\n    }\n\n    function roundPage(uint64 afterRound, uint64 throughRound, uint256 maxRecords)\n        external\n        view\n        returns (RoundPage memory page)\n    {\n        require(afterRound <= throughRound && throughRound <= frontier, \"bounds\");\n        page.start = _point(afterRound);\n        page.certifiedThroughRound = throughRound;\n        uint256 count;\n        for (uint256 i; i < records.length; ++i) {\n            if (records[i].roundId <= afterRound) continue;\n            if (records[i].roundId > throughRound) break;\n            if (count == maxRecords) {\n                page.certifiedThroughRound = records[i].roundId - 1;\n                break;\n            }\n            ++count;\n        }\n        page.rounds = new RoundCheckpoint[](count);\n        uint256 cursor;\n        for (uint256 i; i < records.length && cursor < count; ++i) {\n            if (records[i].roundId > afterRound && records[i].roundId <= throughRound) {\n                page.rounds[cursor++] = records[i];\n            }\n        }\n        page.end = _point(page.certifiedThroughRound);\n    }\n\n    function _point(uint64 round) private view returns (LedgerPosition memory p) {\n        for (uint256 i; i < records.length; ++i) {\n            if (records[i].roundId > round) break;\n            p = LedgerPosition(\n                records[i].cumulativeWork, records[i].cumulativeBlockUnits, records[i].workPerThousandBlocks\n            );\n        }\n    }\n}\n\ncontract ProofFeed is IAggregatorV3 {\n    function decimals() external pure returns (uint8) {\n        return 8;\n    }\n\n    function latestRoundData() external view returns (uint80, int256, uint256, uint256, uint80) {\n        uint80 r = (uint80(1) << 64) | 100;\n        return (r, 2000e8, block.timestamp, block.timestamp, r);\n    }\n\n    function getRoundData(uint80 id) external view returns (uint80, int256, uint256, uint256, uint80) {\n        return (id, 2000e8, block.timestamp, block.timestamp, id);\n    }\n}\n\n/// A cumulative Over/Under lane must not accept a stake after the certified in-window metric has\n/// already crossed its strike: such a stake is a risk-free transfer from the earlier bettors.\ncontract LateInformationBetProof is Test {\n    uint64 constant GENESIS = 1_728_000_000;\n    SqueekPredict predict;\n    SqueekMetrics metrics;\n    ProofCore core;\n    address alice = makeAddr(\"alice\");\n    address bob = makeAddr(\"bob\");\n    address attacker = makeAddr(\"attacker\");\n\n    function setUp() public {\n        vm.warp(GENESIS + 1 days + 1);\n        vm.roll(1000);\n        core = new ProofCore(GENESIS);\n        core.setFrontier(144);\n        metrics = new SqueekMetrics(address(core));\n        ProofFeed feed = new ProofFeed();\n        predict = new SqueekPredict(address(metrics), address(feed), address(feed));\n        vm.deal(alice, 10 ether);\n        vm.deal(bob, 10 ether);\n        vm.deal(attacker, 10 ether);\n        predict.maintain();\n    }\n\n    function testCertifiedOutcomeCannotBeBetOnRiskFree() public {\n        uint256 id = 1; // slot 0: Over/Under 100 Squeek blocks in the next 6 hours, closesAt = startsAt + 3h\n        SqueekPredict.Market memory m = predict.getMarket(id);\n        vm.prank(alice);\n        predict.bet{value: 1 ether}(id, 0); // Under\n        vm.prank(bob);\n        predict.bet{value: 1 ether}(id, 1); // Over\n        uint64 first = (m.startsAt - GENESIS) / 600;\n        uint64 last = (m.endsAt - GENESIS) / 600;\n        // Core certifies 101 in-window blocks by round first+17 (2h50m into the 6h window).\n        core.add(first, 1, 0);\n        core.add(first + 17, 1000, 101_000_000_000);\n        core.setFrontier(first + 17);\n        vm.warp(m.closesAt - 1);\n        // Public, certified: the Over outcome is already decided before betting closes.\n        assertGt(metrics.measure(SqueekMetrics.Metric.Blocks, m.startsAt, GENESIS + (first + 17) * 600), 100);\n\n        uint256 before = attacker.balance;\n        vm.prank(attacker);\n        (bool accepted,) = address(predict).call{value: 8.95 ether}(abi.encodeCall(predict.bet, (id, 1)));\n        if (!accepted) return; // Fixed code refuses the stake: nothing to extract.\n\n        core.add(last, 1000, 101_000_000_000); // the hamster adds no further blocks\n        core.setFrontier(last);\n        vm.warp(m.endsAt + 1);\n        predict.settleSqueek(id);\n        // On the current code the attacker leaves with ~0.80 ETH more than it came with, taken from\n        // Alice (loses 0.99) and Bob (payout falls from 1.98 to 1.09).\n        assertLe(attacker.balance, before, \"risk-free profit on an already-certified outcome\");\n    }\n}","reproduction":"Slot 0 (id 1, 'more than 100 Squeek blocks in 6h', closesAt = startsAt + 3h). Alice bets 1 ETH Under, Bob bets 1 ETH Over at open. Core certifies 101 in-window blocks by round startRound+17 (2h50m into the window) and nothing after. At block.timestamp = closesAt - 1 the attacker calls metrics.measure(Blocks, startsAt, genesis+(startRound+17)*600) = 101 > 100, then bet{value: 8.95 ether}(1, 1) succeeds. settleSqueek(1) after endsAt resolves Over. Expected: a stake on an already-certified outcome is refused or cannot profit. Actual (forge run of test/scratch/LateInformationBet.t.sol): attacker balance rises by 0.801002512562814070 ETH, Bob's payout falls from 1.98 ETH to 1.089497487437185929 ETH, Alice loses her full 0.99 ETH net stake.","severity":"medium","snippet":"        m.closesAt = kind == Kind.MorningEvening ? startsAt : startsAt + duration / 2;","title":"Cumulative Over/Under lanes keep accepting stakes after the certified in-window metric has already crossed the strike, letting a late bettor take a risk-free share of earlier bettors' stakes"},{"citation":"resolved","description":"A replacement generation is scheduled only when the current one reaches endsAt (_roll via _maintain line 401 or _beginResolution line 424), but betting on the current generation stops at closesAt = startsAt + duration/2 (line 383), or at startsAt for MorningEvening. Each lane is therefore bettable for at most half its lifetime and there is never an overlapping generation to bet on. With all 14 lanes opened at the same time (the constructor plus first maintain, which is how launch works) the bettable count is 14 for 3 hours, 12 until hour 12, then 5 (the three 7-day lanes plus the two 6-hour lanes when freshly rolled) from hour 12 to hour 24, every day. MorningEvening is worse: it closes at the start of its UTC day and then sits Open-but-unbettable for 24 hours, so it observes only alternate days, and a first generation opened late in the UTC day gets a betting window of minutes. On the live chain the specified SqueekCore is stalled (frontier 1973, 2026-10-26, confirmed via cast call today), so the seven Squeek lanes never open and the count of bettable markets is 7 at best, 3 at hour 12 and 18 of each cycle, and 2 (only the 7-day price lanes, slots 6 and 12) from hour 15 to 18 and 21 to 24. The brief requires 'Always 12 to 15 active markets' whose cards carry 'two large buttons to bet'; the README acknowledges the Core stall but not the structural halving. Fix preserving the design: stagger lanes or open the next generation of a lane when the current one stops accepting bets (closesAt) rather than when it ends, so at least one bettable generation per lane exists at all times.","line":401,"path":"src/SqueekPredict.sol","reproduction":"Deploy SqueekPredict with live mocks (as test/SqueekPredict.t.sol setUp does) and call maintain() so all 14 lanes open at T0. Count lanes with status Open and block.timestamp < closesAt, calling maintain() before each count so rolled lanes reopen. Observed in test/scratch/Econ.t.sol::testBettableCountOverCycle: T0 -> 14, T0+3h -> 12, T0+12h -> 5, T0+23h -> 5. Expected per the brief: between 12 and 15 markets accepting bets at every time. With the Squeek lanes left Scheduled (MockCore frontier certified more than 6h ago, matching the live Core today), sampled every 3 hours with maintain() first: hour 0 -> 7, 3 -> 6, 6 -> 7, 9 -> 6, 12 -> 3, 15 -> 2, 18 -> 3, 21 -> 2, 24 -> 7.","severity":"medium","snippet":"            if (m.status == Status.Open && block.timestamp >= m.endsAt) _roll(id);\n            else if (m.status == Status.Scheduled) _tryOpen(id);","title":"Only 5 of the 14 lanes accept bets for 12 hours of every day (2 while the specified Core is stalled), against the brief's 12 to 15 always-active markets"},{"citation":"resolved","description":"settleNative authenticates the boundary header only through the BLOCKHASH opcode, which Sepolia serves for the last 256 blocks. BaseFee (slot 8) and EthBlocks (slots 9 and 13) therefore have a ~51-minute proof window per market, after which settlement reverts UnprovenHeader for the rest of the 48-hour grace period and the only terminal path is expire(), which refunds net stakes. Nobody is paid to settle and the loser has every incentive not to, so a single keeper outage, RPC failure, or fee spike of under an hour converts a decided market into a refund and the winners lose their winnings (the 1% fee is still kept). The README documents the window as an operational requirement; the economic effect is a standing loser's option against the operator's liveness on three of the seven lanes that can open while the Core is stalled. Sepolia has run EIP-2935 since Pectra (March 2025): the history contract at 0x0000F90827F1C53a10cb7A02335B175320002935 returns block hashes for the last 8191 blocks (about 27 hours). Reading it as a fallback when blockhash() returns zero would widen the proof window roughly 32-fold without changing the market rules; alternatively lengthen RESOLUTION_GRACE only for native lanes.","line":24,"path":"src/libraries/BlockHeader.sol","reproduction":"Slot 9 (id 10). Alice bets 1 ETH Over, Bob 1 ETH Under. Boundary block number N has timestamp >= endsAt and its parent < endsAt (test/SqueekPredict.t.sol _headers builds and pins such a pair). Warp to endsAt + 60 minutes and vm.roll(N + 257), then settleNative(10, parent, target): reverts BlockHeader.UnprovenHeader (shown by testForgedHeaderLaterSampleAndExpiredHashAreRejected at vm.roll(8458)). Every later call also reverts. At endsAt + 2 days anyone calls expire(10): status Refunded, Alice receives 0.99 ETH instead of 1.98 ETH, Bob receives 0.99 ETH back although he lost. Expected: a market whose outcome is objectively determined on-chain should remain settleable for the full grace period.","severity":"low","snippet":"            target.number >= block.number || block.number - target.number > 256","title":"Native lanes can only be settled within 256 blocks (about 51 minutes) of expiry; after that the decided outcome is irrecoverable and the losing side is refunded at the 48-hour deadline"}],"hash":"1eb51910154f967dd73cbf6b7cb58add3a34c1e21ec2a145d71584c72bbbb632","nodeId":"969ebd34-7e51-4db2-89e9-1d8bf03e8030","outcome":"completed","summary":"The review is complete. The findings file is valid, all three snippets match the tree, the one proof fails on the current code, and no file outside `test/scratch/` and `.imd-findings.json` was touched (the untracked dotfiles in `git status` are sandbox device nodes, not edits).\n\n## Recap\n\n**Scope covered.** I audited the economics of Squeek Predict against the Pashov Economic Security, Invariant and Flow Gap guides, with the Senior Auditor's Mindset read first. I traced every one of the 11 listed entry points, checked the accounting invariants by hand and under the project's own invariant suite, and verified the two external dependencies live on Sepolia: the specified SqueekCore's `roundPage` behaves exactly as `SqueekMetrics.measure` assumes (endpoint reads, 16-record pages, idle spans), and both Chainlink feeds update hourly inside the 2-hour staleness bound. The pool, fee, liability and dust accounting is exact on every path I traced, including deferred pushes and reentrancy attempts, so the Slither and Aderyn reentrancy leads do not reproduce.\n\n**Findings written to `.imd-findings.json`:**\n\n1. **Medium, with proof.** Cumulative Over/Under lanes keep accepting bets until halfway through the window, but the certified Core history is public. Once the in-window count crosses the strike, a last-second Over bet is risk-free. In the proof the attacker nets 0.80 ETH from two 1 ETH bettors. The README mentions the caveat but the market still prices a decided outcome as open.\n2. **Medium.** New generations are scheduled only at `endsAt` while betting stops at `closesAt`, so bettable lanes drop from 14 to 5 for 12 hours of every day. With the specified Core stalled, as it is today, that falls to 2. The brief requires 12 to 15 active markets.\n3. **Low.** Native lanes can only be settled within 256 blocks of expiry. After that the decided outcome is unrecoverable and `expire` refunds the loser at 48 hours. Sepolia supports EIP-2935, which would widen the window to about 27 hours.\n\n**Coverage.** 11 entry-point rows plus 2 invariant rows. Seven entry points hold. `bet`, `settleSqueek`, `maintain`, `performUpkeep`, `settleNative` and `expire` are tied to the findings above. Nothing was left unreached.\n\n**Not pursued.** Opener-chosen BaseFee strikes and dust-bettor queue inflation are visible to all participants and have no identifiable victim, so I left them out rather than pad the report.","treeHash":null,"usage":{"cachedInputTokens":1560011,"cost":{"source":"runtime","usdMicros":5732677},"inputTokens":450,"model":"claude-fable-5-1","outputTokens":53634,"runtime":"claude","turns":35,"wallClockMs":1442517}},{"artifacts":[],"attempt":1,"bundleHash":"31754de4ee8778f3d00b033bf70e3aa26c94faff0d676839650deacf06dc5b65","device":"cf00912a1647ee69","findings":[],"hash":"6098e7f446b6d99d715e5c2c1024444d3fd6dcd636d49ecf2c37cb093118a9a0","nodeId":"8a770fd7-40b4-4040-ade1-baa781778585","outcome":"completed","summary":"Implemented contracts, responsive website, wallet positions, keeper, and [README](/home/imd16/.identitymd/work/91b58f78-cc61-4b4f-81d9-ac69c93c4f33/8a770fd7-40b4-4040-ade1-baa781778585/README.md).\n\nPassed `forge build`, 41 Foundry tests, formatting checks, 17 JavaScript tests, and desktop/mobile browser flows.\n\nDeployment and public hosting remain for the launch stage. The specified SqueekCore is stalled, so new Squeek bets stay disabled until reporting resumes. Automated operation also requires funded keeper gas.","treeHash":"73d6b99874c7891d2b78b35e0905df901201265a","usage":{"cachedInputTokens":7633280,"cost":{"source":"estimated","usdMicros":16902000},"inputTokens":175012,"model":"gpt-6-astra","outputTokens":150372,"runtime":"codex","turns":14,"wallClockMs":5021292}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"96ecbe9236c400bb","findings":[{"citation":"resolved","description":"Trust Gap (access x economics x asymmetry). _payBatch is run inside every outcome transaction (settleSqueek, settlePrice, settleNative and expire all end in _finish -> _payBatch(id, 20)) and pushes ETH to the first 20 addresses of bettors[id], an order any account controls by betting first (bet() even opens a Scheduled lane itself). PUSH_GAS caps what the recipient may execute, but not what it costs the caller: this Solidity low-level call still copies the callee's return data into fresh memory although the bytes are discarded, and the copy is repeated for each recipient, so caller memory grows by ~116 kB per hostile recipient and the quadratic memory price is paid by the settler. With 35,000 gas (+2,300 stipend) a recipient can revert with ~116,000 bytes. Measured on this tree: expire() with 20 plain recipients 1.02M gas, with 20 recipients reverting with empty data 1.18M gas, with 20 recipients reverting with 113-116 kB 12.06M (expire) / 12.65M (settleSqueek). SECURITY.md states 'Automatic calls use bounded gas and ignore failed recipients'; the gas is not bounded. Consequences: (1) scripts/keeper.mjs line 52 refuses any transaction whose estimate*1.25 exceeds 8,000,000 gas ('Transaction exceeds the keeper gas budget'), so the supplied automation never settles such a market; by the quadratic memory rule roughly 15 hostile recipients already pass 6.4M (computed, only the 20-recipient case was run). (2) Settlement stays possible only for someone who hand-sends a ~12.7M-gas transaction, which gives the party holding the bombs a one-sided option: it sends that transaction when its real stake won and stays silent when it lost. For the native lanes 8, 9 and 13 (BaseFee, EthBlocks) the header proof is only valid for 256 blocks (~51 minutes) after endsAt, after which settleNative can never succeed and only expire() (net refund to everyone) remains; for the other lanes the same happens after RESOLUTION_GRACE. The loser's stake is refunded instead of being paid to the winners. (3) pay(id, 40), the fixed batch the keeper uses, grows by the same quadratic rule to roughly 4x the 20-recipient memory cost (not run; above Sepolia's 16,777,216 per-transaction gas cap), so the keeper's payment draining for that market also stops until someone calls pay with a smaller batch. The attacker's cost is 20 x 2 x MIN_BET = 0.04 ETH of stake that it later recovers with claim(). Fix that preserves the design: make the push with an assembly call that requests zero return bytes (call(PUSH_GAS, user, amount, 0, 0, 0, 0)) so nothing is copied; claim() is unaffected because the claimer picks its own recipient.","line":465,"path":"src/SqueekPredict.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {SqueekPredict} from \"src/SqueekPredict.sol\";\nimport {SqueekMetrics} from \"src/SqueekMetrics.sol\";\nimport {ISqueekCore} from \"src/interfaces/ISqueekCore.sol\";\n\n/// Minimal certified ledger: one active round carrying all work/blocks, frontier set by the test.\ncontract BombCore is ISqueekCore {\n    uint64 public genesis;\n    uint64 public frontier;\n    uint64 public activeRound;\n    uint64 public work;\n    uint96 public units;\n\n    constructor(uint64 g) {\n        genesis = g;\n    }\n\n    function setFrontier(uint64 f) external {\n        frontier = f;\n    }\n\n    function certify(uint64 round, uint64 work_, uint96 units_) external {\n        activeRound = round;\n        work = work_;\n        units = units_;\n    }\n\n    function configuration() external view returns (CoreConfiguration memory c) {\n        c.genesisTimestamp = genesis;\n        c.roundDuration = 600;\n        c.blockUnitsPerBlock = 1_000_000_000;\n    }\n\n    function _point(uint64 round) private view returns (LedgerPosition memory p) {\n        if (activeRound != 0 && round >= activeRound) p = LedgerPosition(work, units, 10_000);\n    }\n\n    function state() external view returns (CoreState memory s) {\n        LedgerPosition memory p = _point(frontier);\n        s.settledThroughRound = frontier;\n        s.cumulativeWork = p.cumulativeWork;\n        s.cumulativeBlockUnits = p.cumulativeBlockUnits;\n    }\n\n    function roundPage(uint64 afterRound, uint64 throughRound, uint256 maxRecords)\n        external\n        view\n        returns (RoundPage memory page)\n    {\n        require(afterRound <= throughRound && throughRound <= frontier, \"bounds\");\n        page.certifiedThroughRound = throughRound;\n        page.start = _point(afterRound);\n        page.end = _point(throughRound);\n        if (maxRecords != 0 && activeRound > afterRound && activeRound <= throughRound) {\n            page.rounds = new RoundCheckpoint[](1);\n            page.rounds[0] = RoundCheckpoint(activeRound, work, units, 10_000, bytes32(0));\n        }\n    }\n}\n\n/// A bettor that stakes the minimum on both sides (so it is pushed whichever side wins) and\n/// answers the automatic payment by reverting with as much return data as PUSH_GAS can buy.\ncontract ReturnBomb {\n    function place(SqueekPredict p, uint256 id) external payable {\n        p.bet{value: msg.value / 2}(id, 0);\n        p.bet{value: msg.value / 2}(id, 1);\n    }\n\n    function withdraw(SqueekPredict p, uint256 id, address payable to) external {\n        p.claim(id, to);\n    }\n\n    receive() external payable {\n        assembly {\n            revert(0, 116000)\n        }\n    }\n}\n\ncontract ReturnBombSettlementTest is Test {\n    uint64 constant GENESIS = 1_728_000_000;\n    /// scripts/keeper.mjs refuses a transaction when estimate * 125 / 100 > 8_000_000.\n    uint256 constant KEEPER_ESTIMATE_BUDGET = 6_400_000;\n\n    function testSettlementGasStaysBoundedWhenPushedRecipientsRevert() public {\n        vm.warp(GENESIS + 1 days + 1);\n        BombCore core = new BombCore(GENESIS);\n        core.setFrontier(144);\n        SqueekMetrics metrics = new SqueekMetrics(address(core));\n        SqueekPredict predict = new SqueekPredict(address(metrics), address(0xFEED), address(0xFEED));\n\n        // The attacker takes the first twenty bettor slots of lane 0 (market 1) for 0.04 ether.\n        ReturnBomb[] memory bombs = new ReturnBomb[](20);\n        for (uint256 i; i < 20; ++i) {\n            bombs[i] = new ReturnBomb();\n            bombs[i].place{value: 0.002 ether}(predict, 1);\n        }\n        // Real money: the attacker is on NO, an honest user on YES.\n        address attacker = makeAddr(\"attacker\");\n        address honest = makeAddr(\"honest\");\n        vm.deal(attacker, 10 ether);\n        vm.deal(honest, 10 ether);\n        vm.prank(attacker);\n        predict.bet{value: 10 ether}(1, 0);\n        vm.prank(honest);\n        predict.bet{value: 10 ether}(1, 1);\n\n        // 101 certified blocks > strike 100: YES wins, the attacker's stake should go to `honest`.\n        SqueekPredict.Market memory m = predict.getMarket(1);\n        uint64 startRound = (m.startsAt - GENESIS) / 600;\n        core.certify(startRound + 1, 5000, 101 * 1_000_000_000);\n        core.setFrontier(startRound + 36);\n        vm.warp(m.endsAt);\n\n        uint256 beforeGas = gasleft();\n        predict.settleSqueek(1);\n        uint256 used = beforeGas - gasleft();\n        emit log_named_uint(\"settleSqueek gas with 20 reverting recipients\", used);\n\n        // Twenty pushes of PUSH_GAS = 35_000 each are supposed to bound this transaction.\n        assertLt(used, KEEPER_ESTIMATE_BUDGET, \"settlement gas is controlled by the pushed recipients\");\n    }\n}","reproduction":"State: lane 0 (market 1) freshly scheduled. Calls: (1) 20 contracts each call bet{value: 0.001 ether}(1,0) and bet{value: 0.001 ether}(1,1); their receive() is `assembly { revert(0, 116000) }`. They now occupy bettors[1][0..19] and hold winning-side stake whichever side wins. (2) attacker EOA: bet{value: 10 ether}(1,0); honest EOA: bet{value: 10 ether}(1,1). (3) Core certifies 101 blocks in the window (YES wins), time = endsAt. (4) settleSqueek(1). Expected: gas bounded by 20 pushes of PUSH_GAS (about 1.2M measured with recipients that revert with no data), well inside the keeper's 6.4M estimate budget. Actual: 12,651,148 gas, so the keeper's dispatch throws before sending and the market stays unresolved unless someone manually supplies ~12.7M gas; at endsAt + 48h expire(1) refunds the attacker's 9.9 ETH net stake that should have been paid to the honest YES bettor (on lanes 8, 9, 13 the outcome becomes unprovable after only 256 blocks). Run: forge test --match-path test/scratch/ReturnBombSettlement.t.sol -vv -> FAIL 'settlement gas is controlled by the pushed recipients: 12651148 >= 6400000'.","severity":"high","snippet":"            (bool success,) = payable(user).call{value: amount, gas: PUSH_GAS}(\"\");","title":"Automatic payouts copy the recipient's revert data: 20 hostile bettors raise every settlement from ~1.2M to ~12.7M gas, past the shipped keeper's budget, so a losing bettor can turn a loss into a refu"},{"citation":"resolved","description":"Branch asymmetry in _open (Asymmetry step 3) that becomes an economic transfer (Trust Gap, economics x asymmetry). MorningEvening stops betting when its measurement window starts (closesAt = startsAt). Every other lane keeps bet() open until startsAt + duration/2, i.e. 3h into the 6h window, 12h into the 24h windows and 3.5 days into the 7-day window. For the Squeek lanes 0,1,2 (SqueekBlocks), 3 (LongestRun), 10 (SqueekWork) and 11 (ActiveRounds) the measured value is a non-decreasing function of the Core ledger that Core certifies round by round while the window runs (bet() even requires Core to be at most 6h behind, SqueekMetrics.requireLive). Once the certified value exceeds the strike, YES is the certain result, yet bet() still accepts YES stakes because the only checks are status == Open and block.timestamp < closesAt (src/SqueekPredict.sol:244). Whoever stakes after that point carries no risk and is paid pro rata out of the losing pool, so the winnings of the bettors who took the risk before the window are diverted to the late account. There is no cap other than MAX_BET per transaction (repeatable from the same or other accounts up to MAX_POOL), so the early winners' share can be driven close to zero. SECURITY.md mentions that a threshold 'can become likely or certain before the halfway betting cutoff'; it is nevertheless the one lane-opening rule that differs between MorningEvening and its sibling Squeek lanes, and the result is a risk-free extraction from other users. Fix that keeps the design: close the Squeek cumulative lanes at startsAt as MorningEvening already does (bets are accepted from open() until the aligned window begins), or at least reject a stake when the already certified ledger decides the lane.","line":383,"path":"src/SqueekPredict.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {SqueekPredict} from \"src/SqueekPredict.sol\";\nimport {SqueekMetrics} from \"src/SqueekMetrics.sol\";\nimport {ISqueekCore} from \"src/interfaces/ISqueekCore.sol\";\n\n/// Minimal certified ledger: one active round carrying all work/blocks, frontier set by the test.\ncontract ScratchCore is ISqueekCore {\n    uint64 public genesis;\n    uint64 public frontier;\n    uint64 public activeRound;\n    uint64 public work;\n    uint96 public units;\n\n    constructor(uint64 g) {\n        genesis = g;\n    }\n\n    function setFrontier(uint64 f) external {\n        frontier = f;\n    }\n\n    function certify(uint64 round, uint64 work_, uint96 units_) external {\n        activeRound = round;\n        work = work_;\n        units = units_;\n    }\n\n    function configuration() external view returns (CoreConfiguration memory c) {\n        c.genesisTimestamp = genesis;\n        c.roundDuration = 600;\n        c.blockUnitsPerBlock = 1_000_000_000;\n    }\n\n    function _point(uint64 round) private view returns (LedgerPosition memory p) {\n        if (activeRound != 0 && round >= activeRound) p = LedgerPosition(work, units, 10_000);\n    }\n\n    function state() external view returns (CoreState memory s) {\n        LedgerPosition memory p = _point(frontier);\n        s.settledThroughRound = frontier;\n        s.cumulativeWork = p.cumulativeWork;\n        s.cumulativeBlockUnits = p.cumulativeBlockUnits;\n    }\n\n    function roundPage(uint64 afterRound, uint64 throughRound, uint256 maxRecords)\n        external\n        view\n        returns (RoundPage memory page)\n    {\n        require(afterRound <= throughRound && throughRound <= frontier, \"bounds\");\n        page.certifiedThroughRound = throughRound;\n        page.start = _point(afterRound);\n        page.end = _point(throughRound);\n        if (maxRecords != 0 && activeRound > afterRound && activeRound <= throughRound) {\n            page.rounds = new RoundCheckpoint[](1);\n            page.rounds[0] = RoundCheckpoint(activeRound, work, units, 10_000, bytes32(0));\n        }\n    }\n}\n\ncontract LateBetDecidedTest is Test {\n    uint64 constant GENESIS = 1_728_000_000;\n\n    function testNoStakeAcceptedAfterCertifiedLedgerDecidedTheMarket() public {\n        vm.warp(GENESIS + 1 days + 1);\n        ScratchCore core = new ScratchCore(GENESIS);\n        core.setFrontier(144);\n        SqueekMetrics metrics = new SqueekMetrics(address(core));\n        // Feeds are irrelevant for lane 0; any nonzero address satisfies the constructor.\n        SqueekPredict predict = new SqueekPredict(address(metrics), address(0xFEED), address(0xFEED));\n\n        address alice = makeAddr(\"alice\");\n        address bob = makeAddr(\"bob\");\n        address carol = makeAddr(\"carol\");\n        vm.deal(alice, 10 ether);\n        vm.deal(bob, 10 ether);\n        vm.deal(carol, 100 ether);\n\n        // Lane 0: \"Squeek blocks in 6h over 100\". Honest bettors take both sides before it starts.\n        vm.prank(alice);\n        predict.bet{value: 10 ether}(1, 0);\n        vm.prank(bob);\n        predict.bet{value: 10 ether}(1, 1);\n        SqueekPredict.Market memory m = predict.getMarket(1);\n        assertEq(m.strike, 100);\n        assertEq(m.endsAt - m.startsAt, 6 hours);\n\n        // Two hours into the six-hour window Core has certified 101 blocks: more than the strike.\n        // cumulativeBlockUnits never decreases, so YES is already the certain outcome.\n        vm.warp(m.startsAt + 2 hours);\n        uint64 startRound = (m.startsAt - GENESIS) / 600;\n        core.certify(startRound + 1, 5000, 101 * 1_000_000_000);\n        core.setFrontier(startRound + 12);\n\n        vm.prank(carol);\n        bool accepted;\n        try predict.bet{value: 100 ether}(1, 1) {\n            accepted = true;\n        } catch {}\n\n        if (accepted) {\n            // Show the transfer this allows: settle and compare carol against bob.\n            vm.warp(m.endsAt);\n            core.setFrontier(startRound + 36);\n            predict.settleSqueek(1);\n            emit log_named_uint(\"carol risk-free profit (wei)\", carol.balance - 100 ether);\n            emit log_named_uint(\"bob payout (wei), 19.8 ether without carol\", bob.balance);\n        }\n        assertFalse(accepted, \"stake accepted after the certified ledger had already decided the market\");\n    }\n}","reproduction":"State: lane 0 (market 1, 'Squeek blocks in 6h over 100'), Core genesis G, opened at G+1d+1s so startsAt = G+87000, closesAt = startsAt+3h, endsAt = startsAt+6h. Calls: (1) alice bet{value:10 ether}(1,0); bob bet{value:10 ether}(1,1) before the window. (2) At startsAt+2h Core has certified 12 rounds of the window holding 101 blocks (cumulativeBlockUnits delta = 101e9 > strike 100). (3) carol bet{value:100 ether}(1,1). Expected: revert (the outcome is already certified, as on the MorningEvening lane where betting ends at startsAt). Actual: accepted. (4) At endsAt settleSqueek(1) resolves YES: carol receives 108 ether for 100 staked (+8 ether with zero risk, after her own 1 ether fee), bob receives 10.8 ether instead of the 19.8 ether he would receive without carol. The same holds for lane 3 once four consecutive active rounds are certified, lane 11 once 25 active rounds are certified, lane 10 once work > 5000, lanes 1 and 2 once blocks exceed 400 / 2800. Run: forge test --match-path test/scratch/LateBetDecided.t.sol -vv -> FAIL 'stake accepted after the certified ledger had already decided the market', logs 'carol risk-free profit (wei): 8000000000000000000' and 'bob payout (wei): 10800000000000000000'.","severity":"medium","snippet":"        m.closesAt = kind == Kind.MorningEvening ? startsAt : startsAt + duration / 2;","title":"Squeek cumulative markets accept stakes for half the observation window, after Core has already certified the outcome"},{"citation":"resolved","description":"Outside the permissions area, reported because it breaks an explicit requirement in the state that exists today. Lanes 0, 1, 2, 3, 4, 10 and 11 open only through SqueekMetrics.window(), which reverts CoreStale when the Core's certified frontier is more than 6 hours behind the chain clock; bet() repeats the check (requireLive). Read from Sepolia on 2026-11-06 through https://ethereum-sepolia-rpc.publicnode.com: SqueekCore 0x74665dAecA35A2303e767E76a2FF1FB6F93e119f state() = settledThroughRound 1973, paused false; configuration genesis 1791847800, so certifiedAt = 1793031600 (2026-10-26 16:20 UTC) while the latest block timestamp was 1793966040: 934,440 s (10.8 days) behind. In that state every activate() for those seven lanes reverts and is swallowed by _tryOpen (ActivationDeferred), the lanes stay Scheduled and accept no bets, and only the seven price/native lanes (5, 6, 7, 8, 9, 12, 13) are usable. The brief asks to 'always maintain between 12 and 15 active markets' and for a product that is 'immediately usable'. docs/SOURCES.md and docs/OPERATIONS.md record the stale Core; the contract cannot fabricate hamster data, so this needs a scope decision by the requester (for example enough lanes on public Sepolia data to keep twelve open while Core is stale, or an explicit acceptance that the count drops), not a silent change of the data source.","line":60,"path":"src/SqueekMetrics.sol","reproduction":"State: Core frontier 1973 as read above (or MockCore with frontier F and block.timestamp > genesis + F*600 + 6 hours). Calls: deploy SqueekMetrics(core) and SqueekPredict(metrics, ethFeed, btcFeed); maintain(). Expected: 12-15 markets Open. Actual: ActivationDeferred for market ids 1, 2, 3, 4, 5, 11, 12; getMarket(id).status == Scheduled for those seven; bet(1, 1){value: 0.001 ether} reverts CoreStale(1793031600); 7 markets Open. Repeating maintain() changes nothing until the Core reporter resumes.","severity":"medium","snippet":"        if (certifiedAt > block.timestamp || block.timestamp - certifiedAt > MAX_REPORTING_DELAY) {","title":"With the specified Core's live state seven of the fourteen lanes cannot open, so the product launches with at most 7 markets instead of 12-15"},{"citation":"resolved","description":"Asymmetry between the simulate side and the execute side (view <-> modify walk pair). checkUpkeep treats any Scheduled current lane as upkeep needed, but performUpkeep -> _maintain -> _tryOpen can only open it if the lane's source is healthy; otherwise activate() reverts, the catch emits ActivationDeferred and the lane is still Scheduled, so checkUpkeep is true again in the next block. With the live Core state (frontier 10.8 days old, see the previous finding) seven lanes are in this condition permanently. Anyone who registers the advertised AutomationCompatible pair with an automation network gets a performUpkeep in every check interval, each burning ~210k gas (measured: 286,235 then 209,449 with 11 unopenable lanes) and emitting seven events, until the subscription is drained; the same loop also lets the lanes that are healthy be starved of budget. The supplied scripts/keeper-lib.mjs avoids this by probing open.staticCall first, which shows the condition checkUpkeep should use. Fix: have checkUpkeep return true for a Scheduled lane only when opening it would succeed (for example by try-calling a view that performs the same source checks), or return in performData the ids that can progress.","line":231,"path":"src/SqueekPredict.sol","reproduction":"State: fresh SqueekPredict whose metrics dependency reverts in window() (Core stale or paused), time T. Calls: checkUpkeep('') -> (true, ''); performUpkeep('') -> succeeds, emits ActivationDeferred for the Squeek lanes, no lane changes status; checkUpkeep('') -> (true, '') again; performUpkeep('') -> another ~209k gas with no state change. Expected: checkUpkeep false once nothing can progress. Actual: true indefinitely.","severity":"low","snippet":"            if (m.status == Status.Scheduled || (m.status == Status.Open && block.timestamp >= m.endsAt)) {","title":"checkUpkeep reports work that performUpkeep cannot do: with a lane whose source is down it returns true forever and every performUpkeep is a paid no-op"}],"hash":"658194e87110c311f4fa0c8f69d767792da6f723fc34fe9cc747989d77723962","nodeId":"658e2208-6685-4a16-9704-7852356a0c63","outcome":"completed","summary":"The permissions review is complete: `.imd-findings.json` holds four findings and a coverage row for all 11 entry points (plus three invariant rows). No tracked file was changed; the only additions are the findings file and two proof tests under `test/scratch/`, both git-ignored.\n\n## Findings\n\n1. **High — hostile recipients control settlement gas** (`src/SqueekPredict.sol:465`). The automatic payout copies each failing recipient's revert data, so 20 bettors who revert with ~116 kB raise `settleSqueek` to 12,651,148 gas. With recipients that revert with no data, the same 20-recipient batch costs about 1.2M (measured on `expire`).\n   - The shipped keeper refuses anything above 8M gas, so it never settles such a market.\n   - Whoever holds those 20 slots can settle by hand when they won and stay silent when they lost. On the three block-header lanes the proof expires after 256 blocks, leaving only `expire()`, which refunds the loser.\n   - Settlement is still possible for anyone who hand-sends ~12.7M gas, so this is not an absolute block.\n   - The proof test fails on the current code. I did not apply the fix (a push that requests zero return bytes), so \"passes once fixed\" is reasoned, not run.\n\n2. **Medium — bets accepted after the outcome is certified** (`src/SqueekPredict.sol:383`). The morning/evening lane closes betting when its window starts; the other Squeek lanes stay open for half the window. In the proof test, Core has certified 101 blocks against a strike of 100 two hours in. A 100 ETH late bet is still accepted and nets +8 ETH with no risk, cutting the early winner's payout from 19.8 to 10.8 ETH. `SECURITY.md` already lists this as a known limitation.\n\n3. **Medium, outside my area — seven lanes cannot open today** (`src/SqueekMetrics.sol:60`). Read from Sepolia during this review, the specified Core's certified frontier is 10.8 days behind the chain clock. All seven Squeek lanes therefore stay scheduled, leaving 7 markets rather than the 12–15 the brief requires. The contract cannot invent data, so this needs your scope decision.\n\n4. **Low — `checkUpkeep` stays true for lanes it cannot open** (`src/SqueekPredict.sol:231`). Each `performUpkeep` is then a ~210k-gas no-op, repeated indefinitely by any automation subscription. The shipped keeper script avoids this by probing first.\n\n## Coverage\n\n- **Access control:** `activate` is self-only with no outside route, there is no owner, initializer, upgrade or fee-withdrawal path, and `claim` pays only the caller's own position. Nothing found.\n- **Asymmetry and trust gap:** findings 1, 2 and 4 come from these passes. Price-round and block-header selection cannot be steered by the caller, and the accounting pairs mirror.\n- **Checked live on Sepolia:** Core page semantics match what `SqueekMetrics` assumes, and both Chainlink feeds update hourly, inside the 2-hour staleness limit.\n\n## Not verified\n\n- The claims that about 15 hostile recipients already exceed the keeper budget and that `pay(id, 40)` exceeds the per-transaction gas cap are computed, not run.\n- I did not reproduce base-fee manipulation on the gas-price lane, or test the tie rule that resolves an equal result to No.\n- I did not review `launch.json` (none exists yet) or the website.","treeHash":null,"usage":{"cachedInputTokens":1523636,"cost":{"source":"runtime","usdMicros":5227564},"inputTokens":34,"model":"claude-fable-5-1","outputTokens":51999,"runtime":"claude","turns":21,"wallClockMs":660848}},{"artifacts":[],"attempt":1,"bundleHash":"9e0eb16620d0c10cd8c6fb42c6f0c0f4cb018b320ae3ad13ffb3a2ae50445a04","device":"1ec9f666356a7ad7","findings":[],"hash":"7b6341312de05194e9fdef31c1615763b3693c7d4ab20c73e5f03d44e71e5e63","nodeId":"6421bad7-b48b-4e36-98d0-edd3e1ff57ae","outcome":"completed","summary":"The existing launch.json is correct and I left it unchanged.\n\n**What I verified**\n\n- **No findings were supplied** with this revision brief, so I checked the manifest against the accepted source instead of guessing at a change.\n- **Manifest shape**: exactly the keys kind, contracts and notes. Both contract names are 13 characters, unique, and listed in dependency order. Notes are well under the 4000-character limit.\n- **Constructor arguments match the source**: SqueekMetrics takes one address, the SqueekCore proxy the brief names. SqueekPredict takes the metrics reader as a backward reference, then the Sepolia Chainlink ETH/USD and BTC/USD feeds, in declaration order. The same feed addresses are recorded in docs/SOURCES.md and the site configuration script.\n- **No owner argument is needed**: neither contract has an owner, admin, fee withdrawal or upgrade path, so nothing in the manifest grants privileges to the factory or to IMD, which satisfies the protected owners check.\n- **Deployment rehearsal**: a scratch test in test/scratch replayed the protected probe, deploying both contracts by CREATE2 from an etched factory on chain id 11155111 with no code at the Core or feed addresses. Both constructors succeeded, the dependency getters returned the manifest addresses, fourteen slots were scheduled, and both runtimes fit under the EIP-170 limit.\n- **Build and tests**: forge build succeeds and the full suite passes with the verifier's fuzz seed.\n\n| Check | Result |\n|---|---|\n| forge test (verifier seed) | 49 passed, 0 failed |\n| Scratch manifest rehearsal | 1 passed |\n| SqueekPredict runtime size | 15,241 bytes |\n\nThe working tree is clean apart from the ignored scratch directory, so no file other than launch.json is touched and nothing needed committing.","treeHash":"3d8f1698fba9222697b4dce0d5a487c3f0674ee2","usage":{"cachedInputTokens":200710,"cost":{"source":"runtime","usdMicros":919402},"inputTokens":194,"model":"claude-fable-5-1","outputTokens":5566,"runtime":"claude","turns":14,"wallClockMs":113467}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"c2a2bddd2b92d71e","findings":[{"citation":"resolved","description":"Settles bd9c8f65. The behaviour still reproduces, and the live Core is still stale. On 2026-11-06 state().settledThroughRound was 1973, which gives certifiedAt 1793031600, while the head timestamp was 1793974392: 942,792 s (10.9 days) behind. The author's answer holds on the code. Refusing stakes that could never be settled honestly is correct. The brief fixes this Core and lists five Squeek market types, so at most 10 non-Squeek lanes fit under the cap of 15, which is fewer than 12. README lines 9-11 and the launch.json notes now say this plainly, and the rotation fix gives 14 bettable markets whenever sources are healthy. Severity is recalibrated from medium to low: no funds are at risk, the failure is closed and disclosed, and it depends on external chain state. The brief's 'always 12 to 15' and 'immediately usable' are still unmet until the Core reporter resumes or the requester accepts fewer markets or more public-data lanes. That is a requester decision, not a code fix for the author, so no change is required from the author.","line":69,"path":"src/SqueekMetrics.sol","reproduction":"MockCore genesis 1791847800, frontier 1973, warp to 1793974392 (the live values), feeds updated hourly. Deploy SqueekMetrics and SqueekPredict, call maintain(), then warp +1h and call maintain() for 240 hours, counting lanes with status == Open && now < closesAt after each step. Expected per brief: at least 12. Actual: 7 at every sample (min 7, max 7). The Squeek lane stays Scheduled, bet{value: 1 ether}(id, 1) reverts CoreStale(1793031600), and checkUpkeep is false, so there is no paid no-op loop. Ran as a scratch forge test on this tree.","severity":"low","snippet":"if (certifiedAt > block.timestamp || block.timestamp - certifiedAt > MAX_REPORTING_DELAY) {","title":"Disputed item kept as advisory: while the specified Core stays stale, seven lanes refuse stakes and only 7 markets are bettable, against the brief's 12 to 15"}],"hash":"9b6b77532c5e3ea3043558a2c76501128ef7a738bea88252e3816e1708a10869","nodeId":"d169d955-ad20-46bb-ad62-cb6ad8837f69","outcome":"completed","summary":"**Review result:** all of last round's findings are settled except one. The stale-Core item still reproduces, so I kept it as a low advisory, and I found no new reproducible defects. The report is in `.imd-findings.json`, with one finding and 24 coverage rows covering all 11 entry points.\n\nAll six attached proofs now pass on the revised code. The full suite also passes: 109 forge tests and 21 node tests.\n\n## Earlier findings\n\n| Earlier finding | Outcome | How I checked |\n|---|---|---|\n| High: payout return data inflating settlement gas | Fixed | Both return-bomb proofs pass. The push copies no return data, so settlement stays under the proof's 6.4M limit. |\n| Medium: late Squeek stakes after the outcome is certified | Fixed | All three proofs pass. Betting now ends when the window starts, before any of its rounds can be certified. |\n| Medium: bettable count falling below 12 | Fixed | The proof shows 14 bettable at every hourly check for 48h. I also checked per lane type that one observing slot is enough. |\n| Medium: base-fee strike shown as \"0 gwei\" | Fixed | The strike now prints in exact wei, and the web tests pass. |\n| Medium: native lanes only provable for 256 blocks | Fixed | On live Sepolia, the EIP-2935 history contract returned the correct hash for a block 5,000 back. Live headers also encode to their correct hash at 7,000 blocks back. |\n| Medium: stale Core leaves seven lanes closed (author disputed) | **Still reproduces, now low** | See below. |\n| Low and info advisories (5 items) | Fixed or documented | Upkeep and keeper retries are covered by tests. `configure-site` was checked by reading the code. The new strikes were not re-measured against Core history this round. |\n\n## The one remaining finding\nWith the real Core values (frontier 1973, 10.9 days behind), a 240-hour run gave exactly **7 bettable markets at every hourly check**, against the brief's 12 to 15.\n\nThe author is right about the code: refusing those stakes is correct, and the README and `launch.json` notes disclose it. The brief's 15-market cap and five Squeek types leave room for at most 10 non-Squeek lanes, so no code change can reach 12 while the Core is stale. I lowered it to low so it won't send the author back for a change.\n\n**Decision for you:** the launch will not meet \"always 12–15\" or \"immediately usable\" until the Core reporter resumes, unless you accept fewer markets or add more public-data lanes. If you see this as blocking, it should go back to medium.\n\n## New risks checked and dropped\nAll gas figures come from `eth_simulateV1` on live Sepolia pricing:\n- **`maintain()` gas after rotation:** the worst case is 5.17M gas after an 8-day outage, using stub Core and feeds. That is inside the keeper's 6.4M estimate budget.\n- **Lane opening gas cap:** lane 0 uses about 266k of its 300k cap against the real Core with only its frontier overridden, and no lane failed to open for lack of gas. That leaves about 34k of margin, so a costlier Core upgrade could eat into it.\n- **Static-analysis leads:** the reentrancy leads don't reproduce because of the reentrancy guard and the order of state changes. The other leads I looked at (unused locals, divide-before-multiply) are intentional behaviour.\n\nI removed my scratch files from `test/scratch/`, and no tracked files changed.","treeHash":null,"usage":{"cachedInputTokens":3064232,"cost":{"source":"runtime","usdMicros":2792402},"inputTokens":45,"model":"claude-opus-5-5","outputTokens":46825,"runtime":"claude","turns":42,"wallClockMs":610690}},{"artifacts":[],"attempt":1,"bundleHash":"6f882acf4a138df229f5b4d5b464ed3e93d99c7163b66a138273020315d676d6","device":"0ef0dfae5a05e4be","findings":[],"hash":"a5566dfa71938509a70f3d7b23ff4e8e2d283585193a92be7e6036b44eca494e","nodeId":"14106da7-262d-459f-be9d-361a2c2e78ba","outcome":"completed","summary":"I've updated my test suite for the revised SqueekPredict, and all 109 tests in 12 suites pass with `forge test` and the verifier's fuzz seed (about 8 minutes locally). I found no defect in the revision, so there's no `.imd-findings.json`. All my edits are in `test/` and nothing outside it was touched.\n\n**Earlier tests that broke on purpose.** Four of my earlier tests failed on the revision only because of changes the implementer made deliberately: new Squeek strikes (950 / 6700 / 2 h / 11,000), every Squeek lane now closing betting when its window starts, and rolling windows starting one full duration after opening. They now read those values from the contract instead of hard-coding them. What they check is unchanged:\n- `ResolutionMatrix.t.sol`\n- `ProtocolEdges.t.sol`\n- `MetricsProperties.t.sol`\n\n**New file `test/GenerationLifecycle.t.sol` (12 tests) covers the new behaviour, including failure paths:**\n- **Price lanes:** bets stop exactly at `closesAt`, even before the keeper rotates the lane. The next market gets a fresh strike, and running `maintain` twice in one block changes nothing. Stakes on the new market never land in the old one's pool.\n- **Closed markets still waiting on their result:** one that times out refunds exactly at the grace boundary without touching the next market. A keeper that skips the whole window still produces exactly one next market.\n- **Squeek lanes:** a fuzz test over all seven lanes checks they take bets only before their window, that windows are aligned to Core's 10-minute rounds, and that consecutive windows never overlap.\n- **Core genesis moves:** any move, earlier or later and on or off the round grid, blocks settlement, and stakes are refunded after the grace period.\n- **Block-header proofs:** BLOCKHASH covers exactly 256 blocks and the EIP-2935 history covers up to 8,191. Overlong, reverting or all-zero history answers are rejected, as are targets at the current block.\n\n**Invariants (`test/StatefulSolvency.t.sol`):**\n- **After every call:** a closed market waiting on its result is always its lane's older, still-open market, and the market taking bets never closes before that one ends.\n- **After every sequence:** every scheduled or open market belongs to its lane, so none is ever left unreachable.\n\n**Two things to know:**\n- **Fewer invariant runs.** I cut the stateful suite from 256 to 64 runs (still 96 calls each). The revised `maintain` does more work per call, and 256 runs didn't finish within the ~10-minute limit on each local run; 64 runs takes about 2.5 minutes.\n- **A small gap in rolling Squeek lanes.** If the keeper rotates a 6-hour, 1-day or 7-day Squeek lane even one second after it closes, the next window starts one 10-minute round late, so that round is never covered by any market. Daily lanes and on-time rotations line up exactly. Nobody loses money and the brief doesn't require unbroken coverage, so I recorded it as observed behaviour in `test/INDEPENDENT_TESTS.md` rather than as a finding.","treeHash":"0c418bb6693f0a43081a40d4ed807182e16f0361","usage":{"cachedInputTokens":2584172,"cost":{"source":"runtime","usdMicros":2187153},"inputTokens":59,"model":"claude-opus-5-5","outputTokens":36398,"runtime":"claude","turns":37,"wallClockMs":3183866}},{"artifacts":[],"attempt":1,"bundleHash":"d731e4bb19e0b524da8a482b7051a707215bd67568cc9f3f9e31598e8bd2ea62","device":"d981a5cee43d1fd6","findings":[],"hash":"aa460542dff5d234f9bd10fcac1af55616f663f5f8d4e08aab3d0f75d3bbc7c1","nodeId":"6421bad7-b48b-4e36-98d0-edd3e1ff57ae","outcome":"completed","summary":"Created `launch.json` with both accepted contracts, matching constructor arguments and dependency order.\n\nVerified compiled ABIs, manifest structure, and file scope. `forge build` succeeded; all 41 tests passed with the supplied fuzz seed.\n\nOnly `launch.json` changed. Notes preserve the existing Core availability and keeper requirements.","treeHash":"79c5f694047fe4e071a5a363bb697af8019b261f","usage":{"cachedInputTokens":616320,"cost":{"source":"estimated","usdMicros":236328},"inputTokens":43382,"model":"gpt-6.1-sol","outputTokens":2630,"runtime":"codex","turns":4,"wallClockMs":145310}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"918f8261a6fd589c","findings":[{"citation":"resolved","description":"Boundary x invariant. The brief requires 'Always maintain between 12 and 15 active markets' that users can bet on. Each lane holds exactly one market; it stops accepting bets at closesAt = startsAt + duration/2 (MorningEvening: at startsAt, i.e. a full day before expiry), but _roll() creates the replacement only once block.timestamp >= endsAt. So every lane is un-bettable for the second half of each cycle (lane 4 for 24h out of every ~48h, and it only covers every second UTC day), and because all 1-day lanes open together their closed halves coincide. This happens with all data sources healthy and a keeper calling maintain() every 10 minutes; it is not the outage case the README discloses. bet() reverts BettingClosed for those lanes and the UI shows 'Observing'.","line":383,"path":"src/SqueekPredict.sol","reproduction":"Deploy with a live Core mock (frontier = now) and feeds updating hourly, call maintain() at T0 (all 14 lanes Open and bettable), then warp +600s and call maintain() 2,016 times (14 days). Count lanes with status==Open && block.timestamp < closesAt after each call. Measured on this tree (forge test, scratch harness): T0+3.5h -> 12 bettable; T0+13h -> 6; T0+16.8h -> 4; first 0 bettable at T0+421,200s (4d 21h); 1,571 of 2,016 samples (78%) have fewer than 12 bettable markets, 581 (29%) have 3 or fewer, 39 have none. Expected: >= 12 markets accepting bets at all times while sources are healthy. Actual: bet(id, side) on e.g. lane 9 at T0+13h reverts BettingClosed and no other market of that type exists until T0+24h. Fix direction that keeps the design: schedule/open the successor generation of a lane when the current one reaches closesAt (the lane then has one observing and one bettable market), rather than at endsAt.","severity":"medium","snippet":"        m.closesAt = kind == Kind.MorningEvening ? startsAt : startsAt + duration / 2;","title":"Betting closes halfway through every lane and the replacement is only created at expiry, so the number of bettable markets falls far below 12 (to 0) under perfect upkeep"},{"citation":"resolved","description":"Math precision / zero-rounding at a scale conversion. Lane 8 stores strike = block.basefee in wei (src/SqueekPredict.sol:379). The card question converts it to gwei and prints at most 6 fraction digits, i.e. a resolution of 1,000 wei. Sepolia's base fee is currently 15-26 wei (eth_feeHistory over the last 1,024 blocks on 2026-11-06: min 15, max 26, mode 17), so every strike below 500 wei prints as 0 and 500-1,499 wei prints as 0.000001. The strike is shown nowhere else in the UI. Users are told the question is 'above 0 gwei' (which reads as a certain Yes) while the contract resolves value > 17 wei, which was false in 57% of the sampled blocks (Under/equal wins).","line":39,"path":"web/lib/markets.js","reproduction":"node -e \"import('./web/lib/markets.js').then(m=>console.log(m.question({templateId:8,status:1,strike:17n})))\" prints 'Sepolia base fee above 0 gwei in 24h?'. Same output for strike 7n, 26n and 499n; 500n and 1000n both print '0.000001 gwei'. Expected: the exact strike (e.g. '17 wei' or '0.000000017 gwei'). Actual: '0 gwei'. A user who bets Over because the fee is obviously above 0 loses the whole net stake when the boundary block's base fee is 17 wei or lower (_finish: winner = value > strike ? 1 : 0).","severity":"medium","snippet":"(Number(s) / 1e9).toLocaleString('en-US', { maximumFractionDigits: 6 }) + ' gwei'","title":"Base-fee market strike is displayed as \"0 gwei\": the wei-scale strike is rounded away by a 6-decimal gwei format"},{"citation":"resolved","description":"Boundary x invariant (outside the pure-math guides but severe enough to report; SECURITY.md lists it as an accepted limitation). For the monotone threshold lanes (SqueekBlocks 0/1/2, LongestRun 3, SqueekWork 10, ActiveRounds 11, EthBlocks 9/13 partially) the result can only grow during the window, and betting stays open until startsAt + duration/2. Once Core has certified rounds inside the window whose cumulative value already exceeds the strike, side 1 is certain, yet bet() still accepts stakes. Parimutuel payout is by final pool share, so the late stake dilutes the bettors who took the risk and extracts most of the losing pool. Real Core history makes this the normal case, not an edge: three active rounds earn about 120 blocks (Core steers to 40 blocks per active round; day 12 had 841 blocks over 22 active rounds) versus the 6h strike of 100, and daily blocks were 658-1,176 on all 13 certified days versus the strike of 400.","line":244,"path":"src/SqueekPredict.sol","reproduction":"Lane 0 (More than 100 Squeek blocks in 6h), market opened by maintain(). Alice bets 1 ETH on side 1 and Bob 1 ETH on side 0 before startsAt. Core then certifies cumulativeBlockUnits 5,000e9 at round startRound and 5,120e9 at startRound+3 (120 blocks > strike 100: outcome fixed). At startsAt + 2h (closesAt is startsAt + 3h) Carol calls bet{value: 10 ether}(id, 1): accepted. At endsAt+1 settleSqueek(id) pays Alice 1.08 ETH and Carol 10.8 ETH (measured with a scratch forge test on this tree). Expected for Alice without the post-outcome bet: 0.99 * 1.98/0.99 = 1.98 ETH. Actual: 1.08 ETH; Carol gains 0.8 ETH with zero outcome risk. Fix direction preserving the design: close betting at startsAt for the monotone-threshold lanes (as already done for MorningEvening), or reject bets once the certified partial value exceeds the strike.","severity":"medium","snippet":"        if (m.status != Status.Open || block.timestamp >= m.closesAt) revert BettingClosed();","title":"Bets are accepted after a cumulative-threshold outcome is already certified on-chain, letting a late bettor take the losing pool risk-free from earlier winners"},{"citation":"resolved","description":"Numeric boundary check of the template constants against the pinned Core (0x74665dAecA35A2303e767E76a2FF1FB6F93e119f, read through the public Sepolia RPC https://ethereum-sepolia-rpc.publicnode.com with roundPage(r,r,0) at UTC-day endpoints, rounds 3..1875). Daily Squeek blocks over the 13 complete certified days: 1176, 1129, 1036, 959, 1033, 974, 1014, 923, 892, 918, 982, 658, 841 (strike 400: Over on 13/13, minimum 1.6x the strike). Every run of 7 consecutive days sums to 6,228-7,321 blocks (strike 2,800: Over on 7/7 windows, minimum 2.2x the strike). Daily work: 12934, 12803, 12025, 10939, 11612, 11205, 11801, 10712, 10232, 10277, 11063, 7667, 9543 (lane 10 strike 5,000 at line 146: Over on 13/13). Longest active-round streak on sampled days 0,3,6,9,11,12: 12, 11, 12, 14, 11, 13 rounds = 110-140 minutes (strike 30 minutes: Over on 6/6). Core steers difficulty to 40 blocks per active round, so 400 blocks/day needs only ~10 active rounds while the hamster averages ~24. In a parimutuel pool a certain outcome means every rational stake is on side 1: winners get their net stake back minus the 1% fee, and the 50/50 display on an empty pool misrepresents the odds to the first bettor. By contrast lanes 0 (6h blocks: 293, 77, 0, 548, 207, 114, 0, 661 vs 100) and 11 (active rounds 28, 25, 26, 24, 17, 22 vs 24) are balanced. The native lane 9 is also one-sided so far, though narrowly: first-block-at-or-after counts over the last seven 24h windows on Sepolia were 7163, 7168, 7143, 7140, 7131, 7058, 7090 against the strike of 7,000 (Over 7/7), while lane 13 gave 1784, 1780, 1715, 1779, 1782, 1764, 1776, 1768 against 1,750 (Over 7/8).","line":137,"path":"src/SqueekPredict.sol","reproduction":"State: any day in the Core's certified history. measure(Blocks, dayStart, dayStart+1 days) returns 658..1176 > 400, measure(Work, ...) returns 7667..12934 > 5000, measure(LongestRun, ...) returns 6600..8400 > 1800, so lanes 1, 3 and 10 would have resolved winner = 1 on every observed day and lane 2 on every observed week. Expected: thresholds near the median of the metric (about 970 blocks/day, 6,700 blocks/week, 11,000 work/day, a streak of about 120 minutes) so both sides are live. Actual: a bet on side 0 of lanes 1, 2, 3, 10 would have lost on every day on record.","severity":"low","snippet":"        if (slot == 1) return (Kind.SqueekBlocks, 1 days, 400);\n        if (slot == 2) return (Kind.SqueekBlocks, 7 days, 2800);\n        if (slot == 3) return (Kind.LongestRun, 1 days, 30 minutes);","title":"Hard-coded strikes for lanes 1, 2, 3 and 10 lie far outside the certified SqueekCore distribution, so those markets are foregone conclusions"},{"citation":"resolved","description":"Numeric boundary between three hard-coded constants and the chain's real gas schedule. Sepolia now prices state creation far above the Cancun schedule the repository tests run on: eth_estimateGas on https://ethereum-sepolia-rpc.publicnode.com (2026-11-06, head 12076395) gives 24,402 gas for an empty create and 135,215 / 247,311 / 358,291 with one / two / three fresh SSTOREs, i.e. about 111,000 gas per new storage slot (deploying SqueekPredict alone estimates 26,159,584 gas, SqueekMetrics 6,744,758). _debit() writes positions[id][user].paid, which is a fresh slot for every payee (Position packs the two uint128 stakes into slot 0 and paid into slot 1), so every payout costs about 134,000 gas before the recipient runs. Measured with eth_simulateV1 against live Sepolia state (deploy, maintain, 61 bettors of 0.001 ETH on lane 0, warp past endsAt): settleSqueek(1) with 20 pushes = 3,324,855 gas and pay(1,40) = 5,361,207 gas for plain EOAs; with recipients that are contracts whose receive() uses the 35,000-gas PUSH_GAS allowance and accepts, settleSqueek(1) = 4,030,615 and pay(1,40) = 6,772,727. The keeper always sends pay(id, 40) (scripts/keeper-lib.mjs:60) and refuses any transaction whose estimate x 125/100 exceeds 8,000,000: 6,772,727 x 1.25 = 8,465,908, so the batch is never sent (30 such recipients among the 40 are enough: 5,361,207 + 30 x 35,288 = 6.42M > 6.4M). The queue for that market then never advances, and dispatch() decrements the per-cycle budget before send() throws (scripts/keeper-lib.mjs:24), so each stuck market permanently consumes one of the 12 transaction slots in every cycle. Jobs are processed in a stable order (current lanes, then retained pending ids oldest first), so once 12 markets are stuck (11 when maintain is due) no market that enters Awaiting afterwards is ever settled or expired by the keeper: price and Squeek markets, which are never settleable in the cycle that rolls them, sit until someone calls the contract by hand and are refunded instead of resolved. Under the Cancun schedule the repository's tests run on, the same two pay(1,40) batches cost 1,729,984 and 3,141,504 gas (forge, same 61-bettor scenario), which is why the bundled tests do not see this. The contract-side constants were also checked against the live schedule and currently fit: opening lane 0 through activate costs about 247,000 of its 300,000 gas cap (two fresh slots), every other lane 137,000-160,000, and the first maintain() opens all 14 lanes for 2,006,243 gas.","line":52,"path":"scripts/keeper.mjs","reproduction":"1) Gas: eth_simulateV1 on the public Sepolia RPC with calls [create a Core stub that reports a live frontier and no activity, create SqueekMetrics over it, create SqueekPredict with the two real Chainlink proxies, maintain(), 61 x (contract whose constructor calls bet{value: 0.001 ether}(1, 0) and whose runtime is 0x5b5a6108001060005700, a receive that spends the stipend and returns)], then a block 7h later with [settleSqueek(1), pay(1,40)]: pay(1,40) returns status 1 with gasUsed 6,772,727 and 40 Paid logs. scripts/keeper.mjs computes gasLimit = estimate * 125 / 100 >= 8,465,908 > 8,000,000 and throws 'Transaction exceeds the keeper gas budget.' Cost to cause it per market: about 60 bets of 0.001 ETH placed on both sides (returned minus the 1% fee). 2) Starvation: run runCycle from scripts/keeper-lib.mjs with 12 Resolved markets (paymentCursor 20, bettorCount 61) whose pay send throws that error, one Awaiting market past endsAt+48h whose expire.staticCall succeeds, and 14 open current lanes. Result over 5 consecutive cycles: {completed:0, attempted:12} each cycle, 12 'gas budget' log lines, nothing sent, the expirable market still status 2. Expected: a batch that does not fit is retried smaller (pay(id, n) accepts 1..40) and a job that cannot be sent does not consume the cycle budget, so later settlements proceed. Actual: permanent stall of the payout queue and, with 12 such markets, of all later settlement.","severity":"medium","snippet":"    if (gasLimit > 8_000_000n) throw new Error('Transaction exceeds the keeper gas budget.');","title":"Keeper gas cap and fixed pay(id,40) batch are sized for Cancun gas; on live Sepolia a 40-payee batch needs 6.77M gas, the x1.25 limit exceeds 8,000,000, and each stuck queue burns one of the 12 per-cy"},{"citation":"resolved","description":"Boundary x invariant: the 'needed' predicate is computed from status alone, while the action it triggers is conditional on the source being live. A Scheduled lane whose source cannot open (Core paused or more than 6h stale, feed stale, wrong decimals) makes checkUpkeep return true in every block, and performUpkeep/maintain then calls this.activate for each such lane, catches the revert and emits ActivationDeferred, leaving state identical. This is not hypothetical: the pinned SqueekCore 0x74665dAecA35A2303e767E76a2FF1FB6F93e119f reports settledThroughRound 1973 (certified through 2026-10-26 16:20 UTC, unpaused), so the seven Squeek lanes (0,1,2,3,4,10,11) are in exactly this state from the first block after launch. Any automation registered against the standard checkUpkeep/performUpkeep pair (the reason those signatures exist) is triggered every block and drains its balance with zero progress; off-chain consumers of checkUpkeep cannot tell 'due' from 'blocked'. The bundled keeper avoids it only because it ignores checkUpkeep and probes open.staticCall per lane.","line":231,"path":"src/SqueekPredict.sol","reproduction":"Deploy SqueekMetrics over a Core stub returning genesis 1791847800, roundDuration 600, blockUnitsPerBlock 1e9, settledThroughRound 1973, paused false; warp to 1793966016; deploy SqueekPredict with a fresh 8-decimal feed; call performUpkeep(\"\") once (the 7 feed/native lanes open). Then for 5 consecutive blocks: checkUpkeep(\"\") returns (true, \"\"); performUpkeep(\"\") uses 233,165-233,168 gas (Cancun schedule), emits exactly 7 ActivationDeferred logs, and nextMarketId and activeMarketIds() are unchanged; checkUpkeep is still true afterwards (scratch forge test on this tree; 7 lanes remain Scheduled). Expected: checkUpkeep returns false when the only pending lanes cannot currently be activated (e.g. evaluate the same source-liveness conditions, or return performData naming the lanes that can open). Actual: needed == true indefinitely with a no-op perform.","severity":"low","snippet":"            if (m.status == Status.Scheduled || (m.status == Status.Open && block.timestamp >= m.endsAt)) {","title":"checkUpkeep reports work for lanes performUpkeep cannot open: with the pinned Core in its current state every upkeep spends ~233k gas and changes nothing, forever"},{"citation":"resolved","description":"Boundary at an external data source (outside the math guides; reported because it blocks the documented launch step). The script proves 'newly deployed in this block' with eth_getCode at receipt.blockNumber and receipt.blockNumber - 1, using the default --rpc-url https://ethereum-sepolia-rpc.publicnode.com. That endpoint is pruned and load-balanced: on 2026-11-06 eth_getCode for an existing contract at head-128 and head-1000 returned 'historical state ... is not available' on one of three attempts each, and at head-10500 and older always returned code 4444 'pruned history unavailable: requested 12065968, earliest available 12066396' (about 10,080 blocks, 33.6 hours, of history). The script has no retry and treats the error as fatal, so web/config.json is never written and the site stays in preview. Passing an archive endpoint through --rpc-url works around it but that URL is then written into web/config.json as the public website's rpcUrl.","line":21,"path":"scripts/configure-site.mjs","reproduction":"Query eth_getCode(0x74665dAecA35A2303e767E76a2FF1FB6F93e119f, head-10500) on the default RPC: error 4444 'pruned history unavailable'. Therefore: launch at block B, run node scripts/configure-site.mjs --address <engine> --deployment-tx <tx> once head > B + ~10,080 (or with bad luck any time after head > B + ~128). Expected: the script verifies the deployment and writes web/config.json. Actual: provider.getCode(address, receipt.blockNumber) rejects, the script exits with the RPC error and no configuration is written. The same facts are available without historical state: the receipt's contractAddress / the fourteen MarketScheduled logs at that address in that transaction already prove creation in that block.","severity":"low","snippet":"  const previous = await provider.getCode(address, receipt.blockNumber - 1);","title":"configure-site.mjs needs historical state the default public RPC does not keep, so the documented post-launch step fails (intermittently at once, always after about 33 hours)"}],"hash":"ad2f814b91fa1f220f0bd55f6cfc44dc507ed059a9da11b8f6798d3428b12531","nodeId":"95bd5ccb-610b-4dfb-818f-58d5673a8e8f","outcome":"completed","summary":"partial review: the turn budget ran out with 7 finding(s) written.\n","treeHash":null,"usage":{"cachedInputTokens":8660780,"cost":{"source":"runtime","usdMicros":12604797},"inputTokens":101,"model":"claude-fable-5-1","outputTokens":116550,"runtime":"claude","turns":53,"wallClockMs":1296553}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"c9743cd9807c4c3a","findings":[{"citation":"resolved","description":"Merged from audit_permissions (23e6bbb6). Every outcome transaction (settleSqueek, settlePrice, settleNative, expire) ends in _finish -> _payBatch(id, 20), which pushes ETH to the first 20 entries of bettors[id] in the order they first bet. PUSH_GAS = 35_000 caps what the recipient executes but not what the caller pays: the Solidity low-level call copies the callee's return data into caller memory even though it is discarded, and that copy is repeated per recipient, so 20 recipients that `revert(0, 116000)` make the settler pay the quadratic memory expansion cost (~116 kB per recipient). SECURITY.md line 11 promises 'Automatic calls use bounded gas and ignore failed recipients'. Consequences: (1) scripts/keeper.mjs line 52 refuses any transaction whose estimate*1.25 exceeds 8,000,000, so the shipped automation never settles such a market; (2) the attacker, who holds MIN_BET on both sides through the 20 bombs plus a real stake, settles by hand (~12.7M gas, below the Sepolia block limit) only when its real stake won and stays silent when it lost; (3) for native lanes 8, 9 and 13 the header proof expires after 256 blocks (~51 min) and for the others after RESOLUTION_GRACE, after which only expire() remains and the loser's net stake is refunded instead of paid to the winners; (4) pay(id, 40) for that market grows ~4x by the same quadratic rule. Cost to the attacker: 20 x 2 x MIN_BET = 0.04 ETH, recoverable with claim(). Rated high because funds are paid to the wrong party (the loser is refunded) under conditions one bettor controls and the only remedy is a manual ~12.7M-gas transaction by someone who notices. Design-preserving fix: make the push with an assembly `call(PUSH_GAS, user, amount, 0, 0, 0, 0)` so zero return bytes are copied; claim() is unaffected since the claimer picks its own recipient.","line":465,"path":"src/SqueekPredict.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {SqueekPredict} from \"src/SqueekPredict.sol\";\nimport {SqueekMetrics} from \"src/SqueekMetrics.sol\";\nimport {ISqueekCore} from \"src/interfaces/ISqueekCore.sol\";\n\n/// Minimal certified ledger: one active round carrying all work/blocks, frontier set by the test.\ncontract BombCore is ISqueekCore {\n    uint64 public genesis;\n    uint64 public frontier;\n    uint64 public activeRound;\n    uint64 public work;\n    uint96 public units;\n\n    constructor(uint64 g) {\n        genesis = g;\n    }\n\n    function setFrontier(uint64 f) external {\n        frontier = f;\n    }\n\n    function certify(uint64 round, uint64 work_, uint96 units_) external {\n        activeRound = round;\n        work = work_;\n        units = units_;\n    }\n\n    function configuration() external view returns (CoreConfiguration memory c) {\n        c.genesisTimestamp = genesis;\n        c.roundDuration = 600;\n        c.blockUnitsPerBlock = 1_000_000_000;\n    }\n\n    function _point(uint64 round) private view returns (LedgerPosition memory p) {\n        if (activeRound != 0 && round >= activeRound) p = LedgerPosition(work, units, 10_000);\n    }\n\n    function state() external view returns (CoreState memory s) {\n        LedgerPosition memory p = _point(frontier);\n        s.settledThroughRound = frontier;\n        s.cumulativeWork = p.cumulativeWork;\n        s.cumulativeBlockUnits = p.cumulativeBlockUnits;\n    }\n\n    function roundPage(uint64 afterRound, uint64 throughRound, uint256 maxRecords)\n        external\n        view\n        returns (RoundPage memory page)\n    {\n        require(afterRound <= throughRound && throughRound <= frontier, \"bounds\");\n        page.certifiedThroughRound = throughRound;\n        page.start = _point(afterRound);\n        page.end = _point(throughRound);\n        if (maxRecords != 0 && activeRound > afterRound && activeRound <= throughRound) {\n            page.rounds = new RoundCheckpoint[](1);\n            page.rounds[0] = RoundCheckpoint(activeRound, work, units, 10_000, bytes32(0));\n        }\n    }\n}\n\n/// A bettor that stakes the minimum on both sides (so it is pushed whichever side wins) and\n/// answers the automatic payment by reverting with as much return data as PUSH_GAS can buy.\ncontract ReturnBomb {\n    function place(SqueekPredict p, uint256 id) external payable {\n        p.bet{value: msg.value / 2}(id, 0);\n        p.bet{value: msg.value / 2}(id, 1);\n    }\n\n    function withdraw(SqueekPredict p, uint256 id, address payable to) external {\n        p.claim(id, to);\n    }\n\n    receive() external payable {\n        assembly {\n            revert(0, 116000)\n        }\n    }\n}\n\ncontract ReturnBombSettlementTest is Test {\n    uint64 constant GENESIS = 1_728_000_000;\n    /// scripts/keeper.mjs refuses a transaction when estimate * 125 / 100 > 8_000_000.\n    uint256 constant KEEPER_ESTIMATE_BUDGET = 6_400_000;\n\n    function testSettlementGasStaysBoundedWhenPushedRecipientsRevert() public {\n        vm.warp(GENESIS + 1 days + 1);\n        BombCore core = new BombCore(GENESIS);\n        core.setFrontier(144);\n        SqueekMetrics metrics = new SqueekMetrics(address(core));\n        SqueekPredict predict = new SqueekPredict(address(metrics), address(0xFEED), address(0xFEED));\n\n        // The attacker takes the first twenty bettor slots of lane 0 (market 1) for 0.04 ether.\n        ReturnBomb[] memory bombs = new ReturnBomb[](20);\n        for (uint256 i; i < 20; ++i) {\n            bombs[i] = new ReturnBomb();\n            bombs[i].place{value: 0.002 ether}(predict, 1);\n        }\n        // Real money: the attacker is on NO, an honest user on YES.\n        address attacker = makeAddr(\"attacker\");\n        address honest = makeAddr(\"honest\");\n        vm.deal(attacker, 10 ether);\n        vm.deal(honest, 10 ether);\n        vm.prank(attacker);\n        predict.bet{value: 10 ether}(1, 0);\n        vm.prank(honest);\n        predict.bet{value: 10 ether}(1, 1);\n\n        // 101 certified blocks > strike 100: YES wins, the attacker's stake should go to `honest`.\n        SqueekPredict.Market memory m = predict.getMarket(1);\n        uint64 startRound = (m.startsAt - GENESIS) / 600;\n        core.certify(startRound + 1, 5000, 101 * 1_000_000_000);\n        core.setFrontier(startRound + 36);\n        vm.warp(m.endsAt);\n\n        uint256 beforeGas = gasleft();\n        predict.settleSqueek(1);\n        uint256 used = beforeGas - gasleft();\n        emit log_named_uint(\"settleSqueek gas with 20 reverting recipients\", used);\n\n        // Twenty pushes of PUSH_GAS = 35_000 each are supposed to bound this transaction.\n        assertLt(used, KEEPER_ESTIMATE_BUDGET, \"settlement gas is controlled by the pushed recipients\");\n    }\n}","reproduction":"State: lane 0 (market 1) freshly scheduled, Core live. (1) 20 contracts each call bet{value: 0.001 ether}(1, 0) and bet{value: 0.001 ether}(1, 1); their receive() is `assembly { revert(0, 116000) }`, so they occupy bettors[1][0..19] and hold a winning-side stake whichever side wins. (2) attacker EOA: bet{value: 10 ether}(1, 0); honest EOA: bet{value: 10 ether}(1, 1). (3) Core certifies 101 blocks inside the window (YES wins); warp to endsAt. (4) settleSqueek(1). Expected: gas bounded by 20 pushes of PUSH_GAS (about 1.2M measured with recipients that revert with empty data), inside the keeper's 6.4M estimate budget. Actual, re-run on this tree: 12,651,148 gas, so the keeper throws 'Transaction exceeds the keeper gas budget.' before sending; at endsAt + 48h expire(1) refunds the attacker's 9.9 ETH net stake that should have gone to the honest YES bettor. Run: forge test --match-path test/scratch/Proof_23e6bbb66e43.t.sol -vv -> FAIL 'settlement gas is controlled by the pushed recipients: 12651148 >= 6400000'.","severity":"high","snippet":"            (bool success,) = payable(user).call{value: amount, gas: PUSH_GAS}(\"\");","title":"Automatic payouts copy the recipient's revert data: 20 hostile bettors raise every settlement from ~1.2M to ~12.7M gas, past the shipped keeper's budget, giving a losing bettor a refund option"},{"citation":"resolved","description":"Merged from audit_permissions (7fe0cd5f), audit_math (36885e9f) and audit_economics (249452e1): one root cause. Every lane except MorningEvening sets closesAt = startsAt + duration/2 (line 383), and bet() only checks status == Open and block.timestamp < closesAt (line 244). For the monotone metrics (SqueekBlocks slots 0/1/2, LongestRun 3, SqueekWork 10, ActiveRounds 11) Core certifies rounds inside the window while betting is still open, and bet() itself requires Core to be at most 6h behind, so the partial value is public on-chain through SqueekMetrics.measure over the certified sub-window. Once it exceeds the strike, Over is certain, yet a stake on Over is accepted and paid pro rata from the Under pool, diluting the bettors who took the risk. Repeatable up to MAX_POOL. SECURITY.md line 35 discloses the possibility as a user caveat; for a parimutuel pool it is an extractable transfer. Design-preserving fix: set closesAt = startsAt for the cumulative Squeek lanes (as MorningEvening already does), or refuse a stake when measure() over the certified part of the window already exceeds the strike.","line":244,"path":"src/SqueekPredict.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {SqueekPredict} from \"src/SqueekPredict.sol\";\nimport {SqueekMetrics} from \"src/SqueekMetrics.sol\";\nimport {ISqueekCore} from \"src/interfaces/ISqueekCore.sol\";\n\n/// Minimal certified ledger: one active round carrying all work/blocks, frontier set by the test.\ncontract ScratchCore is ISqueekCore {\n    uint64 public genesis;\n    uint64 public frontier;\n    uint64 public activeRound;\n    uint64 public work;\n    uint96 public units;\n\n    constructor(uint64 g) {\n        genesis = g;\n    }\n\n    function setFrontier(uint64 f) external {\n        frontier = f;\n    }\n\n    function certify(uint64 round, uint64 work_, uint96 units_) external {\n        activeRound = round;\n        work = work_;\n        units = units_;\n    }\n\n    function configuration() external view returns (CoreConfiguration memory c) {\n        c.genesisTimestamp = genesis;\n        c.roundDuration = 600;\n        c.blockUnitsPerBlock = 1_000_000_000;\n    }\n\n    function _point(uint64 round) private view returns (LedgerPosition memory p) {\n        if (activeRound != 0 && round >= activeRound) p = LedgerPosition(work, units, 10_000);\n    }\n\n    function state() external view returns (CoreState memory s) {\n        LedgerPosition memory p = _point(frontier);\n        s.settledThroughRound = frontier;\n        s.cumulativeWork = p.cumulativeWork;\n        s.cumulativeBlockUnits = p.cumulativeBlockUnits;\n    }\n\n    function roundPage(uint64 afterRound, uint64 throughRound, uint256 maxRecords)\n        external\n        view\n        returns (RoundPage memory page)\n    {\n        require(afterRound <= throughRound && throughRound <= frontier, \"bounds\");\n        page.certifiedThroughRound = throughRound;\n        page.start = _point(afterRound);\n        page.end = _point(throughRound);\n        if (maxRecords != 0 && activeRound > afterRound && activeRound <= throughRound) {\n            page.rounds = new RoundCheckpoint[](1);\n            page.rounds[0] = RoundCheckpoint(activeRound, work, units, 10_000, bytes32(0));\n        }\n    }\n}\n\ncontract LateBetDecidedTest is Test {\n    uint64 constant GENESIS = 1_728_000_000;\n\n    function testNoStakeAcceptedAfterCertifiedLedgerDecidedTheMarket() public {\n        vm.warp(GENESIS + 1 days + 1);\n        ScratchCore core = new ScratchCore(GENESIS);\n        core.setFrontier(144);\n        SqueekMetrics metrics = new SqueekMetrics(address(core));\n        // Feeds are irrelevant for lane 0; any nonzero address satisfies the constructor.\n        SqueekPredict predict = new SqueekPredict(address(metrics), address(0xFEED), address(0xFEED));\n\n        address alice = makeAddr(\"alice\");\n        address bob = makeAddr(\"bob\");\n        address carol = makeAddr(\"carol\");\n        vm.deal(alice, 10 ether);\n        vm.deal(bob, 10 ether);\n        vm.deal(carol, 100 ether);\n\n        // Lane 0: \"Squeek blocks in 6h over 100\". Honest bettors take both sides before it starts.\n        vm.prank(alice);\n        predict.bet{value: 10 ether}(1, 0);\n        vm.prank(bob);\n        predict.bet{value: 10 ether}(1, 1);\n        SqueekPredict.Market memory m = predict.getMarket(1);\n        assertEq(m.strike, 100);\n        assertEq(m.endsAt - m.startsAt, 6 hours);\n\n        // Two hours into the six-hour window Core has certified 101 blocks: more than the strike.\n        // cumulativeBlockUnits never decreases, so YES is already the certain outcome.\n        vm.warp(m.startsAt + 2 hours);\n        uint64 startRound = (m.startsAt - GENESIS) / 600;\n        core.certify(startRound + 1, 5000, 101 * 1_000_000_000);\n        core.setFrontier(startRound + 12);\n\n        vm.prank(carol);\n        bool accepted;\n        try predict.bet{value: 100 ether}(1, 1) {\n            accepted = true;\n        } catch {}\n\n        if (accepted) {\n            // Show the transfer this allows: settle and compare carol against bob.\n            vm.warp(m.endsAt);\n            core.setFrontier(startRound + 36);\n            predict.settleSqueek(1);\n            emit log_named_uint(\"carol risk-free profit (wei)\", carol.balance - 100 ether);\n            emit log_named_uint(\"bob payout (wei), 19.8 ether without carol\", bob.balance);\n        }\n        assertFalse(accepted, \"stake accepted after the certified ledger had already decided the market\");\n    }\n}","reproduction":"State: lane 0 (market 1, 'more than 100 Squeek blocks in 6h'), Core genesis G, opened at G+1d+1s so startsAt is round-aligned, closesAt = startsAt + 3h. (1) alice bet{value: 10 ether}(1, 0); bob bet{value: 10 ether}(1, 1) before startsAt. (2) At startsAt + 2h Core has certified a round inside the window with cumulativeBlockUnits delta 101e9 (101 blocks > strike 100). (3) carol bet{value: 100 ether}(1, 1). Expected: revert (outcome already certified, as on MorningEvening where betting ends at startsAt) or no risk-free profit. Actual: accepted. (4) At endsAt settleSqueek(1) resolves YES: carol receives 108 ether for 100 staked (+8 ether with zero risk after her own 1 ether fee), bob receives 10.8 ether instead of 19.8. Run: forge test --match-path test/scratch/Proof_7fe0cd5f1040.t.sol -vv -> FAIL 'stake accepted after the certified ledger had already decided the market', logs 'carol risk-free profit (wei): 8000000000000000000'. The economics proof (Proof_249452e1c413) fails the same way with an optimally sized stake (+0.801 ETH on a 1 ETH Under pool).","severity":"medium","snippet":"        if (m.status != Status.Open || block.timestamp >= m.closesAt) revert BettingClosed();","title":"Cumulative Squeek lanes keep accepting stakes for half the observation window, after Core has already certified the outcome, so a late bettor takes earlier winners' payout risk-free"},{"citation":"resolved","description":"Merged from audit_math (01eb7911), audit_economics (f2d7cfa4) and the MorningEvening note of audit_flow (46d86d45). Each lane holds exactly one market. It stops accepting bets at closesAt = startsAt + duration/2 (MorningEvening: at startsAt, a full day before expiry), but _maintain line 401 and _beginResolution line 424 only call _roll, which schedules and opens the successor, once block.timestamp >= endsAt. Every lane is therefore un-bettable for the second half of its cycle, and because all one-day lanes open together at launch their closed halves coincide. The brief requires 'Always 12 to 15 active markets' whose cards carry 'two large buttons to bet'. This is the healthy-source case, independent of the Core outage disclosed in the README. MorningEvening additionally observes only alternate UTC days and, when opened at 23:50 UTC, accepts bets for 600 seconds then shows as Open-but-closed for 86,400 seconds. Design-preserving fix: schedule and open the successor generation when the current one reaches closesAt, so each lane always has one bettable market while the other observes.","line":383,"path":"src/SqueekPredict.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {SqueekPredict} from \"src/SqueekPredict.sol\";\nimport {SqueekMetrics} from \"src/SqueekMetrics.sol\";\nimport {ISqueekCore} from \"src/interfaces/ISqueekCore.sol\";\nimport {IAggregatorV3} from \"src/interfaces/IAggregatorV3.sol\";\n\n/// A Core whose certified frontier is always current (the healthy case).\ncontract LiveCore is ISqueekCore {\n    uint64 public genesis;\n    uint64 public frontier;\n\n    constructor(uint64 g) {\n        genesis = g;\n    }\n\n    function setFrontier(uint64 f) external {\n        frontier = f;\n    }\n\n    function configuration() external view returns (CoreConfiguration memory c) {\n        c.genesisTimestamp = genesis;\n        c.roundDuration = 600;\n        c.blockUnitsPerBlock = 1_000_000_000;\n    }\n\n    function state() external view returns (CoreState memory s) {\n        s.settledThroughRound = frontier;\n    }\n\n    function roundPage(uint64, uint64 throughRound, uint256) external pure returns (RoundPage memory page) {\n        page.certifiedThroughRound = throughRound;\n    }\n}\n\n/// A Chainlink-shaped feed updated by the test every hour.\ncontract LiveFeed is IAggregatorV3 {\n    uint80 public latest;\n    mapping(uint80 => uint256) public updatedAt;\n\n    function put(uint80 id, uint256 time) external {\n        latest = id;\n        updatedAt[id] = time;\n    }\n\n    function decimals() external pure returns (uint8) {\n        return 8;\n    }\n\n    function description() external pure returns (string memory) {\n        return \"mock\";\n    }\n\n    function version() external pure returns (uint256) {\n        return 1;\n    }\n\n    function latestRoundData() external view returns (uint80, int256, uint256, uint256, uint80) {\n        return (latest, 2000e8, updatedAt[latest], updatedAt[latest], latest);\n    }\n\n    function getRoundData(uint80 id) external view returns (uint80, int256, uint256, uint256, uint80) {\n        return (id, 2000e8, updatedAt[id], updatedAt[id], id);\n    }\n}\n\ncontract BettableCountTest is Test {\n    uint64 constant GENESIS = 1_728_000_000;\n    SqueekPredict predict;\n    LiveCore core;\n    LiveFeed eth;\n    LiveFeed btc;\n    uint80 round = (uint80(1) << 64) | 100;\n\n    function setUp() public {\n        // One second after a UTC midnight, so the first maintain opens all fourteen lanes together.\n        uint64 t0 = ((GENESIS + 1 days) / 1 days) * 1 days + 1;\n        vm.warp(t0);\n        vm.roll(1000);\n        vm.fee(10 gwei);\n        core = new LiveCore(GENESIS);\n        core.setFrontier(uint64((t0 - GENESIS) / 600));\n        SqueekMetrics metrics = new SqueekMetrics(address(core));\n        eth = new LiveFeed();\n        btc = new LiveFeed();\n        eth.put(round, t0);\n        btc.put(round, t0);\n        predict = new SqueekPredict(address(metrics), address(eth), address(btc));\n        predict.maintain();\n    }\n\n    /// Advance one hour with every source healthy and the keeper calling maintain().\n    function _hour() internal {\n        uint256 now_ = vm.getBlockTimestamp() + 1 hours;\n        vm.warp(now_);\n        vm.roll(vm.getBlockNumber() + 300);\n        core.setFrontier(uint64((now_ - GENESIS) / 600));\n        ++round;\n        eth.put(round, now_);\n        btc.put(round, now_);\n        predict.maintain();\n    }\n\n    function _bettable() internal view returns (uint256 n) {\n        uint256[14] memory ids = predict.activeMarketIds();\n        uint256 now_ = vm.getBlockTimestamp();\n        for (uint256 i; i < 14; ++i) {\n            SqueekPredict.Market memory m = predict.getMarket(ids[i]);\n            if (m.status == SqueekPredict.Status.Open && now_ < m.closesAt) ++n;\n        }\n    }\n\n    function testAtLeastTwelveBettableMarketsWithHealthySources() public {\n        assertEq(_bettable(), 14, \"all lanes open at launch\");\n        uint256 minSeen = 14;\n        uint256 worstHour;\n        for (uint256 h = 1; h <= 48; ++h) {\n            _hour();\n            uint256 n = _bettable();\n            if (n < minSeen) {\n                minSeen = n;\n                worstHour = h;\n            }\n        }\n        emit log_named_uint(\"minimum bettable markets over 48h\", minSeen);\n        emit log_named_uint(\"first hour at that minimum\", worstHour);\n        // At hour 13 every one-day lane has passed closesAt and no replacement exists until hour 24.\n        vm.warp(vm.getBlockTimestamp()); // no-op, documents the measurement point\n        assertGe(minSeen, 12, \"fewer than 12 bettable markets while every source is healthy\");\n    }\n}","reproduction":"Deploy SqueekMetrics over a Core whose frontier tracks the clock and SqueekPredict over feeds updated hourly; warp to one second after a UTC midnight and call maintain() so all 14 lanes open. Every hour: warp +1h, update frontier and feeds, call maintain(), count lanes with status == Open && now < closesAt. Measured on this tree: hours 0-2: 14; hour 3: 13; hours 4-11: 12-14; hour 12: 9; hours 13-23: 5 (the three 7-day lanes plus the two 6-hour lanes); hour 24: 9; hours 38-41 and 46-47: 3. Expected per brief: >= 12 bettable at all times. Actual: bet(id, side) on a one-day lane at hour 13 reverts BettingClosed and no other market of that type exists until hour 24. Run: forge test --match-path test/scratch/BettableCount.t.sol -vv -> FAIL 'fewer than 12 bettable markets while every source is healthy: 3 < 12'.","severity":"medium","snippet":"        m.closesAt = kind == Kind.MorningEvening ? startsAt : startsAt + duration / 2;","title":"Betting closes halfway through every lane but the replacement is only scheduled at endsAt, so with all sources healthy the bettable market count falls to 5 and then 3, against the brief's 12 to 15"},{"citation":"resolved","description":"From audit_math (2dd3aa7b), reproduced. Lane 8 stores strike = block.basefee in wei (src/SqueekPredict.sol:379). The card question divides by 1e9 and formats with at most 6 fraction digits, a resolution of 1,000 wei. Live Sepolia base fee over the last 1,025 blocks (eth_feeHistory, 2026-11-06): min 16, max 25 wei, 618 of 1,025 blocks at or below 17 wei. Every strike below 500 wei prints as '0 gwei' and the strike is shown nowhere else in the UI. A user who bets Over because 'above 0 gwei' is obviously true loses the whole net stake when the boundary block's base fee is at or below the real strike (_finish: winner = value > strike ? 1 : 0), which was the case in about 60% of sampled blocks for a 17 wei strike.","line":39,"path":"web/lib/markets.js","reproduction":"node -e \"import('./web/lib/markets.js').then(m=>console.log(m.question({templateId:8,status:1,strike:17n})))\" prints 'Sepolia base fee above 0 gwei in 24h?'. Same output for strikes 7n, 16n, 26n and 499n; 500n and 1000n both print '0.000001 gwei'. Expected: the exact strike ('17 wei' or '0.000000017 gwei'). Actual: '0 gwei'.","severity":"medium","snippet":"  if (t.kind === 5) return `Sepolia base fee above ${s === null || Number(m.status) === 0 ? 'its opening level' : (Number(s) / 1e9).toLocaleString('en-US', { maximumFractionDigits: 6 }) + ' gwei'} in 24h?`;","title":"Base-fee market question displays the wei-scale strike as '0 gwei': users read a certain Over while the contract resolves against 16-25 wei"},{"citation":"resolved","description":"Merged from audit_flow (31ad7682) and audit_economics (d5e77aa4). settleNative authenticates the boundary header only through blockhash(target.number), and boundary() rejects any target older than 256 blocks (about 51 minutes on Sepolia). Once that window passes, every settleNative call reverts UnprovenHeader for the rest of the 48-hour grace period and the only terminal path is expire(), which refunds net stakes and keeps the 1% fee. Slots 8, 9 and 13 therefore have a 51-minute effective resolution window instead of RESOLUTION_GRACE, nobody is paid to settle, and the losing side has every incentive not to. The README (line 101) and docs/OPERATIONS.md document the window as an operational requirement; it is reported because an ordinary one-hour keeper, RPC or fee outage across a market end changes who gets paid (winners receive their stake back, not the pool), and because finding 1 lets a bettor make the shipped keeper fail during exactly that hour. Design-preserving fix: when blockhash() is zero, read the hash from the EIP-2935 history contract (0x0000F90827F1C53a10cb7A02335B175320002935, 8191-block window) as a fallback, or accept a parentHash chain from a BLOCKHASH-provable recent header back to the boundary header.","line":24,"path":"src/libraries/BlockHeader.sol","reproduction":"State: slot 9 (EthBlocks 1d) Open with alice 1 ETH Over and bob 1 ETH Under; boundary block N has timestamp >= endsAt and parent timestamp < endsAt. (1) vm.roll(N + 257), warp endsAt + 257*12, pin blockhash(N) = keccak(targetHeader). (2) settleNative(id, parentHeader, targetHeader) -> reverts UnprovenHeader although both headers are genuine (line 24: block.number - target.number > 256). Every later call also reverts. (3) warp endsAt + 2 days; expire(id) -> status Refunded, alice receives 0.99 ETH instead of 1.98 ETH, bob gets 0.99 ETH back although he lost. The existing test testForgedHeaderLaterSampleAndExpiredHashAreRejected at vm.roll(8458) exercises the same revert. Expected: a market whose outcome is objectively determined on-chain stays settleable for the full grace period.","severity":"medium","snippet":"            target.number >= block.number || block.number - target.number > 256","title":"Native lanes (BaseFee, EthBlocks) can only be settled within 256 blocks of expiry; a one-hour settler outage turns a decided market into a refund"},{"citation":"resolved","description":"Merged from audit_permissions (257946bf) and audit_flow (64ffb239). Lanes 0, 1, 2, 3, 4, 10 and 11 open only through SqueekMetrics.window(), which calls _requireLive and reverts CoreStale when the Core's certified frontier is more than MAX_REPORTING_DELAY (6 hours) behind the chain clock; bet() repeats the check. Verified on 2026-11-06 via https://ethereum-sepolia-rpc.publicnode.com: SqueekCore 0x74665dAecA35A2303e767E76a2FF1FB6F93e119f state().settledThroughRound = 1973 (0x7b5), paused = false; configuration().genesisTimestamp = 1791847800 (0x6acd6d78); certifiedAt = 1793031600 while the head block timestamp was 1793967468, i.e. 935,868 s (10.8 days) behind. In that state every activate() for those seven lanes reverts, is swallowed by _tryOpen as ActivationDeferred, and the lanes stay Scheduled with bet() reverting CoreStale; only the seven price/native lanes are usable, and with finding 3 the bettable count drops to 3 or 2 for hours of every day. The fail-closed behaviour itself is correct and the README, launch.json notes and docs disclose the stale Core, but the brief's 'always 12 to 15 active markets' and 'immediately usable' are not met at launch. This needs a requester scope decision (for example enough additional public-Sepolia lanes to keep twelve bettable while Core is stale), not a silent change of data source.","line":60,"path":"src/SqueekMetrics.sol","reproduction":"State: MockCore with frontier F such that genesis + F*600 < block.timestamp - 6 hours (the live Core state today). Calls: deploy SqueekMetrics(core), SqueekPredict(metrics, ethFeed, btcFeed); maintain(). Expected: 12-15 markets Open. Actual: ActivationDeferred for market ids 1, 2, 3, 4, 5, 11, 12; getMarket(id).status == Scheduled for those seven; bet{value: 0.001 ether}(1, 1) reverts CoreStale(1793031600); 7 markets Open. Repeating maintain() changes nothing until the Core reporter resumes. Reproduced in test/scratch/Judge.t.sol testUpkeepLoopWithStaleCore (Squeek lane stays Scheduled across repeated upkeep) and by the existing testPausedAndStaleCoreRejectNewStakesButPreserveRefunds.","severity":"medium","snippet":"        if (certifiedAt > block.timestamp || block.timestamp - certifiedAt > MAX_REPORTING_DELAY) {","title":"With the specified SqueekCore's live state (frontier 10.8 days old) seven of fourteen lanes cannot open, so the product launches with at most 7 markets instead of 12 to 15"},{"citation":"resolved","description":"Merged from audit_permissions (6d3a9ca2), audit_flow (173612d2) and audit_math (8f4b09a7). checkUpkeep treats any Scheduled current lane as upkeep needed, but performUpkeep -> _maintain -> _tryOpen can only open it if the lane's source is healthy; otherwise activate() reverts (CoreStale, CorePaused, StalePrice, feed failure), the catch emits ActivationDeferred and the lane stays Scheduled, so checkUpkeep is true again in the next block. With the live Core state (finding 6) seven lanes are in this condition from the first block after launch. Any automation registered against the advertised AutomationCompatible pair is triggered every interval and pays a full transaction with no state change; the bundled keeper avoids it only because it probes open.staticCall per lane. Fix: have checkUpkeep return true for a Scheduled lane only when opening it would succeed (try-call the same view preconditions), or return in performData the ids that can progress.","line":231,"path":"src/SqueekPredict.sol","reproduction":"Deploy as in the existing test setUp (Core frontier current, feeds fresh), maintain() so all lanes open, then warp +7h without advancing the Core frontier and refresh the feeds; maintain() rolls the two 6h lanes and the Squeek 6h replacement cannot open (CoreStale). Then for 3 consecutive calls: checkUpkeep('') returns (true, ''); performUpkeep('') succeeds using 161,195-161,201 gas, nextMarketId is unchanged and the lane is still Scheduled; checkUpkeep('') is true again. Expected: checkUpkeep false once nothing can progress. Actual: true indefinitely. Run: forge test --match-path test/scratch/Judge.t.sol --match-test testUpkeepLoopWithStaleCore -vv -> FAIL 'checkUpkeep keeps reporting work that performUpkeep cannot do'.","severity":"low","snippet":"            if (m.status == Status.Scheduled || (m.status == Status.Open && block.timestamp >= m.endsAt)) {","title":"checkUpkeep reports work that performUpkeep cannot do: with a lane whose source is down it returns true forever and every performUpkeep is a paid no-op"},{"citation":"resolved","description":"From audit_math (4f3f2cb9), reproduced in its premise and by code reading. Live Sepolia prices fresh storage far above Cancun: eth_estimateGas on 2026-11-06 for a creation with 0/3 fresh SSTOREs returned 210,421 / 543,344 gas (about 111,000 per new slot) on both https://ethereum-sepolia-rpc.publicnode.com and https://sepolia.gateway.tenderly.co; deploying SqueekPredict alone estimates 26,344,653 gas and SqueekMetrics 6,799,455. _debit() writes positions[id][user].paid, a fresh slot for every payee (Position packs the two uint128 stakes in slot 0 and paid in slot 1), so each payout costs about 111k before the recipient runs; the specialist's eth_simulateV1 figure of 6,772,727 gas for pay(1, 40) with 40 contract recipients is consistent with the Cancun forge measurement of 3,141,504 plus 40 x ~89k. scripts/keeper-lib.mjs line 60 always sends pay(id, 40) and scripts/keeper.mjs line 52 refuses when estimate*1.25 > 8,000,000 (6,772,727 x 1.25 = 8,465,908). dispatch() decrements the budget (keeper-lib.mjs line 24) before send() throws, so each stuck market consumes one of the 12 per-cycle slots forever, and once 12 markets are stuck no later market is settled by the keeper. Funds are not lost (anyone can call pay(id, n) with a smaller n, or claim), so low. Fix: retry with a smaller batch when the estimate exceeds the budget, and do not charge the cycle budget for a transaction that was never sent.","line":52,"path":"scripts/keeper.mjs","reproduction":"(1) eth_estimateGas on the public Sepolia RPC: data 0x00 -> 0x335f5 (210,421); data 0x600160005560016001556001600255 -> 0x84a70 (543,344); SqueekPredict creation code + constructor args -> 0x191fccd (26,344,653). (2) A Resolved market with 61 bettors whose first 20 were pushed at settlement: the keeper sends pay(id, 40); with ~111k per fresh paid slot plus 40 recipients spending PUSH_GAS the estimate is ~6.77M, gasLimit = 8.47M > 8,000,000, send() throws 'Transaction exceeds the keeper gas budget.' Expected: a smaller batch is retried and the budget is not consumed. Actual: runCycle logs 'payments <id>: Transaction exceeds the keeper gas budget.' every cycle, attempted counts the slot, nothing is sent, paymentCursor stays at 20.","severity":"low","snippet":"    if (gasLimit > 8_000_000n) throw new Error('Transaction exceeds the keeper gas budget.');","title":"Keeper gas cap and fixed pay(id, 40) batch are sized for the Cancun schedule; under live Sepolia's repriced state creation a 40-payee batch does not fit and each stuck queue burns one per-cycle transa"},{"citation":"resolved","description":"From audit_math (36f4a310), reproduced. The script proves 'newly deployed in this block' with eth_getCode at receipt.blockNumber and receipt.blockNumber - 1 against the default --rpc-url https://ethereum-sepolia-rpc.publicnode.com. That endpoint serves roughly 10,000 blocks of historical state: on 2026-11-06 eth_getCode(SqueekCore, head-10500) returned error 4444 'pruned history unavailable: requested 12066040, earliest available 12066461'. The script has no retry and treats the error as fatal, so web/config.json is never written and the site stays in preview. Passing an archive URL works around it but is then written into web/config.json as the public site's rpcUrl. The same proof is available without historical state: the receipt's logs already contain the fourteen MarketScheduled events at that address in that transaction, which the script checks on line 19.","line":21,"path":"scripts/configure-site.mjs","reproduction":"curl -X POST https://ethereum-sepolia-rpc.publicnode.com -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_getCode\",\"params\":[\"0x74665dAecA35A2303e767E76a2FF1FB6F93e119f\",\"<head-10500 hex>\"]}' -> {\"error\":{\"code\":4444,\"message\":\"pruned history unavailable: ...\"}}. Therefore: launch at block B, run `node scripts/configure-site.mjs --address <engine> --deployment-tx <tx>` once head > B + ~10,000 (about 33 hours). Expected: deployment verified and web/config.json written. Actual: provider.getCode(address, receipt.blockNumber) rejects with the RPC error, the script exits and no configuration is written.","severity":"low","snippet":"  const previous = await provider.getCode(address, receipt.blockNumber - 1);","title":"configure-site.mjs needs historical state the default public RPC prunes after about 33 hours, so the documented post-launch step fails"},{"citation":"resolved","description":"From audit_math (995dc4bc), reproduced for lanes 1 and 2 against the live Core. Reading roundPage(r, r, 0).end.cumulativeBlockUnits at genesis-aligned day boundaries (rounds 0, 144, ..., 1872) from SqueekCore on 2026-11-06 gives daily Squeek block counts of 1148, 1135, 1058, 964, 1025, 977, 1041, 909, 894, 980, 912, 775, 841 for the 13 complete certified days: Over on 13/13 days against the strike of 400 (minimum 1.9x), and every 7-day window sums to 6,000-7,300 blocks against the strike of 2,800. In a parimutuel pool a certain outcome means every rational stake is on side 1, winners get back their net stake minus the fee, and the 50/50 display on an empty pool misrepresents the odds to the first bettor. The specialist's figures for lane 10 (work 7,667-12,934 vs 5,000) and lane 3 (streaks of 110-140 minutes vs 30) were not re-measured here and are left as leads. Lane 0 (100 blocks in 6h) is balanced. Fix: thresholds near the metric's median (about 950 blocks/day and 6,700 blocks/week), or a strike derived at open from the trailing certified value, as the price lanes do.","line":137,"path":"src/SqueekPredict.sol","reproduction":"cast call 0x74665dAecA35A2303e767E76a2FF1FB6F93e119f 'roundPage(uint64,uint64,uint256)' 1728 1728 0 and 1872 1872 0 --rpc-url https://ethereum-sepolia-rpc.publicnode.com; word 7 of each result is end.cumulativeBlockUnits; the difference / 1e9 is 841 blocks. Repeating for every consecutive pair of day boundaries gives the 13 values above, all > 400, so measure(Blocks, dayStart, dayStart + 1 days) would have resolved winner = 1 on every certified day and lane 2 on every certified week. Expected: thresholds that leave both sides live. Actual: a bet on side 0 of lanes 1 or 2 would have lost on every day on record.","severity":"low","snippet":"        if (slot == 1) return (Kind.SqueekBlocks, 1 days, 400);","title":"Hard-coded Squeek block strikes for lanes 1 and 2 lie far below the certified Core distribution, so those markets are foregone conclusions"},{"citation":"resolved","description":"From audit_flow (49105f97), kept as a documented trust boundary, not a defect. SqueekCore at the specified address is an EIP-1967 proxy with guardian and reporter roles. _genesis() re-checks roundDuration and blockUnitsPerBlock but not genesisTimestamp, so an implementation upgrade or re-initialisation that moves genesis would shift which rounds a funded market measures ((startsAt - genesis) / 600) without any revert. Low-cost hardening: record configuration().genesisTimestamp in the market at open and revert (fail closed to refund) in settleSqueek if it changed.","line":131,"path":"src/SqueekMetrics.sol","reproduction":"State: a market opened with genesis G; before settlement the Core's configuration().genesisTimestamp becomes G + 600 with roundDuration and blockUnitsPerBlock unchanged. settleSqueek(id): measure maps startsAt/endsAt to rounds one lower than at open and resolves on a window shifted by one round. Requires privileged third-party action; reported as a trust assumption only.","severity":"info","snippet":"        if (c.roundDuration != ROUND_SECONDS || c.blockUnitsPerBlock != BLOCK_UNITS) {","title":"Trust assumption: seven lanes resolve on an upgradeable third-party Core whose genesis is not re-checked after open"},{"citation":"resolved","description":"Own observation for the launch's gas ceiling, not a defect. Runtime sizes are within EIP-170 (SqueekPredict 13,753 bytes, SqueekMetrics 4,227) and init code within 49,152, but Sepolia now prices code deposit at roughly 1,500 gas per byte and fresh storage at about 111k per slot: eth_estimateGas for the SqueekPredict creation with constructor arguments returned 26,344,653 and for SqueekMetrics 6,799,455 on 2026-11-06. A per-transaction cap of 2^24 is not enforced on Sepolia (a 92,943,162-gas transaction was included in the last 20 blocks and the node accepted a 20M-gas raw transaction up to the balance check), so deployment is possible, but the policy's gasCeilingWei must cover about 33M gas plus factory overhead.","line":123,"path":"src/SqueekPredict.sol","reproduction":"eth_estimateGas {from: 0xdEaD, data: <out/SqueekPredict.sol/SqueekPredict.json bytecode.object + abi.encode(metrics, 0x694AA1769357215DE4FAC081bf1f309aDC325306, 0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43)>} on https://ethereum-sepolia-rpc.publicnode.com -> 0x191fccd (26,344,653). Same for SqueekMetrics with the Core address -> 0x67c05f (6,799,455). Expected under the Cancun schedule the tests run on: about 4.2M and 1.0M.","severity":"info","snippet":"    constructor(address metrics_, address ethFeed_, address btcFeed_) {","title":"Under live Sepolia's repriced gas schedule the factory transaction needs about 33M gas for the two contracts"}],"hash":"c4bfe64cd964788b2a69e0ff42842bec8da4663eadea18715dbd58e7a7bb9166","nodeId":"d169d955-ad20-46bb-ad62-cb6ad8837f69","outcome":"completed","summary":"The review is complete. `.imd-findings.json` holds 12 findings and 13 coverage rows, no tracked file was changed, and every snippet matches its file line.\n\n**What I verified**\n\n- All three supplied proofs fail on this tree for the stated reasons (return bomb at 12,651,148 gas; late bet profit of 8 ETH; optimal late bet profit of 0.80 ETH).\n- The existing Foundry suite passes (89 tests). Contract runtime sizes are 13,753 and 4,227 bytes, within limits.\n- Live Sepolia reads confirmed the specialists' external claims: Core frontier at round 1973 (10.8 days stale), base fee 16 to 25 wei, pruned history error 4444 on the default RPC, and a repriced gas schedule (about 111k gas per fresh storage slot, confirmed on two RPCs).\n- I corrected one lead: the 2^24 per-transaction cap is not enforced on Sepolia (a 92.9M-gas transaction was included), so deployment is possible. I recorded the ~33M gas deployment cost as info for the gas ceiling.\n\n**Findings kept, after merging 19 specialist reports into 12**\n\n1. **High.** Automatic payout pushes copy the recipient's revert data, so 20 hostile bettors push settlement past the keeper's budget and a loser can force a refund. Proof attached.\n2. **Medium.** Cumulative Squeek lanes accept stakes after Core has certified the outcome (three specialists merged). Proof attached.\n3. **Medium.** Betting closes at half-time but replacements only appear at expiry, so bettable markets fall to 5 then 3 with healthy sources. My own self-contained proof attached.\n4. **Medium.** The base-fee card prints the wei strike as \"0 gwei\".\n5. **Medium.** Native lanes have only a 256-block proof window, so a one-hour outage refunds instead of paying winners.\n6. **Medium.** The live Core is stale, so seven lanes cannot open at launch. Needs a requester scope decision.\n7. **Low.** checkUpkeep reports work forever while performUpkeep is a no-op (three specialists merged, reproduced at 161k gas per call).\n8. **Low.** Keeper gas budget and fixed pay batch do not fit the live gas schedule.\n9. **Low.** configure-site.mjs fails on pruned history after about 33 hours.\n10. **Low.** Lanes 1 and 2 strikes are foregone conclusions (daily blocks 775 to 1148 vs 400 on all 13 certified days). Lane 3 and 10 figures were not re-measured and are left as leads.\n11. **Info.** Core genesis is not re-checked after open (trust assumption).\n12. **Info.** Deployment gas under the live schedule.\n\n**Coverage**: all 11 entry points answered, 6 holds and 5 pointing at findings, plus two invariant rows.","treeHash":null,"usage":{"cachedInputTokens":1265453,"cost":{"source":"runtime","usdMicros":5552762},"inputTokens":386,"model":"claude-fable-5-1","outputTokens":49901,"runtime":"claude","turns":27,"wallClockMs":947600}},{"artifacts":[],"attempt":1,"bundleHash":"f87cc7239a22ee7c4e258c5724d5a3e3a98cc71892c20e0b270b0ec725a7b1df","device":"63458a1bd2b2d376","findings":[],"hash":"f05a174cee06025e3911caf5d07f63fb667393f2fa3b33a13eb7e8f7a62f5749","nodeId":"8a770fd7-40b4-4040-ade1-baa781778585","outcome":"completed","summary":"All checks pass. Here is the recap.\n\n**Outcome.** All three reviewer proofs failed on the starting tree and pass now. The full Foundry suite (52 tests, including the three proofs copied under test/scratch) passes with the verifier's fuzz seed, `forge fmt --check` is clean, and the 21 JavaScript tests pass. Answers to all twelve findings are in `.imd-responses.json` (eleven fixed, one disputed).\n\n**Contract fixes in `src/`**\n- **Return-data bomb (high):** automatic pushes now use an assembly `call` that copies no return data. Twenty reverting bombs cost settlement about 1.3M gas instead of 12.7M.\n- **Late Squeek stakes (medium):** all Squeek lanes close betting at `startsAt`, and the window starts one full duration after opening, so no stake can land after Core certifies part of the outcome.\n- **Bettable count (medium):** each lane now has a bettable generation and an observing slot (`observingMarketIds()`). At `closesAt` the market moves to observing and its successor opens at once; fourteen markets stay bettable with healthy sources.\n- **Native proofs (medium):** header hashes fall back to the EIP-2935 history contract beyond 256 blocks. Native lanes use a one-day grace so the entire grace period is provable.\n- **Advisory items:** `checkUpkeep` only reports work `performUpkeep` can do, via a new `previewOpen` view. Squeek strikes were re-based on live Core data I measured today (950 blocks/day, 6,700/week, 11,000 work/day, 2-hour streak). Settlement reverts if the Core's genesis has moved.\n\n**Off-chain fixes.** Base-fee strikes display in exact wei. The keeper retries payment batches at 20, 10, 5 without charging the cycle budget, and triggers upkeep on close and observing-end. The configure-site script no longer needs pruned historical state. README, SECURITY.md, OPERATIONS.md and the site text document the lifecycle, grace periods, strikes and the ~33M launch gas figure.\n\n**Disputed.** The stale-Core finding (seven lanes cannot open while the specified Core is 10.8 days behind) is real but intentional fail-closed behaviour. Meeting twelve bettable markets during that outage needs a requester scope decision on extra public-data lanes, which the reviewer also noted. The README states this plainly.\n\n**Not done.** I did not commit; the harness committed the previous round and nothing in the brief asked me to.","treeHash":"605a57af303fe4d34ae41eb9cc03cd89c4184906","usage":{"cachedInputTokens":2652114,"cost":{"source":"runtime","usdMicros":8513978},"inputTokens":578,"model":"claude-fable-5-1","outputTokens":79246,"runtime":"claude","turns":99,"wallClockMs":1187300}}],"verification":[{"checks":[{"durationMs":42540,"exitCode":0,"name":"build","output":"Compiling 39 files with Solc 0.8.26\nSolc 0.8.26 finished in 42.42s\nCompiler run successful with warnings:\nWarning (5159): \"selfdestruct\" has been deprecated. Note that, starting from the Cancun hard fork, the underlying opcode no longer deletes the code and data associated with an account and only transfers its Ether to the beneficiary, unless executed in the same transaction in which the contract was created (see EIP-6780). Any use in newly deployed contracts is strongly discouraged even if the new behavior is taken into account. Future changes to the EVM might further reduce the functionality of the opcode.\n  --> test/helpers/IndependentSetup.sol:39:9:\n   |\n39 |         selfdestruct(recipient);\n   |         ^^^^^^^^^^^^\n\n","passed":true},{"durationMs":46025,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/ResolutionMatrix.t.sol:ResolutionMatrixTest\n[PASS] testAllMarketDurationsAndOutcomeKindsAreFixed() (gas: 543993)\n[PASS] testFuzzAllNativeResolutionRoutesAtStrikeAndOneUnitOver(uint8,bool) (runs: 256, μ: 1421467, ~: 1421378)\n[PASS] testFuzzAllSqueekResolutionRoutesAtStrikeAndOneUnitOver(uint8,bool) (runs: 256, μ: 1812497, ~: 1685305)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 852.26ms (1.33s CPU time)\n\nRan 25 tests for test/SqueekPredict.t.sol:SqueekPredictTest\n[PASS] testApplicationRuntimeSizeAndNoEscapeOpcodes() (gas: 11106228)\n[PASS] testCannotChooseLaterPriceOrSkipDeadlineRound() (gas: 1045962)\n[PASS] testConstructorNoCallsNoOwnerAndFourteenLanes() (gas: 60324)\n[PASS] testEarlySettlementCannotResolveAndTimeoutIsFixed() (gas: 677580)\n[PASS] testEmptySettlementAndSingleSidedWinner() (gas: 1474820)\n[PASS] testExactExpiryFeedTimestampWinsOverPrevious() (gas: 861479)\n[PASS] testFeeOddsAndRepeatedPositionIndex() (gas: 641662)\n[PASS] testForgedHeaderLaterSampleAndExpiredHashAreRejected() (gas: 672871)\n[PASS] testFuzzConservationProportionalPayoutAndRounding(uint96,uint96,uint96,bool) (runs: 256, μ: 1416750, ~: 1389181)\nLogs:\n  Bound result 99999000000000000006\n  Bound result 15457143729341384\n  Bound result 1044497060318734075\n\n[PASS] testInvalidBatchRecipientAndUnknownMarket() (gas: 204703)\n[PASS] testInvalidBetsAndExactClosingBoundary() (gas: 603216)\n[PASS] testInvalidFeedAnswerTimestampDecimalsAndAnsweredRound() (gas: 608533)\n[PASS] testMaintainRollsFixedSlotsEvenWithBrokenOracles() (gas: 3069240)\n[PASS] testManyWinnersPaidInBoundedBatchesExactlyOnce() (gas: 10482314)\n[PASS] testMissingSqueekCertificationThenRecoveryOrRefund() (gas: 898088)\n[PASS] testNativeGasAndBlockMarketsUseAuthenticatedBoundary() (gas: 1058237)\n[PASS] testNoWinningBetsRefundNetNotFee() (gas: 1167986)\n[PASS] testPausedAndStaleCoreRejectNewStakesButPreserveRefunds() (gas: 729858)\n[PASS] testProportionalAutomaticPayoutAndReplacement() (gas: 1531597)\n[PASS] testReentrancyDuringPushAndClaimIsRejected() (gas: 1511422)\n[PASS] testRejectedPushCannotBlockOthersOrStealClaims() (gas: 1979282)\n[PASS] testSqueekSettlementReadsExactCertifiedWindowAndPays() (gas: 1125462)\n[PASS] testStaleBoundaryPhaseChangeAndMissingRoundFailClosed() (gas: 962650)\n[PASS] testStaleOpeningPriceCannotTakeFundsAndOtherLanesWork() (gas: 1173235)\n[PASS] testTieResolvesUnderAndBothSidesPerWallet() (gas: 1245797)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 924.25ms (414.76ms CPU time)\n\nRan 10 tests for test/MetricsProperties.t.sol:MetricsPropertiesTest\n[PASS] testFuzzEndpointMetricsAreAdditiveAcrossAdjacentWindows(uint96,uint96,uint96) (runs: 256, μ: 558462, ~: 558321)\nLogs:\n  Bound result 9482\n  Bound result 7223\n  Bound result 1269\n\n[PASS] testFuzzMalformedActivityPageNeverProducesAnOutcome(uint8) (runs: 256, μ: 255439, ~: 252886)\n[PASS] testFuzzSparsePaginationAgainstDenseDailyModel(uint256,uint16,uint8) (runs: 256, μ: 9509625, ~: 4407401)\nLogs:\n  Bound result 3\n  Bound result 1\n\n[PASS] testFuzzWindowAlignmentWithNonUtcGenesis(uint16,uint32,uint16,bool) (runs: 256, μ: 930482, ~: 931261)\nLogs:\n  Bound result 201\n  Bound result 4079905\n  Bound result 10\n\n[PASS] testHistoricalResultCannotBeChangedByLaterRounds() (gas: 1087852)\n[PASS] testInvalidEndpointCertificationIsRejected() (gas: 44388)\n[PASS] testInvalidWindowDurationsAndEndpoints() (gas: 266783)\n[PASS] testLivenessAcceptsExactSixHoursAndRejectsFutureCertification() (gas: 95002)\n[PASS] testPartialPagesMustReachEndWithinBoundedNumberOfCalls() (gas: 88561)\n[PASS] testUnsupportedRoundDurationOrBlockUnitScalingFailsClosed() (gas: 52579)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 924.30ms (1.36s CPU time)\n\nRan 11 tests for test/SqueekMetrics.t.sol:SqueekMetricsTest\n[PASS] testBlocksSubtractEndpointFloorsNotFloorOfDifference() (gas: 269890)\n[PASS] testConstructorDoesNotCallDependency() (gas: 12958)\n[PASS] testEmptyCertifiedDayIsZero() (gas: 103455)\n[PASS] testFullDay144RecordsAndSevenDayEndpoint() (gas: 56454816)\n[PASS] testFuzzEndpointFloor(uint96,uint96) (runs: 256, μ: 228981, ~: 228865)\nLogs:\n  Bound result 59092427865039329000535\n  Bound result 5770745034821590155865587\n\n[PASS] testInvalidWindowsAndNonAdvancingPageRevert() (gas: 117393)\n[PASS] testMorningAndEveningUseUtcRoundStartAndTieIsNo() (gas: 435258)\n[PASS] testMorningWins() (gas: 241481)\n[PASS] testPaginatedRunSpansPagesAndZeroRoundBreaksStreak() (gas: 8550635)\n[PASS] testUncertifiedRoundsAreUnknownNotZero() (gas: 53573)\n[PASS] testWindowsUseFutureFullRoundsAndUtcDay() (gas: 43505)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 941.70ms (441.96ms CPU time)\n\nRan 4 tests for test/BlockHeader.t.sol:BlockHeaderTest\n[PASS] testBoundaryAt256BlockLimit() (gas: 93889)\n[PASS] testFuzzDecodeBoundedHeader(uint64,uint64,uint64,bytes32) (runs: 256, μ: 42469, ~: 42659)\n[PASS] testRealSepoliaAmsterdamBoundaryIsDecodedAndAuthenticated() (gas: 68864)\n[PASS] testRejectMalformedRlp() (gas: 90237)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 941.68ms (30.54ms CPU time)\n\nRan 15 tests for test/ProtocolEdges.t.sol:ProtocolEdgesTest\n[PASS] testAggregatePoolCapAcrossRepeatedCallsAndBothSides() (gas: 771342513)\n[PASS] testBatchReadsHandleEmptyMaximumDuplicateAndUnknownIds() (gas: 5165933)\n[PASS] testBoundaryRoundIdentifiersCannotBeForged() (gas: 850013)\n[PASS] testExactTwoHourBoundaryAgesAreAcceptedButOneSecondOlderIsNot() (gas: 755223)\n[PASS] testFuzzAllPriceLanesUseTheirOwnFeedAndStrictStrike(uint8,bool) (runs: 1000, μ: 1622206, ~: 1622138)\n[PASS] testFuzzEveryInvalidBoundaryRoundPreservesFundedMarket(uint8,bool) (runs: 1000, μ: 1368578, ~: 1368298)\n[PASS] testFuzzFeeRoundsDownAndOddsMoveTowardNewStake(uint96,bool) (runs: 1000, μ: 1165811, ~: 1165839)\nLogs:\n  Bound result 1000000000000003\n\n[PASS] testFuzzUnknownIdsCannotReadMutateOrClaim(uint256) (runs: 1000, μ: 959705, ~: 959088)\nLogs:\n  Bound result 1218\n\n[PASS] testMinimumMaximumAndOneWeiOutsideBetBounds() (gas: 1216945)\n[PASS] testMorningBetsCloseAtStartAndOtherActivityBetsUseHalfWindow() (gas: 973070)\n[PASS] testNativeProofCannotCountBlocksBeforeOpening() (gas: 254707)\n[PASS] testNewLaneOpensOnceAndOldAwaitingMarketSettlesWithoutSecondReplacement() (gas: 2320306)\n[PASS] testPositionPaginationIsNewestFirstUniqueAndBounded() (gas: 1425062)\n[PASS] testSqueekHistoricalSettlementWorksWhileCorePaused() (gas: 1147183)\n[PASS] testWrongSettlementRouteRollsBackReplacementAndBalances() (gas: 1369002)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 941.78ms (4.40s CPU time)\n\nRan 10 tests for test/PayoutProperties.t.sol:PayoutPropertiesTest\n[PASS] testAllRejectingWinnersRetainDustUntilLastClaim() (gas: 2605295)\n[PASS] testClaimBeforeItsBatchTurnIsSkippedWithoutPayingTwice() (gas: 6243121)\n[PASS] testClaimSurvivesManyGenerationsAndHasNoExpiry() (gas: 5731992)\n[PASS] testForcedEtherDoesNotChangeOddsEntitlementsOrFees() (gas: 2110433)\n[PASS] testFuzzClaimCannotReenterAnyGuardedEntryPoint(uint8) (runs: 1000, μ: 2382476, ~: 2396334)\n[PASS] testFuzzPayoutBoundsAndOrderIndependence(uint96,uint96,uint96,bool) (runs: 1000, μ: 2638929, ~: 2639082)\nLogs:\n  Bound result 99999000000000001949\n  Bound result 99999000000000001943\n  Bound result 99999000000000007470\n\n[PASS] testFuzzSameWalletOnBothSidesCannotProfitFromRefund(uint96,uint96) (runs: 1000, μ: 1156276, ~: 1156382)\nLogs:\n  Bound result 99999000000000011844\n  Bound result 99999000000000001016\n\n[PASS] testGasExhaustingRecipientCannotFreezeFollowingBettors() (gas: 2012043)\n[PASS] testOtherMarketsCannotConsumePendingWinnerLiability() (gas: 2443677)\n[PASS] testRejectedClaimRollsBackEveryDebitThenCanRetry() (gas: 2193383)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 941.70ms (2.57s CPU time)\n\nRan 9 tests for test/HeaderProperties.t.sol:HeaderPropertiesTest\n[PASS] testAuthenticatedButNonBoundaryHeadersAreRejected() (gas: 282125)\n[PASS] testCurrentAndFutureBlocksCannotBeProven() (gas: 79064)\n[PASS] testEveryNumericFieldRejectsNonCanonicalAndOversizedIntegers() (gas: 572219)\n[PASS] testFuzzBoundaryRequiresExactParentAndTargetHashes(bytes32) (runs: 1000, μ: 162625, ~: 162625)\n[PASS] testFuzzFullWidthHeaderValuesRoundTrip(bytes32,uint256,uint256,uint256) (runs: 1000, μ: 56714, ~: 57494)\n[PASS] testFuzzTruncatedHeaderCannotDecode(uint256) (runs: 1000, μ: 37636, ~: 37763)\nLogs:\n  Bound result 17\n\n[PASS] testMissingBaseFeeTooManyFieldsAndNestedUnusedFieldsAreRejected() (gas: 109645)\n[PASS] testParentHashMustBeExactly32Bytes() (gas: 41941)\n[PASS] testZeroAndMaximumIntegersHaveDistinctValidEncodings() (gas: 121798)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 941.87ms (1.94s CPU time)\n\nRan 1 test for test/AccountingInvariant.t.sol:AccountingInvariantTest\n[PASS]\nAccountingInvariantTest invariants:\n[PASS] invariantBalanceEqualsLiabilitiesAndLockedReserves\n[PASS] invariantExactlyFourteenUniqueCurrentLanes\n[PASS] invariantTotalEtherIsConservedAcrossEveryActor\n AccountingInvariantTest invariants (runs: 64, calls: 4096, reverts: 0)\n\n╭---------------+----------+-------+---------+----------╮\n| Contract      | Selector | Calls | Reverts | Discards |\n+=======================================================+\n| MarketHandler | bet      | 1001  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| MarketHandler | elapse   | 1055  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| MarketHandler | pay      | 1033  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| MarketHandler | resolve  | 1007  | 0       | 0        |\n╰---------------+----------+-------+---------+----------╯\n\nLogs:\n  Bound result 18\n  Bound result 999000000000010225\n  Bound result 999000000000001368\n  Bound result 12362\n  Bound result 999000000000007742\n  Bound result 242136897799168488\n  Bound result 22\n  Bound result 259200\n  Bound result 5956\n  Bound result 999000002074284042\n  Bound result 999000000000004901\n  Bound result 54\n  Bound result 1189\n  Bound result 999000000000002832\n  Bound result 999000000000003693\n  Bound result 8874\n  Bound result 32\n  Bound result 8201\n  Bound result 14\n  Bound result 474724182344070025\n  Bound result 7523\n  Bound result 5847\n  Bound result 999000000000013222\n  Bound result 999000000000000362\n  Bound result 50000\n  Bound result 25\n  Bound result 999000003749755649\n  Bound result 6304\n  Bound result 4485\n  Bound result 999\n  Bound result 44880\n  Bound result 2673\n  Bound result 450\n  Bound result 23294\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 5.83s (5.83s CPU time)\n\nRan 1 test for test/StatefulSolvency.t.sol:StatefulSolvencyTest\n[PASS]\nStatefulSolvencyTest invariants:\n[PASS] invariantCurrentSlotsRemainUniqueAndKeepTheirTemplates\n[PASS] invariantIndependentLedgerAndClosedSystemConserveEther\n[PASS] invariantPayoutRightsAndTerminalMarketsCannotChange\n StatefulSolvencyTest invariants (runs: 256, calls: 24576, reverts: 0)\n\n╭-----------------+-----------------+-------+---------+----------╮\n| Contract        | Selector        | Calls | Reverts | Discards |\n+================================================================+\n| SolvencyHandler | advance         | 2706  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| SolvencyHandler | bet             | 2843  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| SolvencyHandler | claim           | 2755  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| SolvencyHandler | donate          | 2704  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| SolvencyHandler | finish          | 2704  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| SolvencyHandler | invalidBet      | 2822  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| SolvencyHandler | maintain        | 2678  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| SolvencyHandler | pay             | 2619  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| SolvencyHandler | toggleRecipient | 2745  | 0       | 0        |\n╰-----------------+-----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 10000000000000000\n  Bound result 20000000000000000\n  Bound result 30000000000000000\n  Bound result 40000000000000000\n  Bound result 50000000000000000\n  Bound result 60000000000000000\n  Bound result 100000000000000000\n  Bound result 10000000000000000\n  Bound result 20000000000000000\n  Bound result 1000000000000001\n  Bound result 1000001\n  Bound result 57540466775738563628\n  Bound result 20\n  Bound result 65382430433206047852\n  Bound result 25\n  Bound result 99999000000000003448\n  Bound result 28\n  Bound result 99999000000000001733\n  Bound result 199\n  Bound result 9999\n  Bound result 4006\n  Bound result 2751\n  Bound result 357\n  Bound result 20000000000000000\n  Bound result 11826\n  Bound result 11713\n  Bound result 1008\n  Bound result 99999000000000003918\n  Bound result 144000\n  Bound result 520104745063045019\n  Bound result 999\n  Bound result 23\n  Bound result 12142\n  Bound result 99077088488950575787\n  Bound result 9\n  Bound result 83437822266892281\n  Bound result 40\n  Bound result 100\n  Bound result 99999000000000009999\n  Bound result 38\n  Bound result 6426\n  Bound result 9998\n  Bound result 99999000000000003601\n  Bound result 65618\n  Bound result 21\n  Bound result 115200\n  Bound result 1000000000000000000\n  Bound result 31\n  Bound result 17827\n  Bound result 1000000000000000000\n  Bound result 230000000000\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 45.84s (45.83s CPU time)\n\nRan 10 test suites in 45.84s (59.08s CPU time): 89 tests passed, 0 failed, 0 skipped (89 total tests)\n","passed":true},{"durationMs":95,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SqueekPredict.activate(uint256)\",\"SqueekPredict.bet(uint256,uint8)\",\"SqueekPredict.claim(uint256,address)\",\"SqueekPredict.expire(uint256)\",\"SqueekPredict.maintain()\",\"SqueekPredict.open(uint256)\",\"SqueekPredict.pay(uint256,uint256)\",\"SqueekPredict.performUpkeep(bytes)\",\"SqueekPredict.settleNative(uint256,bytes,bytes)\",\"SqueekPredict.settlePrice(uint256,uint80)\",\"SqueekPredict.settleSqueek(uint256)\"],\"files\":{\".gitignore\":5,\"LICENSE\":21,\"README.md\":137,\"SECURITY.md\":43,\"THIRD_PARTY.md\":15,\"docs/OPERATIONS.md\":42,\"docs/SOURCES.md\":33,\"docs/VALIDATION.md\":38,\"foundry.toml\":22,\"index.html\":2,\"package.json\":12,\"remappings.txt\":1,\"scripts/configure-site.mjs\":39,\"scripts/export-abi.mjs\":8,\"scripts/keeper-lib.mjs\":71,\"scripts/keeper.mjs\":76,\"scripts/runtime.mjs\":16,\"scripts/serve.mjs\":22,\"src/SqueekMetrics.sol\":136,\"src/SqueekPredict.sol\":517,\"src/interfaces/IAggregatorV3.sol\":8,\"src/interfaces/ISqueekCore.sol\":58,\"src/libraries/BlockHeader.sol\":91,\"test/AccountingInvariant.t.sol\":124,\"test/BlockHeader.t.sol\":67,\"test/HeaderProperties.t.sol\":174,\"test/INDEPENDENT_TESTS.md\":87,\"test/MetricsProperties.t.sol\":208,\"test/PayoutProperties.t.sol\":254,\"test/ProtocolEdges.t.sol\":274,\"test/ResolutionMatrix.t.sol\":112,\"test/SqueekMetrics.t.sol\":111,\"test/SqueekPredict.t.sol\":541,\"test/StatefulSolvency.t.sol\":448,\"test/fixtures/SepoliaHeaders.sol\":18,\"test/fixtures/sepolia-headers.json\":56,\"test/helpers/Headers.sol\":34,\"test/helpers/IndependentSetup.sol\":108,\"test/keeper.test.mjs\":34,\"test/mocks/MockCore.sol\":104,\"test/mocks/MockFeed.sol\":50,\"test/proofs.test.mjs\":61,\"test/runtime.test.mjs\":11,\"test/web.test.mjs\":41,\"web/abi/SqueekMetrics.json\":187,\"web/abi/SqueekPredict.json\":1192,\"web/app.js\":390,\"web/assets/Inter-LICENSE.txt\":92,\"web/assets/InterVariable.woff2\":1389,\"web/assets/mark.svg\":1,\"web/config.json\":8,\"web/index.html\":57,\"web/lib/icons.js\":17,\"web/lib/markets.js\":92,\"web/lib/proofs.js\":96,\"web/styles.css\":12,\"web/vendor/ethers-6.15.0.min.js\":1,\"web/vendor/ethers-LICENSE.md\":21},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"0b0879f6d9158444c902ea990ea8230f51fa502177e4c5d67bd7ca2f4cf8576f","verifiedTreeHash":"73d9c7baeb1477f69e1859dfc00274b53e5dafaf","verifierVersion":"0.1.0+18543036"},{"checks":[{"durationMs":13412,"exitCode":0,"name":"build","output":"Compiling 32 files with Solc 0.8.26\nSolc 0.8.26 finished in 13.33s\nCompiler run successful!\n","passed":true},{"durationMs":4175,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 11 tests for test/SqueekMetrics.t.sol:SqueekMetricsTest\n[PASS] testBlocksSubtractEndpointFloorsNotFloorOfDifference() (gas: 269890)\n[PASS] testConstructorDoesNotCallDependency() (gas: 12958)\n[PASS] testEmptyCertifiedDayIsZero() (gas: 103455)\n[PASS] testFullDay144RecordsAndSevenDayEndpoint() (gas: 56454816)\n[PASS] testFuzzEndpointFloor(uint96,uint96) (runs: 256, μ: 228981, ~: 228865)\nLogs:\n  Bound result 4069379762\n  Bound result 638212885\n\n[PASS] testInvalidWindowsAndNonAdvancingPageRevert() (gas: 117393)\n[PASS] testMorningAndEveningUseUtcRoundStartAndTieIsNo() (gas: 435258)\n[PASS] testMorningWins() (gas: 241481)\n[PASS] testPaginatedRunSpansPagesAndZeroRoundBreaksStreak() (gas: 8550635)\n[PASS] testUncertifiedRoundsAreUnknownNotZero() (gas: 53573)\n[PASS] testWindowsUseFutureFullRoundsAndUtcDay() (gas: 43505)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 57.63ms (52.45ms CPU time)\n\nRan 4 tests for test/BlockHeader.t.sol:BlockHeaderTest\n[PASS] testBoundaryAt256BlockLimit() (gas: 93889)\n[PASS] testFuzzDecodeBoundedHeader(uint64,uint64,uint64,bytes32) (runs: 256, μ: 42443, ~: 42659)\n[PASS] testRealSepoliaAmsterdamBoundaryIsDecodedAndAuthenticated() (gas: 68864)\n[PASS] testRejectMalformedRlp() (gas: 90237)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 60.96ms (14.76ms CPU time)\n\nRan 25 tests for test/SqueekPredict.t.sol:SqueekPredictTest\n[PASS] testApplicationRuntimeSizeAndNoEscapeOpcodes() (gas: 11106228)\n[PASS] testCannotChooseLaterPriceOrSkipDeadlineRound() (gas: 1045962)\n[PASS] testConstructorNoCallsNoOwnerAndFourteenLanes() (gas: 60324)\n[PASS] testEarlySettlementCannotResolveAndTimeoutIsFixed() (gas: 677580)\n[PASS] testEmptySettlementAndSingleSidedWinner() (gas: 1474820)\n[PASS] testExactExpiryFeedTimestampWinsOverPrevious() (gas: 861479)\n[PASS] testFeeOddsAndRepeatedPositionIndex() (gas: 641662)\n[PASS] testForgedHeaderLaterSampleAndExpiredHashAreRejected() (gas: 672871)\n[PASS] testFuzzConservationProportionalPayoutAndRounding(uint96,uint96,uint96,bool) (runs: 256, μ: 1416750, ~: 1389181)\nLogs:\n  Bound result 99999000000000017931\n  Bound result 99999000000000004671\n  Bound result 99999000000000015767\n\n[PASS] testInvalidBatchRecipientAndUnknownMarket() (gas: 204703)\n[PASS] testInvalidBetsAndExactClosingBoundary() (gas: 603216)\n[PASS] testInvalidFeedAnswerTimestampDecimalsAndAnsweredRound() (gas: 608533)\n[PASS] testMaintainRollsFixedSlotsEvenWithBrokenOracles() (gas: 3069240)\n[PASS] testManyWinnersPaidInBoundedBatchesExactlyOnce() (gas: 10482314)\n[PASS] testMissingSqueekCertificationThenRecoveryOrRefund() (gas: 898088)\n[PASS] testNativeGasAndBlockMarketsUseAuthenticatedBoundary() (gas: 1058237)\n[PASS] testNoWinningBetsRefundNetNotFee() (gas: 1167986)\n[PASS] testPausedAndStaleCoreRejectNewStakesButPreserveRefunds() (gas: 729858)\n[PASS] testProportionalAutomaticPayoutAndReplacement() (gas: 1531597)\n[PASS] testReentrancyDuringPushAndClaimIsRejected() (gas: 1511422)\n[PASS] testRejectedPushCannotBlockOthersOrStealClaims() (gas: 1979282)\n[PASS] testSqueekSettlementReadsExactCertifiedWindowAndPays() (gas: 1125462)\n[PASS] testStaleBoundaryPhaseChangeAndMissingRoundFailClosed() (gas: 962650)\n[PASS] testStaleOpeningPriceCannotTakeFundsAndOtherLanesWork() (gas: 1173235)\n[PASS] testTieResolvesUnderAndBothSidesPerWallet() (gas: 1245797)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 100.90ms (206.27ms CPU time)\n\nRan 1 test for test/AccountingInvariant.t.sol:AccountingInvariantTest\n[PASS]\nAccountingInvariantTest invariants:\n[PASS] invariantBalanceEqualsLiabilitiesAndLockedReserves\n[PASS] invariantExactlyFourteenUniqueCurrentLanes\n[PASS] invariantTotalEtherIsConservedAcrossEveryActor\n AccountingInvariantTest invariants (runs: 64, calls: 4096, reverts: 0)\n\n╭---------------+----------+-------+---------+----------╮\n| Contract      | Selector | Calls | Reverts | Discards |\n+=======================================================+\n| MarketHandler | bet      | 1001  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| MarketHandler | elapse   | 1055  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| MarketHandler | pay      | 1033  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| MarketHandler | resolve  | 1007  | 0       | 0        |\n╰---------------+----------+-------+---------+----------╯\n\nLogs:\n  Bound result 259200\n  Bound result 999000000000011405\n  Bound result 999000000000001455\n  Bound result 13191\n  Bound result 999000000000008609\n  Bound result 242136897799168488\n  Bound result 37\n  Bound result 36\n  Bound result 5870\n  Bound result 999000002074284042\n  Bound result 999000000000007997\n  Bound result 54\n  Bound result 1189\n  Bound result 999000000000002786\n  Bound result 999999999998999000\n  Bound result 8874\n  Bound result 43200\n  Bound result 7201\n  Bound result 15\n  Bound result 999000000000002324\n  Bound result 7658\n  Bound result 23295\n  Bound result 557817741235783364\n  Bound result 999000000000000370\n  Bound result 144\n  Bound result 60\n  Bound result 999000000000005864\n  Bound result 6052\n  Bound result 5648\n  Bound result 101\n  Bound result 5414\n  Bound result 2544\n  Bound result 80\n  Bound result 23294\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.05s (4.05s CPU time)\n\nRan 4 test suites in 4.06s (4.27s CPU time): 41 tests passed, 0 failed, 0 skipped (41 total tests)\n","passed":true},{"durationMs":44,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SqueekPredict.activate(uint256)\",\"SqueekPredict.bet(uint256,uint8)\",\"SqueekPredict.claim(uint256,address)\",\"SqueekPredict.expire(uint256)\",\"SqueekPredict.maintain()\",\"SqueekPredict.open(uint256)\",\"SqueekPredict.pay(uint256,uint256)\",\"SqueekPredict.performUpkeep(bytes)\",\"SqueekPredict.settleNative(uint256,bytes,bytes)\",\"SqueekPredict.settlePrice(uint256,uint80)\",\"SqueekPredict.settleSqueek(uint256)\"],\"files\":{\".gitignore\":5,\"LICENSE\":21,\"README.md\":137,\"SECURITY.md\":43,\"THIRD_PARTY.md\":15,\"docs/OPERATIONS.md\":42,\"docs/SOURCES.md\":33,\"docs/VALIDATION.md\":38,\"foundry.toml\":22,\"index.html\":2,\"package.json\":12,\"remappings.txt\":1,\"scripts/configure-site.mjs\":39,\"scripts/export-abi.mjs\":8,\"scripts/keeper-lib.mjs\":71,\"scripts/keeper.mjs\":76,\"scripts/runtime.mjs\":16,\"scripts/serve.mjs\":22,\"src/SqueekMetrics.sol\":136,\"src/SqueekPredict.sol\":517,\"src/interfaces/IAggregatorV3.sol\":8,\"src/interfaces/ISqueekCore.sol\":58,\"src/libraries/BlockHeader.sol\":91,\"test/AccountingInvariant.t.sol\":124,\"test/BlockHeader.t.sol\":67,\"test/SqueekMetrics.t.sol\":111,\"test/SqueekPredict.t.sol\":541,\"test/fixtures/SepoliaHeaders.sol\":18,\"test/fixtures/sepolia-headers.json\":56,\"test/helpers/Headers.sol\":34,\"test/keeper.test.mjs\":34,\"test/mocks/MockCore.sol\":104,\"test/mocks/MockFeed.sol\":50,\"test/proofs.test.mjs\":61,\"test/runtime.test.mjs\":11,\"test/web.test.mjs\":41,\"web/abi/SqueekMetrics.json\":187,\"web/abi/SqueekPredict.json\":1192,\"web/app.js\":390,\"web/assets/Inter-LICENSE.txt\":92,\"web/assets/InterVariable.woff2\":1389,\"web/assets/mark.svg\":1,\"web/config.json\":8,\"web/index.html\":57,\"web/lib/icons.js\":17,\"web/lib/markets.js\":92,\"web/lib/proofs.js\":96,\"web/styles.css\":12,\"web/vendor/ethers-6.15.0.min.js\":1,\"web/vendor/ethers-LICENSE.md\":21},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":2548,"exitCode":0,"name":"slither","output":"[high/medium] reentrancy-eth at src/SqueekPredict.sol:335: Reentrancy in SqueekPredict.claim(uint256,address) (src/SqueekPredict.sol#335-344):\n[high/medium] reentrancy-eth at src/SqueekPredict.sol:454: Reentrancy in SqueekPredict._payBatch(uint256,uint256) (src/SqueekPredict.sol#454-477):\n[medium/medium] divide-before-multiply at src/SqueekMetrics.sol:35: SqueekMetrics.window(uint64,uint32,bool) (src/SqueekMetrics.sol#35-48) performs a multiplication on the result of a division:\n[medium/medium] reentrancy-no-eth at src/SqueekPredict.sol:397: Reentrancy in SqueekPredict._maintain() (src/SqueekPredict.sol#397-404):\n[medium/medium] uninitialized-local at src/SqueekPredict.sol:273: SqueekPredict.settleSqueek(uint256).metric (src/SqueekPredict.sol#273) is a local variable never initialized\n[medium/medium] uninitialized-local at src/SqueekMetrics.sol:93: SqueekMetrics.measure(SqueekMetrics.Metric,uint64,uint64).morning (src/SqueekMetrics.sol#93) is a local variable never initialized\n[medium/medium] uninitialized-local at src/SqueekMetrics.sol:94: SqueekMetrics.measure(SqueekMetrics.Metric,uint64,uint64).evening (src/SqueekMetrics.sol#94) is a local variable never initialized\n[medium/medium] uninitialized-local at src/SqueekMetrics.sol:92: SqueekMetrics.measure(SqueekMetrics.Metric,uint64,uint64).longest (src/SqueekMetrics.sol#92) is a local variable never initialized\n[medium/medium] uninitialized-local at src/SqueekMetrics.sol:95: SqueekMetrics.measure(SqueekMetrics.Metric,uint64,uint64).active (src/SqueekMetrics.sol#95) is a local variable never initialized\n[medium/medium] uninitialized-local at src/SqueekMetrics.sol:91: SqueekMetrics.measure(SqueekMetrics.Metric,uint64,uint64).streak (src/SqueekMetrics.sol#91) is a local variable never initialized\n[medium/medium] uninitialized-local at src/SqueekMetrics.sol:89: SqueekMetrics.measure(SqueekMetrics.Metric,uint64,uint64).previousRound (src/SqueekMetrics.sol#89) is a local variable never initialized\n[low/medium] calls-loop at src/SqueekPredict.sol:454: SqueekPredict._payBatch(uint256,uint256) (src/SqueekPredict.sol#454-477) has external calls inside a loop: (success,None) = address(user).call{gas: PUSH_GAS,value: amount}() (src/SqueekPredict.sol#465)\n[low/medium] calls-loop at src/SqueekPredict.sol:454: SqueekPredict._payBatch(uint256,uint256) (src/SqueekPredict.sol#454-477) has external calls inside a loop: (success,None) = address(user).call{gas: PUSH_GAS,value: amount}() (src/SqueekPredict.sol#465)\n[low/medium] calls-loop at src/SqueekPredict.sol:390: SqueekPredict._tryOpen(uint256) (src/SqueekPredict.sol#390-395) has external calls inside a loop: this.activate{gas: 300_000}(id) (src/SqueekPredict.sol#391-394)\n[low/medium] calls-loop at src/SqueekPredict.sol:390: SqueekPredict._tryOpen(uint256) (src/SqueekPredict.sol#390-395) has external calls inside a loop: this.activate{gas: 300_000}(id) (src/SqueekPredict.sol#391-394)\n[low/medium] calls-loop at src/SqueekPredict.sol:454: SqueekPredict._payBatch(uint256,uint256) (src/SqueekPredict.sol#454-477) has external calls inside a loop: (success,None) = address(user).call{gas: PUSH_GAS,value: amount}() (src/SqueekPredict.sol#465)\n[low/medium] calls-loop at src/SqueekMetrics.sol:67: SqueekMetrics.measure(SqueekMetrics.Metric,uint64,uint64) (src/SqueekMetrics.sol#67-127) has external calls inside a loop: page = core.roundPage(cursor,throughRound,16) (src/SqueekMetrics.sol#98)\n[low/medium] calls-loop at src/SqueekPredict.sol:454: SqueekPredict._payBatch(uint256,uint256) (src/SqueekPredict.sol#454-477) has external calls inside a loop: (success,None) = address(user).call{gas: PUSH_GAS,value: amount}() (src/SqueekPredict.sol#465)\n[low/medium] calls-loop at src/SqueekPredict.sol:454: SqueekPredict._payBatch(uint256,uint256) (src/SqueekPredict.sol#454-477) has external calls inside a loop: (success,None) = address(user).call{gas: PUSH_GAS,value: amount}() (src/SqueekPredict.sol#465)\n[low/medium] reentrancy-benign at src/SqueekPredict.sol:335: Reentrancy in SqueekPredict.claim(uint256,address) (src/SqueekPredict.sol#335-344):\n[low/medium] timestamp at src/SqueekPredict.sol:497: SqueekPredict._validatePrice(uint80,int256,uint256,uint256,uint80) (src/SqueekPredict.sol#497-507) uses timestamp for comparisons\n[low/medium] timestamp at src/SqueekPredict.sol:228: SqueekPredict.checkUpkeep(bytes) (src/SqueekPredict.sol#228-236) uses timestamp for comparisons\n[low/medium] timestamp at src/SqueekMetrics.sol:56: SqueekMetrics._requireLive(uint64) (src/SqueekMetrics.sol#56-63) uses timestamp for comparisons\n[low/medium] timestamp at src/SqueekPredict.sol:238: SqueekPredict.bet(uint256,uint8) (src/SqueekPredict.sol#238-267) uses timestamp for comparisons\n[low/medium] timestamp at src/SqueekPredict.sol:360: SqueekPredict._open(uint256) (src/SqueekPredict.sol#360-388) uses timestamp for comparisons\n[low/medium] timestamp at src/SqueekPredict.sol:397: SqueekPredict._maintain() (src/SqueekPredict.sol#397-404) uses timestamp for comparisons\n[low/medium] timestamp at src/libraries/BlockHeader.sol:17: BlockHeader.boundary(bytes,bytes,uint256) (src/libraries/BlockHeader.sol#17-33) uses timestamp for comparisons\n[low/medium] timestamp at src/SqueekPredict.sol:414: SqueekPredict._beginResolution(uint256,bool) (src/SqueekPredict.sol#414-425) uses timestamp for comparisons","passed":true},{"durationMs":366,"exitCode":0,"name":"aderyn","output":"[high] eth-send-unchecked-address at src/SqueekPredict.sol:269: ETH transferred without address checks (5 places)\n[high] reentrancy-state-change at src/SqueekPredict.sol:246: Reentrancy: State change after external call\n[low] costly-loop at src/SqueekPredict.sol:398: Costly operations inside loop (2 places)\n[low] large-numeric-literal at src/SqueekMetrics.sol:26: Large Numeric Literal (4 places)\n[low] literal-instead-of-constant at src/SqueekMetrics.sol:42: Literal Instead of Constant (38 places)\n[low] require-revert-in-loop at src/SqueekMetrics.sol:97: Loop Contains `require`/`revert` (5 places)\n[low] unchecked-return at src/SqueekPredict.sol:131: Unchecked Return\n[low] uninitialized-local-variable at src/SqueekMetrics.sol:95: Uninitialized Local Variable (9 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"6098e7f446b6d99d715e5c2c1024444d3fd6dcd636d49ecf2c37cb093118a9a0","verifiedTreeHash":"73d6b99874c7891d2b78b35e0905df901201265a","verifierVersion":"0.1.0+18543036"},{"checks":[{"durationMs":18474,"exitCode":0,"name":"build","output":"Compiling 34 files with Solc 0.8.26\nSolc 0.8.26 finished in 18.39s\nCompiler run successful!\n","passed":true},{"durationMs":4584,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/BlockHeader.t.sol:BlockHeaderTest\n[PASS] testBoundaryAt256BlockLimit() (gas: 96949)\n[PASS] testFuzzDecodeBoundedHeader(uint64,uint64,uint64,bytes32) (runs: 256, μ: 42459, ~: 42659)\n[PASS] testRealSepoliaAmsterdamBoundaryIsDecodedAndAuthenticated() (gas: 69024)\n[PASS] testRejectMalformedRlp() (gas: 90237)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 16.55ms (16.59ms CPU time)\n\nRan 11 tests for test/SqueekMetrics.t.sol:SqueekMetricsTest\n[PASS] testBlocksSubtractEndpointFloorsNotFloorOfDifference() (gas: 270000)\n[PASS] testConstructorDoesNotCallDependency() (gas: 12980)\n[PASS] testEmptyCertifiedDayIsZero() (gas: 103697)\n[PASS] testFullDay144RecordsAndSevenDayEndpoint() (gas: 56455410)\n[PASS] testFuzzEndpointFloor(uint96,uint96) (runs: 256, μ: 229031, ~: 228915)\nLogs:\n  Bound result 6839801609718685\n  Bound result 303600870219707176148\n\n[PASS] testInvalidWindowsAndNonAdvancingPageRevert() (gas: 117547)\n[PASS] testMorningAndEveningUseUtcRoundStartAndTieIsNo() (gas: 435346)\n[PASS] testMorningWins() (gas: 241547)\n[PASS] testPaginatedRunSpansPagesAndZeroRoundBreaksStreak() (gas: 8550899)\n[PASS] testUncertifiedRoundsAreUnknownNotZero() (gas: 53595)\n[PASS] testWindowsLeaveOneDurationOfBettingAndUseFullRoundsAndUtcDay() (gas: 106726)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 67.88ms (50.27ms CPU time)\n\nRan 8 tests for test/SqueekPredictRotation.t.sol:SqueekPredictRotationTest\n[PASS] testCheckUpkeepIsFalseWhenNoLaneCanProgress() (gas: 1581050)\n[PASS] testClosedMarketObservesAndSettlesWithoutDisturbingItsSuccessor() (gas: 2575999)\n[PASS] testEveryLaneKeepsABettableMarketWhileThePreviousOneObserves() (gas: 84417827)\n[PASS] testMovedCoreGenesisFailsClosedToRefund() (gas: 1121002)\n[PASS] testNativeLanesHaveAOneDayGraceAndOthersTwoDays() (gas: 1001905)\n[PASS] testNativeProofsUseTheHistoryContractBeyond256BlocksWithinOneDay() (gas: 1716009)\n[PASS] testSettlementGasIsBoundedWhenPushedRecipientsRevertWithLargePayloads() (gas: 9475220)\n[PASS] testSqueekStakesAreRefusedOnceTheWindowCanBeCertified() (gas: 1085331)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 104.94ms (116.93ms CPU time)\n\nRan 25 tests for test/SqueekPredict.t.sol:SqueekPredictTest\n[PASS] testApplicationRuntimeSizeAndNoEscapeOpcodes() (gas: 12092592)\n[PASS] testCannotChooseLaterPriceOrSkipDeadlineRound() (gas: 1053560)\n[PASS] testConstructorNoCallsNoOwnerAndFourteenLanes() (gas: 60554)\n[PASS] testEarlySettlementCannotResolveAndTimeoutIsFixed() (gas: 681028)\n[PASS] testEmptySettlementAndSingleSidedWinner() (gas: 1480255)\n[PASS] testExactExpiryFeedTimestampWinsOverPrevious() (gas: 866603)\n[PASS] testFeeOddsAndRepeatedPositionIndex() (gas: 642220)\n[PASS] testForgedHeaderLaterSampleAndExpiredHashAreRejected() (gas: 684008)\n[PASS] testFuzzConservationProportionalPayoutAndRounding(uint96,uint96,uint96,bool) (runs: 256, μ: 1419760, ~: 1392220)\nLogs:\n  Bound result 99999000000000000006\n  Bound result 15457143729341384\n  Bound result 1044497060318734075\n\n[PASS] testInvalidBatchRecipientAndUnknownMarket() (gas: 205189)\n[PASS] testInvalidBetsAndExactClosingBoundary() (gas: 603722)\n[PASS] testInvalidFeedAnswerTimestampDecimalsAndAnsweredRound() (gas: 609678)\n[PASS] testMaintainRollsFixedSlotsEvenWithBrokenOracles() (gas: 3259120)\n[PASS] testManyWinnersPaidInBoundedBatchesExactlyOnce() (gas: 10486149)\n[PASS] testMissingSqueekCertificationThenRecoveryOrRefund() (gas: 1322243)\n[PASS] testNativeGasAndBlockMarketsUseAuthenticatedBoundary() (gas: 1063976)\n[PASS] testNoWinningBetsRefundNetNotFee() (gas: 1170864)\n[PASS] testPausedAndStaleCoreRejectNewStakesButPreserveRefunds() (gas: 732356)\n[PASS] testProportionalAutomaticPayoutAndReplacement() (gas: 1534953)\n[PASS] testReentrancyDuringPushAndClaimIsRejected() (gas: 1514351)\n[PASS] testRejectedPushCannotBlockOthersOrStealClaims() (gas: 1982207)\n[PASS] testSqueekSettlementReadsExactCertifiedWindowAndPays() (gas: 1136262)\n[PASS] testStaleBoundaryPhaseChangeAndMissingRoundFailClosed() (gas: 970160)\n[PASS] testStaleOpeningPriceCannotTakeFundsAndOtherLanesWork() (gas: 1231835)\n[PASS] testTieResolvesUnderAndBothSidesPerWallet() (gas: 1248728)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 112.26ms (232.57ms CPU time)\n\nRan 1 test for test/AccountingInvariant.t.sol:AccountingInvariantTest\n[PASS]\nAccountingInvariantTest invariants:\n[PASS] invariantBalanceEqualsLiabilitiesAndLockedReserves\n[PASS] invariantExactlyFourteenUniqueCurrentLanes\n[PASS] invariantTotalEtherIsConservedAcrossEveryActor\n AccountingInvariantTest invariants (runs: 64, calls: 4096, reverts: 0)\n\n╭---------------+----------+-------+---------+----------╮\n| Contract      | Selector | Calls | Reverts | Discards |\n+=======================================================+\n| MarketHandler | bet      | 1001  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| MarketHandler | elapse   | 1055  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| MarketHandler | pay      | 1033  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| MarketHandler | resolve  | 1007  | 0       | 0        |\n╰---------------+----------+-------+---------+----------╯\n\nLogs:\n  Bound result 39324\n  Bound result 782593440526928755\n  Bound result 999000000000001510\n  Bound result 13695\n  Bound result 999000000000008641\n  Bound result 242136897799168488\n  Bound result 208000\n  Bound result 60\n  Bound result 7170\n  Bound result 999000002074284042\n  Bound result 999000000000009697\n  Bound result 54\n  Bound result 7\n  Bound result 999000000000043201\n  Bound result 999000000000004591\n  Bound result 8874\n  Bound result 8191\n  Bound result 84000\n  Bound result 192\n  Bound result 999000000000004047\n  Bound result 5671\n  Bound result 7226\n  Bound result 999000001728208211\n  Bound result 999000000000000210\n  Bound result 199\n  Bound result 244\n  Bound result 999000000000007171\n  Bound result 1371\n  Bound result 6446\n  Bound result 25200\n  Bound result 3751\n  Bound result 3413\n  Bound result 836\n  Bound result 23294\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.48s (4.48s CPU time)\n\nRan 5 test suites in 4.48s (4.78s CPU time): 49 tests passed, 0 failed, 0 skipped (49 total tests)\n","passed":true},{"durationMs":54,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SqueekPredict.activate(uint256)\",\"SqueekPredict.bet(uint256,uint8)\",\"SqueekPredict.claim(uint256,address)\",\"SqueekPredict.expire(uint256)\",\"SqueekPredict.maintain()\",\"SqueekPredict.open(uint256)\",\"SqueekPredict.pay(uint256,uint256)\",\"SqueekPredict.performUpkeep(bytes)\",\"SqueekPredict.settleNative(uint256,bytes,bytes)\",\"SqueekPredict.settlePrice(uint256,uint80)\",\"SqueekPredict.settleSqueek(uint256)\"],\"files\":{\".gitignore\":5,\"LICENSE\":21,\"README.md\":148,\"SECURITY.md\":46,\"THIRD_PARTY.md\":15,\"docs/OPERATIONS.md\":42,\"docs/SOURCES.md\":33,\"docs/VALIDATION.md\":38,\"foundry.toml\":22,\"index.html\":2,\"launch.json\":20,\"package.json\":12,\"remappings.txt\":1,\"scripts/configure-site.mjs\":40,\"scripts/export-abi.mjs\":8,\"scripts/keeper-lib.mjs\":91,\"scripts/keeper.mjs\":77,\"scripts/runtime.mjs\":16,\"scripts/serve.mjs\":22,\"src/SqueekMetrics.sol\":145,\"src/SqueekPredict.sol\":605,\"src/interfaces/IAggregatorV3.sol\":8,\"src/interfaces/ISqueekCore.sol\":58,\"src/libraries/BlockHeader.sol\":105,\"test/AccountingInvariant.t.sol\":124,\"test/BlockHeader.t.sol\":67,\"test/SqueekMetrics.t.sol\":120,\"test/SqueekPredict.t.sol\":541,\"test/SqueekPredictRotation.t.sol\":299,\"test/fixtures/SepoliaHeaders.sol\":18,\"test/fixtures/sepolia-headers.json\":56,\"test/helpers/Headers.sol\":34,\"test/keeper.test.mjs\":52,\"test/mocks/MockCore.sol\":109,\"test/mocks/MockFeed.sol\":50,\"test/mocks/MockHistory.sol\":18,\"test/proofs.test.mjs\":62,\"test/runtime.test.mjs\":11,\"test/web.test.mjs\":56,\"web/abi/SqueekMetrics.json\":206,\"web/abi/SqueekPredict.json\":1295,\"web/app.js\":390,\"web/assets/Inter-LICENSE.txt\":92,\"web/assets/InterVariable.woff2\":1389,\"web/assets/mark.svg\":1,\"web/config.json\":8,\"web/index.html\":57,\"web/lib/icons.js\":17,\"web/lib/markets.js\":102,\"web/lib/proofs.js\":96,\"web/styles.css\":12,\"web/vendor/ethers-6.15.0.min.js\":1,\"web/vendor/ethers-LICENSE.md\":21},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"7b6341312de05194e9fdef31c1615763b3693c7d4ab20c73e5f03d44e71e5e63","verifiedTreeHash":"3d8f1698fba9222697b4dce0d5a487c3f0674ee2","verifierVersion":"0.1.0+18543036"},{"checks":[{"durationMs":47257,"exitCode":0,"name":"build","output":"Compiling 42 files with Solc 0.8.26\nSolc 0.8.26 finished in 47.09s\nCompiler run successful with warnings:\nWarning (5159): \"selfdestruct\" has been deprecated. Note that, starting from the Cancun hard fork, the underlying opcode no longer deletes the code and data associated with an account and only transfers its Ether to the beneficiary, unless executed in the same transaction in which the contract was created (see EIP-6780). Any use in newly deployed contracts is strongly discouraged even if the new behavior is taken into account. Future changes to the EVM might further reduce the functionality of the opcode.\n  --> test/helpers/IndependentSetup.sol:39:9:\n   |\n39 |         selfdestruct(recipient);\n   |         ^^^^^^^^^^^^\n\n","passed":true},{"durationMs":11402,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 11 tests for test/SqueekMetrics.t.sol:SqueekMetricsTest\n[PASS] testBlocksSubtractEndpointFloorsNotFloorOfDifference() (gas: 270000)\n[PASS] testConstructorDoesNotCallDependency() (gas: 12980)\n[PASS] testEmptyCertifiedDayIsZero() (gas: 103697)\n[PASS] testFullDay144RecordsAndSevenDayEndpoint() (gas: 56455410)\n[PASS] testFuzzEndpointFloor(uint96,uint96) (runs: 256, μ: 229031, ~: 228915)\nLogs:\n  Bound result 59092427865039329000535\n  Bound result 5770745034821590155865587\n\n[PASS] testInvalidWindowsAndNonAdvancingPageRevert() (gas: 117547)\n[PASS] testMorningAndEveningUseUtcRoundStartAndTieIsNo() (gas: 435346)\n[PASS] testMorningWins() (gas: 241547)\n[PASS] testPaginatedRunSpansPagesAndZeroRoundBreaksStreak() (gas: 8550899)\n[PASS] testUncertifiedRoundsAreUnknownNotZero() (gas: 53595)\n[PASS] testWindowsLeaveOneDurationOfBettingAndUseFullRoundsAndUtcDay() (gas: 106726)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 69.04ms (121.15ms CPU time)\n\nRan 8 tests for test/SqueekPredictRotation.t.sol:SqueekPredictRotationTest\n[PASS] testCheckUpkeepIsFalseWhenNoLaneCanProgress() (gas: 1581050)\n[PASS] testClosedMarketObservesAndSettlesWithoutDisturbingItsSuccessor() (gas: 2575999)\n[PASS] testEveryLaneKeepsABettableMarketWhileThePreviousOneObserves() (gas: 84417827)\n[PASS] testMovedCoreGenesisFailsClosedToRefund() (gas: 1121002)\n[PASS] testNativeLanesHaveAOneDayGraceAndOthersTwoDays() (gas: 1001905)\n[PASS] testNativeProofsUseTheHistoryContractBeyond256BlocksWithinOneDay() (gas: 1716009)\n[PASS] testSettlementGasIsBoundedWhenPushedRecipientsRevertWithLargePayloads() (gas: 9475220)\n[PASS] testSqueekStakesAreRefusedOnceTheWindowCanBeCertified() (gas: 1085331)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 103.68ms (109.41ms CPU time)\n\nRan 25 tests for test/SqueekPredict.t.sol:SqueekPredictTest\n[PASS] testApplicationRuntimeSizeAndNoEscapeOpcodes() (gas: 12092592)\n[PASS] testCannotChooseLaterPriceOrSkipDeadlineRound() (gas: 1053560)\n[PASS] testConstructorNoCallsNoOwnerAndFourteenLanes() (gas: 60554)\n[PASS] testEarlySettlementCannotResolveAndTimeoutIsFixed() (gas: 681028)\n[PASS] testEmptySettlementAndSingleSidedWinner() (gas: 1480255)\n[PASS] testExactExpiryFeedTimestampWinsOverPrevious() (gas: 866603)\n[PASS] testFeeOddsAndRepeatedPositionIndex() (gas: 642220)\n[PASS] testForgedHeaderLaterSampleAndExpiredHashAreRejected() (gas: 684008)\n[PASS] testFuzzConservationProportionalPayoutAndRounding(uint96,uint96,uint96,bool) (runs: 256, μ: 1419760, ~: 1392220)\nLogs:\n  Bound result 99999000000000000006\n  Bound result 15457143729341384\n  Bound result 1044497060318734075\n\n[PASS] testInvalidBatchRecipientAndUnknownMarket() (gas: 205189)\n[PASS] testInvalidBetsAndExactClosingBoundary() (gas: 603722)\n[PASS] testInvalidFeedAnswerTimestampDecimalsAndAnsweredRound() (gas: 609678)\n[PASS] testMaintainRollsFixedSlotsEvenWithBrokenOracles() (gas: 3259120)\n[PASS] testManyWinnersPaidInBoundedBatchesExactlyOnce() (gas: 10486149)\n[PASS] testMissingSqueekCertificationThenRecoveryOrRefund() (gas: 1322243)\n[PASS] testNativeGasAndBlockMarketsUseAuthenticatedBoundary() (gas: 1063976)\n[PASS] testNoWinningBetsRefundNetNotFee() (gas: 1170864)\n[PASS] testPausedAndStaleCoreRejectNewStakesButPreserveRefunds() (gas: 732356)\n[PASS] testProportionalAutomaticPayoutAndReplacement() (gas: 1534953)\n[PASS] testReentrancyDuringPushAndClaimIsRejected() (gas: 1514351)\n[PASS] testRejectedPushCannotBlockOthersOrStealClaims() (gas: 1982207)\n[PASS] testSqueekSettlementReadsExactCertifiedWindowAndPays() (gas: 1136262)\n[PASS] testStaleBoundaryPhaseChangeAndMissingRoundFailClosed() (gas: 970160)\n[PASS] testStaleOpeningPriceCannotTakeFundsAndOtherLanesWork() (gas: 1231835)\n[PASS] testTieResolvesUnderAndBothSidesPerWallet() (gas: 1248728)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 744.30ms (763.12ms CPU time)\n\nRan 3 tests for test/ResolutionMatrix.t.sol:ResolutionMatrixTest\n[PASS] testAllMarketDurationsAndOutcomeKindsAreFixed() (gas: 545869)\n[PASS] testFuzzAllNativeResolutionRoutesAtStrikeAndOneUnitOver(uint8,bool) (runs: 256, μ: 1426662, ~: 1426583)\n[PASS] testFuzzAllSqueekResolutionRoutesAtStrikeAndOneUnitOver(uint8,bool) (runs: 256, μ: 2080778, ~: 1698413)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 862.10ms (1.27s CPU time)\n\nRan 10 tests for test/MetricsProperties.t.sol:MetricsPropertiesTest\n[PASS] testFuzzEndpointMetricsAreAdditiveAcrossAdjacentWindows(uint96,uint96,uint96) (runs: 256, μ: 558840, ~: 558699)\nLogs:\n  Bound result 9341\n  Bound result 7177\n  Bound result 1159\n\n[PASS] testFuzzMalformedActivityPageNeverProducesAnOutcome(uint8) (runs: 256, μ: 255716, ~: 254113)\n[PASS] testFuzzSparsePaginationAgainstDenseDailyModel(uint256,uint16,uint8) (runs: 256, μ: 9317991, ~: 3993727)\nLogs:\n  Bound result 3\n  Bound result 1\n\n[PASS] testFuzzWindowAlignmentWithNonUtcGenesis(uint16,uint32,uint16,bool) (runs: 256, μ: 944517, ~: 945275)\nLogs:\n  Bound result 0\n  Bound result 181192\n  Bound result 763\n\n[PASS] testHistoricalResultCannotBeChangedByLaterRounds() (gas: 1088644)\n[PASS] testInvalidEndpointCertificationIsRejected() (gas: 44454)\n[PASS] testInvalidWindowDurationsAndEndpoints() (gas: 267250)\n[PASS] testLivenessAcceptsExactSixHoursAndRejectsFutureCertification() (gas: 95068)\n[PASS] testPartialPagesMustReachEndWithinBoundedNumberOfCalls() (gas: 88627)\n[PASS] testUnsupportedRoundDurationOrBlockUnitScalingFailsClosed() (gas: 52600)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 868.02ms (1.08s CPU time)\n\nRan 12 tests for test/GenerationLifecycle.t.sol:GenerationLifecycleTest\n[PASS] testBlockhashServesExactly256BlocksAndHistoryBeyond() (gas: 886976)\n[PASS] testFuzzAnyCoreGenesisMoveFailsClosedAndStakesRefund(uint256,bool) (runs: 512, μ: 1651700, ~: 1641956)\nLogs:\n  Bound result 22546\n\n[PASS] testFuzzSqueekLanesTakeStakesOnlyBeforeTheirWindowStarts(uint8,uint256) (runs: 512, μ: 956698, ~: 956849)\nLogs:\n  Bound result 1728144369\n\n[PASS] testFuzzSqueekSuccessorWindowStartsExactlyWherePredecessorEnds(uint8,uint256) (runs: 256, μ: 1498568, ~: 1702749)\nLogs:\n  Bound result 333\n\n[PASS] testKeeperSkippingTheWholeWindowRetiresDirectlyWithOneSuccessor() (gas: 1803894)\n[PASS] testMalformedOrFailingHistoryAnswersAreNeverTrusted() (gas: 902475)\n[PASS] testObservingMarketTimesOutToExactRefundWithoutTouchingSuccessor() (gas: 3046503)\n[PASS] testObservingNativeLaneSettlesThroughHistoryAndClearsItsSlot() (gas: 2765624)\n[PASS] testPreviewOpenAndActivateRejectEveryNonScheduledOrForeignCall() (gas: 73223)\n[PASS] testPriceBetsStopExactlyAtCloseAndSuccessorTakesFreshStrike() (gas: 3094414)\n[PASS] testSettlingAnUnrotatedActiveMarketSchedulesItsSuccessor() (gas: 1733918)\n[PASS] testUnchangedGenesisStillSettlesAfterLaneRotation() (gas: 2179931)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 868.20ms (2.45s CPU time)\n\nRan 9 tests for test/HeaderProperties.t.sol:HeaderPropertiesTest\n[PASS] testAuthenticatedButNonBoundaryHeadersAreRejected() (gas: 282765)\n[PASS] testCurrentAndFutureBlocksCannotBeProven() (gas: 79126)\n[PASS] testEveryNumericFieldRejectsNonCanonicalAndOversizedIntegers() (gas: 572219)\n[PASS] testFuzzBoundaryRequiresExactParentAndTargetHashes(bytes32) (runs: 1000, μ: 163036, ~: 163036)\n[PASS] testFuzzFullWidthHeaderValuesRoundTrip(bytes32,uint256,uint256,uint256) (runs: 1000, μ: 56756, ~: 57494)\n[PASS] testFuzzTruncatedHeaderCannotDecode(uint256) (runs: 1000, μ: 37799, ~: 37540)\nLogs:\n  Bound result 22\n\n[PASS] testMissingBaseFeeTooManyFieldsAndNestedUnusedFieldsAreRejected() (gas: 109645)\n[PASS] testParentHashMustBeExactly32Bytes() (gas: 41941)\n[PASS] testZeroAndMaximumIntegersHaveDistinctValidEncodings() (gas: 121798)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 887.48ms (2.64s CPU time)\n\nRan 4 tests for test/BlockHeader.t.sol:BlockHeaderTest\n[PASS] testBoundaryAt256BlockLimit() (gas: 96949)\n[PASS] testFuzzDecodeBoundedHeader(uint64,uint64,uint64,bytes32) (runs: 256, μ: 42519, ~: 42639)\n[PASS] testRealSepoliaAmsterdamBoundaryIsDecodedAndAuthenticated() (gas: 69024)\n[PASS] testRejectMalformedRlp() (gas: 90237)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 887.48ms (19.71ms CPU time)\n\nRan 10 tests for test/PayoutProperties.t.sol:PayoutPropertiesTest\n[PASS] testAllRejectingWinnersRetainDustUntilLastClaim() (gas: 2609970)\n[PASS] testClaimBeforeItsBatchTurnIsSkippedWithoutPayingTwice() (gas: 6248792)\n[PASS] testClaimSurvivesManyGenerationsAndHasNoExpiry() (gas: 5876914)\n[PASS] testForcedEtherDoesNotChangeOddsEntitlementsOrFees() (gas: 2115256)\n[PASS] testFuzzClaimCannotReenterAnyGuardedEntryPoint(uint8) (runs: 1000, μ: 2386093, ~: 2400990)\n[PASS] testFuzzPayoutBoundsAndOrderIndependence(uint96,uint96,uint96,bool) (runs: 1000, μ: 2643759, ~: 2643911)\nLogs:\n  Bound result 99999000000000000076\n  Bound result 90734394338070429591\n  Bound result 95734236441236350257\n\n[PASS] testFuzzSameWalletOnBothSidesCannotProfitFromRefund(uint96,uint96) (runs: 1000, μ: 1160905, ~: 1161011)\nLogs:\n  Bound result 28219178049917660786\n  Bound result 52976591953516181342\n\n[PASS] testGasExhaustingRecipientCannotFreezeFollowingBettors() (gas: 2016883)\n[PASS] testOtherMarketsCannotConsumePendingWinnerLiability() (gas: 2451514)\n[PASS] testRejectedClaimRollsBackEveryDebitThenCanRetry() (gas: 2198404)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 887.37ms (2.60s CPU time)\n\nRan 15 tests for test/ProtocolEdges.t.sol:ProtocolEdgesTest\n[PASS] testAggregatePoolCapAcrossRepeatedCallsAndBothSides() (gas: 771880176)\n[PASS] testBatchReadsHandleEmptyMaximumDuplicateAndUnknownIds() (gas: 5175041)\n[PASS] testBoundaryRoundIdentifiersCannotBeForged() (gas: 857523)\n[PASS] testExactTwoHourBoundaryAgesAreAcceptedButOneSecondOlderIsNot() (gas: 760347)\n[PASS] testFuzzAllPriceLanesUseTheirOwnFeedAndStrictStrike(uint8,bool) (runs: 1000, μ: 1627118, ~: 1627025)\n[PASS] testFuzzEveryInvalidBoundaryRoundPreservesFundedMarket(uint8,bool) (runs: 1000, μ: 1373518, ~: 1373060)\n[PASS] testFuzzFeeRoundsDownAndOddsMoveTowardNewStake(uint96,bool) (runs: 1000, μ: 1168232, ~: 1168259)\nLogs:\n  Bound result 46248838233222914958\n\n[PASS] testFuzzUnknownIdsCannotReadMutateOrClaim(uint256) (runs: 1000, μ: 962508, ~: 961904)\nLogs:\n  Bound result 36529991493058067436787464720809167123670678860630917155119967976963475497019\n\n[PASS] testMinimumMaximumAndOneWeiOutsideBetBounds() (gas: 1219629)\n[PASS] testMorningBetsCloseAtStartAndOtherActivityBetsUseHalfWindow() (gas: 1006848)\n[PASS] testNativeProofCannotCountBlocksBeforeOpening() (gas: 257555)\n[PASS] testNewLaneOpensOnceAndOldAwaitingMarketSettlesWithoutSecondReplacement() (gas: 2481352)\n[PASS] testPositionPaginationIsNewestFirstUniqueAndBounded() (gas: 1426053)\n[PASS] testSqueekHistoricalSettlementWorksWhileCorePaused() (gas: 1155352)\n[PASS] testWrongSettlementRouteRollsBackReplacementAndBalances() (gas: 1380049)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 904.56ms (3.67s CPU time)\n\nRan 1 test for test/AccountingInvariant.t.sol:AccountingInvariantTest\n[PASS]\nAccountingInvariantTest invariants:\n[PASS] invariantBalanceEqualsLiabilitiesAndLockedReserves\n[PASS] invariantExactlyFourteenUniqueCurrentLanes\n[PASS] invariantTotalEtherIsConservedAcrossEveryActor\n AccountingInvariantTest invariants (runs: 64, calls: 4096, reverts: 0)\n\n╭---------------+----------+-------+---------+----------╮\n| Contract      | Selector | Calls | Reverts | Discards |\n+=======================================================+\n| MarketHandler | bet      | 1001  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| MarketHandler | elapse   | 1055  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| MarketHandler | pay      | 1033  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| MarketHandler | resolve  | 1007  | 0       | 0        |\n╰---------------+----------+-------+---------+----------╯\n\nLogs:\n  Bound result 8192\n  Bound result 999000000000011330\n  Bound result 999000000000000973\n  Bound result 13505\n  Bound result 999000000000008708\n  Bound result 242136897799168488\n  Bound result 101\n  Bound result 10101\n  Bound result 6355\n  Bound result 999000002074284042\n  Bound result 999000000000005986\n  Bound result 54\n  Bound result 379\n  Bound result 999000000000000376\n  Bound result 999000000000003912\n  Bound result 8874\n  Bound result 2048\n  Bound result 25200\n  Bound result 7200\n  Bound result 999000000000002839\n  Bound result 2843\n  Bound result 6446\n  Bound result 999000000000014980\n  Bound result 999000000000001185\n  Bound result 2049\n  Bound result 46\n  Bound result 999000000000006333\n  Bound result 5213\n  Bound result 5943\n  Bound result 8458\n  Bound result 5354\n  Bound result 7905\n  Bound result 1665\n  Bound result 23294\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 5.36s (5.35s CPU time)\n\nRan 1 test for test/StatefulSolvency.t.sol:StatefulSolvencyTest\n[PASS]\nStatefulSolvencyTest invariants:\n[PASS] invariantCurrentSlotsRemainUniqueAndKeepTheirTemplates\n[PASS] invariantIndependentLedgerAndClosedSystemConserveEther\n[PASS] invariantObservingGenerationsAreOlderOpenAndNeverOverlapTheirSuccessor\n[PASS] invariantPayoutRightsAndTerminalMarketsCannotChange\n StatefulSolvencyTest invariants (runs: 64, calls: 6144, reverts: 0)\n\n╭-----------------+-----------------+-------+---------+----------╮\n| Contract        | Selector        | Calls | Reverts | Discards |\n+================================================================+\n| SolvencyHandler | advance         | 684   | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| SolvencyHandler | bet             | 728   | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| SolvencyHandler | claim           | 729   | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| SolvencyHandler | donate          | 664   | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| SolvencyHandler | finish          | 653   | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| SolvencyHandler | invalidBet      | 688   | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| SolvencyHandler | maintain        | 666   | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| SolvencyHandler | pay             | 646   | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| SolvencyHandler | toggleRecipient | 686   | 0       | 0        |\n╰-----------------+-----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 10000000000000000\n  Bound result 20000000000000000\n  Bound result 30000000000000000\n  Bound result 40000000000000000\n  Bound result 50000000000000000\n  Bound result 60000000000000000\n  Bound result 100000000000000000\n  Bound result 10000000000000000\n  Bound result 20000000000000000\n  Bound result 99999000000000000021\n  Bound result 74729656121979870523\n  Bound result 37\n  Bound result 1680\n  Bound result 15\n  Bound result 40\n  Bound result 220000000000\n  Bound result 1000000000000000000\n  Bound result 39\n  Bound result 7\n  Bound result 99999000000000000007\n  Bound result 9636015767456223058\n  Bound result 18\n  Bound result 893488147419103231\n  Bound result 7500\n  Bound result 99999000000000002101\n  Bound result 99999000000000000033\n  Bound result 1\n  Bound result 198016\n  Bound result 236798\n  Bound result 826074471\n  Bound result 41\n  Bound result 22\n  Bound result 23\n  Bound result 3522\n  Bound result 12\n  Bound result 128000\n  Bound result 86\n  Bound result 62689\n  Bound result 2\n  Bound result 243\n  Bound result 19611\n  Bound result 28142141643097\n  Bound result 109\n  Bound result 1200000\n  Bound result 337408173386050706\n  Bound result 199\n  Bound result 408074578788100483\n  Bound result 253\n  Bound result 1750\n  Bound result 1\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 11.27s (11.27s CPU time)\n\nRan 12 test suites in 11.28s (23.71s CPU time): 109 tests passed, 0 failed, 0 skipped (109 total tests)\n","passed":true},{"durationMs":60,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SqueekPredict.activate(uint256)\",\"SqueekPredict.bet(uint256,uint8)\",\"SqueekPredict.claim(uint256,address)\",\"SqueekPredict.expire(uint256)\",\"SqueekPredict.maintain()\",\"SqueekPredict.open(uint256)\",\"SqueekPredict.pay(uint256,uint256)\",\"SqueekPredict.performUpkeep(bytes)\",\"SqueekPredict.settleNative(uint256,bytes,bytes)\",\"SqueekPredict.settlePrice(uint256,uint80)\",\"SqueekPredict.settleSqueek(uint256)\"],\"files\":{\".gitignore\":5,\"LICENSE\":21,\"README.md\":148,\"SECURITY.md\":46,\"THIRD_PARTY.md\":15,\"docs/OPERATIONS.md\":42,\"docs/SOURCES.md\":33,\"docs/VALIDATION.md\":38,\"foundry.toml\":22,\"index.html\":2,\"package.json\":12,\"remappings.txt\":1,\"scripts/configure-site.mjs\":40,\"scripts/export-abi.mjs\":8,\"scripts/keeper-lib.mjs\":91,\"scripts/keeper.mjs\":77,\"scripts/runtime.mjs\":16,\"scripts/serve.mjs\":22,\"src/SqueekMetrics.sol\":145,\"src/SqueekPredict.sol\":605,\"src/interfaces/IAggregatorV3.sol\":8,\"src/interfaces/ISqueekCore.sol\":58,\"src/libraries/BlockHeader.sol\":105,\"test/AccountingInvariant.t.sol\":124,\"test/BlockHeader.t.sol\":67,\"test/GenerationLifecycle.t.sol\":372,\"test/HeaderProperties.t.sol\":174,\"test/INDEPENDENT_TESTS.md\":121,\"test/MetricsProperties.t.sol\":209,\"test/PayoutProperties.t.sol\":254,\"test/ProtocolEdges.t.sol\":277,\"test/ResolutionMatrix.t.sol\":113,\"test/SqueekMetrics.t.sol\":120,\"test/SqueekPredict.t.sol\":541,\"test/SqueekPredictRotation.t.sol\":299,\"test/StatefulSolvency.t.sol\":480,\"test/fixtures/SepoliaHeaders.sol\":18,\"test/fixtures/sepolia-headers.json\":56,\"test/helpers/Headers.sol\":34,\"test/helpers/IndependentSetup.sol\":108,\"test/keeper.test.mjs\":52,\"test/mocks/MockCore.sol\":109,\"test/mocks/MockFeed.sol\":50,\"test/mocks/MockHistory.sol\":18,\"test/proofs.test.mjs\":62,\"test/runtime.test.mjs\":11,\"test/web.test.mjs\":56,\"web/abi/SqueekMetrics.json\":206,\"web/abi/SqueekPredict.json\":1295,\"web/app.js\":390,\"web/assets/Inter-LICENSE.txt\":92,\"web/assets/InterVariable.woff2\":1389,\"web/assets/mark.svg\":1,\"web/config.json\":8,\"web/index.html\":57,\"web/lib/icons.js\":17,\"web/lib/markets.js\":102,\"web/lib/proofs.js\":96,\"web/styles.css\":12,\"web/vendor/ethers-6.15.0.min.js\":1,\"web/vendor/ethers-LICENSE.md\":21},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"a5566dfa71938509a70f3d7b23ff4e8e2d283585193a92be7e6036b44eca494e","verifiedTreeHash":"0c418bb6693f0a43081a40d4ed807182e16f0361","verifierVersion":"0.1.0+18543036"},{"checks":[{"durationMs":13816,"exitCode":0,"name":"build","output":"Compiling 32 files with Solc 0.8.26\nSolc 0.8.26 finished in 13.74s\nCompiler run successful!\n","passed":true},{"durationMs":4239,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/BlockHeader.t.sol:BlockHeaderTest\n[PASS] testBoundaryAt256BlockLimit() (gas: 93889)\n[PASS] testFuzzDecodeBoundedHeader(uint64,uint64,uint64,bytes32) (runs: 256, μ: 42443, ~: 42659)\n[PASS] testRealSepoliaAmsterdamBoundaryIsDecodedAndAuthenticated() (gas: 68864)\n[PASS] testRejectMalformedRlp() (gas: 90237)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 12.53ms (12.87ms CPU time)\n\nRan 11 tests for test/SqueekMetrics.t.sol:SqueekMetricsTest\n[PASS] testBlocksSubtractEndpointFloorsNotFloorOfDifference() (gas: 269890)\n[PASS] testConstructorDoesNotCallDependency() (gas: 12958)\n[PASS] testEmptyCertifiedDayIsZero() (gas: 103455)\n[PASS] testFullDay144RecordsAndSevenDayEndpoint() (gas: 56454816)\n[PASS] testFuzzEndpointFloor(uint96,uint96) (runs: 256, μ: 228981, ~: 228865)\nLogs:\n  Bound result 59092427865039329000535\n  Bound result 5770745034821590155865587\n\n[PASS] testInvalidWindowsAndNonAdvancingPageRevert() (gas: 117393)\n[PASS] testMorningAndEveningUseUtcRoundStartAndTieIsNo() (gas: 435258)\n[PASS] testMorningWins() (gas: 241481)\n[PASS] testPaginatedRunSpansPagesAndZeroRoundBreaksStreak() (gas: 8550635)\n[PASS] testUncertifiedRoundsAreUnknownNotZero() (gas: 53573)\n[PASS] testWindowsUseFutureFullRoundsAndUtcDay() (gas: 43505)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 55.90ms (51.27ms CPU time)\n\nRan 25 tests for test/SqueekPredict.t.sol:SqueekPredictTest\n[PASS] testApplicationRuntimeSizeAndNoEscapeOpcodes() (gas: 11106228)\n[PASS] testCannotChooseLaterPriceOrSkipDeadlineRound() (gas: 1045962)\n[PASS] testConstructorNoCallsNoOwnerAndFourteenLanes() (gas: 60324)\n[PASS] testEarlySettlementCannotResolveAndTimeoutIsFixed() (gas: 677580)\n[PASS] testEmptySettlementAndSingleSidedWinner() (gas: 1474820)\n[PASS] testExactExpiryFeedTimestampWinsOverPrevious() (gas: 861479)\n[PASS] testFeeOddsAndRepeatedPositionIndex() (gas: 641662)\n[PASS] testForgedHeaderLaterSampleAndExpiredHashAreRejected() (gas: 672871)\n[PASS] testFuzzConservationProportionalPayoutAndRounding(uint96,uint96,uint96,bool) (runs: 256, μ: 1416750, ~: 1389181)\nLogs:\n  Bound result 99999000000000019217\n  Bound result 99999000000000016947\n  Bound result 99999000000000000651\n\n[PASS] testInvalidBatchRecipientAndUnknownMarket() (gas: 204703)\n[PASS] testInvalidBetsAndExactClosingBoundary() (gas: 603216)\n[PASS] testInvalidFeedAnswerTimestampDecimalsAndAnsweredRound() (gas: 608533)\n[PASS] testMaintainRollsFixedSlotsEvenWithBrokenOracles() (gas: 3069240)\n[PASS] testManyWinnersPaidInBoundedBatchesExactlyOnce() (gas: 10482314)\n[PASS] testMissingSqueekCertificationThenRecoveryOrRefund() (gas: 898088)\n[PASS] testNativeGasAndBlockMarketsUseAuthenticatedBoundary() (gas: 1058237)\n[PASS] testNoWinningBetsRefundNetNotFee() (gas: 1167986)\n[PASS] testPausedAndStaleCoreRejectNewStakesButPreserveRefunds() (gas: 729858)\n[PASS] testProportionalAutomaticPayoutAndReplacement() (gas: 1531597)\n[PASS] testReentrancyDuringPushAndClaimIsRejected() (gas: 1511422)\n[PASS] testRejectedPushCannotBlockOthersOrStealClaims() (gas: 1979282)\n[PASS] testSqueekSettlementReadsExactCertifiedWindowAndPays() (gas: 1125462)\n[PASS] testStaleBoundaryPhaseChangeAndMissingRoundFailClosed() (gas: 962650)\n[PASS] testStaleOpeningPriceCannotTakeFundsAndOtherLanesWork() (gas: 1173235)\n[PASS] testTieResolvesUnderAndBothSidesPerWallet() (gas: 1245797)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 110.96ms (242.24ms CPU time)\n\nRan 1 test for test/AccountingInvariant.t.sol:AccountingInvariantTest\n[PASS]\nAccountingInvariantTest invariants:\n[PASS] invariantBalanceEqualsLiabilitiesAndLockedReserves\n[PASS] invariantExactlyFourteenUniqueCurrentLanes\n[PASS] invariantTotalEtherIsConservedAcrossEveryActor\n AccountingInvariantTest invariants (runs: 64, calls: 4096, reverts: 0)\n\n╭---------------+----------+-------+---------+----------╮\n| Contract      | Selector | Calls | Reverts | Discards |\n+=======================================================+\n| MarketHandler | bet      | 1001  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| MarketHandler | elapse   | 1055  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| MarketHandler | pay      | 1033  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| MarketHandler | resolve  | 1007  | 0       | 0        |\n╰---------------+----------+-------+---------+----------╯\n\nLogs:\n  Bound result 259200\n  Bound result 999000000000011405\n  Bound result 999000000000001455\n  Bound result 13191\n  Bound result 999000000000008609\n  Bound result 242136897799168488\n  Bound result 37\n  Bound result 36\n  Bound result 5870\n  Bound result 999000002074284042\n  Bound result 999000000000007997\n  Bound result 54\n  Bound result 1189\n  Bound result 999000000000002786\n  Bound result 999999999998998999\n  Bound result 8874\n  Bound result 43200\n  Bound result 7201\n  Bound result 15\n  Bound result 999000000000002324\n  Bound result 7658\n  Bound result 23295\n  Bound result 557817741235783364\n  Bound result 999000000000000370\n  Bound result 144\n  Bound result 60\n  Bound result 999000000000005864\n  Bound result 6052\n  Bound result 5648\n  Bound result 101\n  Bound result 5414\n  Bound result 2544\n  Bound result 80\n  Bound result 23294\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.14s (4.14s CPU time)\n\nRan 4 test suites in 4.14s (4.32s CPU time): 41 tests passed, 0 failed, 0 skipped (41 total tests)\n","passed":true},{"durationMs":50,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SqueekPredict.activate(uint256)\",\"SqueekPredict.bet(uint256,uint8)\",\"SqueekPredict.claim(uint256,address)\",\"SqueekPredict.expire(uint256)\",\"SqueekPredict.maintain()\",\"SqueekPredict.open(uint256)\",\"SqueekPredict.pay(uint256,uint256)\",\"SqueekPredict.performUpkeep(bytes)\",\"SqueekPredict.settleNative(uint256,bytes,bytes)\",\"SqueekPredict.settlePrice(uint256,uint80)\",\"SqueekPredict.settleSqueek(uint256)\"],\"files\":{\".gitignore\":5,\"LICENSE\":21,\"README.md\":137,\"SECURITY.md\":43,\"THIRD_PARTY.md\":15,\"docs/OPERATIONS.md\":42,\"docs/SOURCES.md\":33,\"docs/VALIDATION.md\":38,\"foundry.toml\":22,\"index.html\":2,\"launch.json\":20,\"package.json\":12,\"remappings.txt\":1,\"scripts/configure-site.mjs\":39,\"scripts/export-abi.mjs\":8,\"scripts/keeper-lib.mjs\":71,\"scripts/keeper.mjs\":76,\"scripts/runtime.mjs\":16,\"scripts/serve.mjs\":22,\"src/SqueekMetrics.sol\":136,\"src/SqueekPredict.sol\":517,\"src/interfaces/IAggregatorV3.sol\":8,\"src/interfaces/ISqueekCore.sol\":58,\"src/libraries/BlockHeader.sol\":91,\"test/AccountingInvariant.t.sol\":124,\"test/BlockHeader.t.sol\":67,\"test/SqueekMetrics.t.sol\":111,\"test/SqueekPredict.t.sol\":541,\"test/fixtures/SepoliaHeaders.sol\":18,\"test/fixtures/sepolia-headers.json\":56,\"test/helpers/Headers.sol\":34,\"test/keeper.test.mjs\":34,\"test/mocks/MockCore.sol\":104,\"test/mocks/MockFeed.sol\":50,\"test/proofs.test.mjs\":61,\"test/runtime.test.mjs\":11,\"test/web.test.mjs\":41,\"web/abi/SqueekMetrics.json\":187,\"web/abi/SqueekPredict.json\":1192,\"web/app.js\":390,\"web/assets/Inter-LICENSE.txt\":92,\"web/assets/InterVariable.woff2\":1389,\"web/assets/mark.svg\":1,\"web/config.json\":8,\"web/index.html\":57,\"web/lib/icons.js\":17,\"web/lib/markets.js\":92,\"web/lib/proofs.js\":96,\"web/styles.css\":12,\"web/vendor/ethers-6.15.0.min.js\":1,\"web/vendor/ethers-LICENSE.md\":21},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"aa460542dff5d234f9bd10fcac1af55616f663f5f8d4e08aab3d0f75d3bbc7c1","verifiedTreeHash":"79c5f694047fe4e071a5a363bb697af8019b261f","verifierVersion":"0.1.0+18543036"},{"checks":[{"durationMs":17378,"exitCode":0,"name":"build","output":"Compiling 34 files with Solc 0.8.26\nSolc 0.8.26 finished in 17.28s\nCompiler run successful!\n","passed":true},{"durationMs":4504,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/BlockHeader.t.sol:BlockHeaderTest\n[PASS] testBoundaryAt256BlockLimit() (gas: 96949)\n[PASS] testFuzzDecodeBoundedHeader(uint64,uint64,uint64,bytes32) (runs: 256, μ: 42459, ~: 42659)\n[PASS] testRealSepoliaAmsterdamBoundaryIsDecodedAndAuthenticated() (gas: 69024)\n[PASS] testRejectMalformedRlp() (gas: 90237)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 10.72ms (12.62ms CPU time)\n\nRan 11 tests for test/SqueekMetrics.t.sol:SqueekMetricsTest\n[PASS] testBlocksSubtractEndpointFloorsNotFloorOfDifference() (gas: 270000)\n[PASS] testConstructorDoesNotCallDependency() (gas: 12980)\n[PASS] testEmptyCertifiedDayIsZero() (gas: 103697)\n[PASS] testFullDay144RecordsAndSevenDayEndpoint() (gas: 56455410)\n[PASS] testFuzzEndpointFloor(uint96,uint96) (runs: 256, μ: 229031, ~: 228915)\nLogs:\n  Bound result 7908\n  Bound result 2039\n\n[PASS] testInvalidWindowsAndNonAdvancingPageRevert() (gas: 117547)\n[PASS] testMorningAndEveningUseUtcRoundStartAndTieIsNo() (gas: 435346)\n[PASS] testMorningWins() (gas: 241547)\n[PASS] testPaginatedRunSpansPagesAndZeroRoundBreaksStreak() (gas: 8550899)\n[PASS] testUncertifiedRoundsAreUnknownNotZero() (gas: 53595)\n[PASS] testWindowsLeaveOneDurationOfBettingAndUseFullRoundsAndUtcDay() (gas: 106726)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 29.99ms (53.11ms CPU time)\n\nRan 8 tests for test/SqueekPredictRotation.t.sol:SqueekPredictRotationTest\n[PASS] testCheckUpkeepIsFalseWhenNoLaneCanProgress() (gas: 1581050)\n[PASS] testClosedMarketObservesAndSettlesWithoutDisturbingItsSuccessor() (gas: 2575999)\n[PASS] testEveryLaneKeepsABettableMarketWhileThePreviousOneObserves() (gas: 84417827)\n[PASS] testMovedCoreGenesisFailsClosedToRefund() (gas: 1121002)\n[PASS] testNativeLanesHaveAOneDayGraceAndOthersTwoDays() (gas: 1001905)\n[PASS] testNativeProofsUseTheHistoryContractBeyond256BlocksWithinOneDay() (gas: 1716009)\n[PASS] testSettlementGasIsBoundedWhenPushedRecipientsRevertWithLargePayloads() (gas: 9475220)\n[PASS] testSqueekStakesAreRefusedOnceTheWindowCanBeCertified() (gas: 1085331)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 102.48ms (118.12ms CPU time)\n\nRan 25 tests for test/SqueekPredict.t.sol:SqueekPredictTest\n[PASS] testApplicationRuntimeSizeAndNoEscapeOpcodes() (gas: 12092592)\n[PASS] testCannotChooseLaterPriceOrSkipDeadlineRound() (gas: 1053560)\n[PASS] testConstructorNoCallsNoOwnerAndFourteenLanes() (gas: 60554)\n[PASS] testEarlySettlementCannotResolveAndTimeoutIsFixed() (gas: 681028)\n[PASS] testEmptySettlementAndSingleSidedWinner() (gas: 1480255)\n[PASS] testExactExpiryFeedTimestampWinsOverPrevious() (gas: 866603)\n[PASS] testFeeOddsAndRepeatedPositionIndex() (gas: 642220)\n[PASS] testForgedHeaderLaterSampleAndExpiredHashAreRejected() (gas: 684008)\n[PASS] testFuzzConservationProportionalPayoutAndRounding(uint96,uint96,uint96,bool) (runs: 256, μ: 1419760, ~: 1392220)\nLogs:\n  Bound result 99999000000000019732\n  Bound result 99999000000000005970\n  Bound result 99999000000000009930\n\n[PASS] testInvalidBatchRecipientAndUnknownMarket() (gas: 205189)\n[PASS] testInvalidBetsAndExactClosingBoundary() (gas: 603722)\n[PASS] testInvalidFeedAnswerTimestampDecimalsAndAnsweredRound() (gas: 609678)\n[PASS] testMaintainRollsFixedSlotsEvenWithBrokenOracles() (gas: 3259120)\n[PASS] testManyWinnersPaidInBoundedBatchesExactlyOnce() (gas: 10486149)\n[PASS] testMissingSqueekCertificationThenRecoveryOrRefund() (gas: 1322243)\n[PASS] testNativeGasAndBlockMarketsUseAuthenticatedBoundary() (gas: 1063976)\n[PASS] testNoWinningBetsRefundNetNotFee() (gas: 1170864)\n[PASS] testPausedAndStaleCoreRejectNewStakesButPreserveRefunds() (gas: 732356)\n[PASS] testProportionalAutomaticPayoutAndReplacement() (gas: 1534953)\n[PASS] testReentrancyDuringPushAndClaimIsRejected() (gas: 1514351)\n[PASS] testRejectedPushCannotBlockOthersOrStealClaims() (gas: 1982207)\n[PASS] testSqueekSettlementReadsExactCertifiedWindowAndPays() (gas: 1136262)\n[PASS] testStaleBoundaryPhaseChangeAndMissingRoundFailClosed() (gas: 970160)\n[PASS] testStaleOpeningPriceCannotTakeFundsAndOtherLanesWork() (gas: 1231835)\n[PASS] testTieResolvesUnderAndBothSidesPerWallet() (gas: 1248728)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 113.14ms (200.31ms CPU time)\n\nRan 1 test for test/AccountingInvariant.t.sol:AccountingInvariantTest\n[PASS]\nAccountingInvariantTest invariants:\n[PASS] invariantBalanceEqualsLiabilitiesAndLockedReserves\n[PASS] invariantExactlyFourteenUniqueCurrentLanes\n[PASS] invariantTotalEtherIsConservedAcrossEveryActor\n AccountingInvariantTest invariants (runs: 64, calls: 4096, reverts: 0)\n\n╭---------------+----------+-------+---------+----------╮\n| Contract      | Selector | Calls | Reverts | Discards |\n+=======================================================+\n| MarketHandler | bet      | 1001  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| MarketHandler | elapse   | 1055  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| MarketHandler | pay      | 1033  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| MarketHandler | resolve  | 1007  | 0       | 0        |\n╰---------------+----------+-------+---------+----------╯\n\nLogs:\n  Bound result 39324\n  Bound result 782593440526928755\n  Bound result 999000000000001510\n  Bound result 13695\n  Bound result 999000000000008641\n  Bound result 242136897799168488\n  Bound result 208000\n  Bound result 60\n  Bound result 7170\n  Bound result 999000002074284042\n  Bound result 999000000000009697\n  Bound result 54\n  Bound result 7\n  Bound result 999000000000043201\n  Bound result 999000000000004591\n  Bound result 8874\n  Bound result 8191\n  Bound result 84000\n  Bound result 192\n  Bound result 999000003540285835\n  Bound result 5670\n  Bound result 7227\n  Bound result 999000001596441808\n  Bound result 999000000000000210\n  Bound result 199\n  Bound result 244\n  Bound result 999000000000007171\n  Bound result 1386\n  Bound result 6444\n  Bound result 25200\n  Bound result 5713\n  Bound result 3419\n  Bound result 856\n  Bound result 23294\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.39s (4.38s CPU time)\n\nRan 5 test suites in 4.39s (4.64s CPU time): 49 tests passed, 0 failed, 0 skipped (49 total tests)\n","passed":true},{"durationMs":55,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SqueekPredict.activate(uint256)\",\"SqueekPredict.bet(uint256,uint8)\",\"SqueekPredict.claim(uint256,address)\",\"SqueekPredict.expire(uint256)\",\"SqueekPredict.maintain()\",\"SqueekPredict.open(uint256)\",\"SqueekPredict.pay(uint256,uint256)\",\"SqueekPredict.performUpkeep(bytes)\",\"SqueekPredict.settleNative(uint256,bytes,bytes)\",\"SqueekPredict.settlePrice(uint256,uint80)\",\"SqueekPredict.settleSqueek(uint256)\"],\"files\":{\".gitignore\":5,\"LICENSE\":21,\"README.md\":148,\"SECURITY.md\":46,\"THIRD_PARTY.md\":15,\"docs/OPERATIONS.md\":42,\"docs/SOURCES.md\":33,\"docs/VALIDATION.md\":38,\"foundry.toml\":22,\"index.html\":2,\"package.json\":12,\"remappings.txt\":1,\"scripts/configure-site.mjs\":40,\"scripts/export-abi.mjs\":8,\"scripts/keeper-lib.mjs\":91,\"scripts/keeper.mjs\":77,\"scripts/runtime.mjs\":16,\"scripts/serve.mjs\":22,\"src/SqueekMetrics.sol\":145,\"src/SqueekPredict.sol\":605,\"src/interfaces/IAggregatorV3.sol\":8,\"src/interfaces/ISqueekCore.sol\":58,\"src/libraries/BlockHeader.sol\":105,\"test/AccountingInvariant.t.sol\":124,\"test/BlockHeader.t.sol\":67,\"test/SqueekMetrics.t.sol\":120,\"test/SqueekPredict.t.sol\":541,\"test/SqueekPredictRotation.t.sol\":299,\"test/fixtures/SepoliaHeaders.sol\":18,\"test/fixtures/sepolia-headers.json\":56,\"test/helpers/Headers.sol\":34,\"test/keeper.test.mjs\":52,\"test/mocks/MockCore.sol\":109,\"test/mocks/MockFeed.sol\":50,\"test/mocks/MockHistory.sol\":18,\"test/proofs.test.mjs\":62,\"test/runtime.test.mjs\":11,\"test/web.test.mjs\":56,\"web/abi/SqueekMetrics.json\":206,\"web/abi/SqueekPredict.json\":1295,\"web/app.js\":390,\"web/assets/Inter-LICENSE.txt\":92,\"web/assets/InterVariable.woff2\":1389,\"web/assets/mark.svg\":1,\"web/config.json\":8,\"web/index.html\":57,\"web/lib/icons.js\":17,\"web/lib/markets.js\":102,\"web/lib/proofs.js\":96,\"web/styles.css\":12,\"web/vendor/ethers-6.15.0.min.js\":1,\"web/vendor/ethers-LICENSE.md\":21},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":3015,"exitCode":0,"name":"slither","output":"[high/medium] reentrancy-eth at src/SqueekPredict.sol:374: Reentrancy in SqueekPredict.claim(uint256,address) (src/SqueekPredict.sol#374-383):\n[medium/medium] divide-before-multiply at src/SqueekMetrics.sol:37: SqueekMetrics.window(uint64,uint32,bool) (src/SqueekMetrics.sol#37-50) performs a multiplication on the result of a division:\n[medium/medium] reentrancy-no-eth at src/SqueekPredict.sol:362: Reentrancy in SqueekPredict.expire(uint256) (src/SqueekPredict.sol#362-365):\n[medium/medium] reentrancy-no-eth at src/SqueekPredict.sol:349: Reentrancy in SqueekPredict.settleNative(uint256,bytes,bytes) (src/SqueekPredict.sol#349-360):\n[medium/medium] reentrancy-no-eth at src/SqueekPredict.sol:306: Reentrancy in SqueekPredict.settleSqueek(uint256) (src/SqueekPredict.sol#306-321):\n[medium/medium] reentrancy-no-eth at src/SqueekPredict.sol:325: Reentrancy in SqueekPredict.settlePrice(uint256,uint80) (src/SqueekPredict.sol#325-346):\n[medium/medium] reentrancy-no-eth at src/SqueekPredict.sol:456: Reentrancy in SqueekPredict._maintain() (src/SqueekPredict.sol#456-469):\n[medium/medium] uninitialized-local at src/SqueekPredict.sol:310: SqueekPredict.settleSqueek(uint256).metric (src/SqueekPredict.sol#310) is a local variable never initialized\n[medium/medium] uninitialized-local at src/SqueekMetrics.sol:102: SqueekMetrics.measure(SqueekMetrics.Metric,uint64,uint64).morning (src/SqueekMetrics.sol#102) is a local variable never initialized\n[medium/medium] uninitialized-local at src/SqueekMetrics.sol:103: SqueekMetrics.measure(SqueekMetrics.Metric,uint64,uint64).evening (src/SqueekMetrics.sol#103) is a local variable never initialized\n[medium/medium] uninitialized-local at src/SqueekMetrics.sol:101: SqueekMetrics.measure(SqueekMetrics.Metric,uint64,uint64).longest (src/SqueekMetrics.sol#101) is a local variable never initialized\n[medium/medium] uninitialized-local at src/SqueekMetrics.sol:104: SqueekMetrics.measure(SqueekMetrics.Metric,uint64,uint64).active (src/SqueekMetrics.sol#104) is a local variable never initialized\n[medium/medium] uninitialized-local at src/SqueekMetrics.sol:100: SqueekMetrics.measure(SqueekMetrics.Metric,uint64,uint64).streak (src/SqueekMetrics.sol#100) is a local variable never initialized\n[medium/medium] uninitialized-local at src/SqueekMetrics.sol:98: SqueekMetrics.measure(SqueekMetrics.Metric,uint64,uint64).previousRound (src/SqueekMetrics.sol#98) is a local variable never initialized\n[medium/medium] unused-return at src/SqueekPredict.sol:257: SqueekPredict.checkUpkeep(bytes) (src/SqueekPredict.sol#257-273) ignores return value by () = this.previewOpen(id) (src/SqueekPredict.sol#267-269)\n[low/medium] calls-loop at src/SqueekPredict.sol:447: SqueekPredict._tryOpen(uint256) (src/SqueekPredict.sol#447-452) has external calls inside a loop: this.activate{gas: 300_000}(id) (src/SqueekPredict.sol#448-451)\n[low/medium] calls-loop at src/SqueekMetrics.sol:76: SqueekMetrics.measure(SqueekMetrics.Metric,uint64,uint64) (src/SqueekMetrics.sol#76-136) has external calls inside a loop: page = core.roundPage(cursor,throughRound,16) (src/SqueekMetrics.sol#107)\n[low/medium] calls-loop at src/SqueekPredict.sol:447: SqueekPredict._tryOpen(uint256) (src/SqueekPredict.sol#447-452) has external calls inside a loop: this.activate{gas: 300_000}(id) (src/SqueekPredict.sol#448-451)\n[low/medium] calls-loop at src/SqueekPredict.sol:257: SqueekPredict.checkUpkeep(bytes) (src/SqueekPredict.sol#257-273) has external calls inside a loop: () = this.previewOpen(id) (src/SqueekPredict.sol#267-269)\n[low/medium] reentrancy-benign at src/SqueekPredict.sol:325: Reentrancy in SqueekPredict.settlePrice(uint256,uint80) (src/SqueekPredict.sol#325-346):\n[low/medium] reentrancy-benign at src/SqueekPredict.sol:306: Reentrancy in SqueekPredict.settleSqueek(uint256) (src/SqueekPredict.sol#306-321):\n[low/medium] reentrancy-benign at src/SqueekPredict.sol:362: Reentrancy in SqueekPredict.expire(uint256) (src/SqueekPredict.sol#362-365):\n[low/medium] reentrancy-benign at src/SqueekPredict.sol:374: Reentrancy in SqueekPredict.claim(uint256,address) (src/SqueekPredict.sol#374-383):\n[low/medium] reentrancy-benign at src/SqueekPredict.sol:349: Reentrancy in SqueekPredict.settleNative(uint256,bytes,bytes) (src/SqueekPredict.sol#349-360):\n[low/medium] timestamp at src/SqueekPredict.sol:456: SqueekPredict._maintain() (src/SqueekPredict.sol#456-469) uses timestamp for comparisons\n[low/medium] timestamp at src/SqueekPredict.sol:401: SqueekPredict._opening(uint256) (src/SqueekPredict.sol#401-431) uses timestamp for comparisons\n[low/medium] timestamp at src/SqueekPredict.sol:275: SqueekPredict.bet(uint256,uint8) (src/SqueekPredict.sol#275-304) uses timestamp for comparisons\n[low/medium] timestamp at src/libraries/BlockHeader.sol:21: BlockHeader.boundary(bytes,bytes,uint256) (src/libraries/BlockHeader.sol#21-36) uses timestamp for comparisons\n[low/medium] timestamp at src/SqueekPredict.sol:585: SqueekPredict._validatePrice(uint80,int256,uint256,uint256,uint80) (src/SqueekPredict.sol#585-595) uses timestamp for comparisons\n[low/medium] timestamp at src/SqueekPredict.sol:257: SqueekPredict.checkUpkeep(bytes) (src/SqueekPredict.sol#257-273) uses timestamp for comparisons\n[low/medium] timestamp at src/SqueekMetrics.sol:65: SqueekMetrics._requireLive(uint64) (src/SqueekMetrics.sol#65-72) uses timestamp for comparisons\n[low/medium] timestamp at src/SqueekPredict.sol:497: SqueekPredict._beginResolution(uint256,bool) (src/SqueekPredict.sol#497-509) uses timestamp for comparisons","passed":true},{"durationMs":504,"exitCode":0,"name":"aderyn","output":"[high] reentrancy-state-change at src/SqueekPredict.sol:283: Reentrancy: State change after external call\n[low] costly-loop at src/SqueekPredict.sol:457: Costly operations inside loop (2 places)\n[low] internal-function-used-once at src/libraries/BlockHeader.sol:39: Internal Function Used Only Once\n[low] large-numeric-literal at src/SqueekMetrics.sol:26: Large Numeric Literal (4 places)\n[low] literal-instead-of-constant at src/SqueekMetrics.sol:44: Literal Instead of Constant (40 places)\n[low] require-revert-in-loop at src/SqueekMetrics.sol:106: Loop Contains `require`/`revert` (5 places)\n[low] unchecked-return at src/SqueekPredict.sol:139: Unchecked Return\n[low] uninitialized-local-variable at src/SqueekMetrics.sol:104: Uninitialized Local Variable (9 places)","passed":true},{"durationMs":6226,"exitCode":0,"name":"proof ba9038344265","output":"Compiling 30 files with Solc 0.8.26\nSolc 0.8.26 finished in 5.73s\nCompiler run successful!\n\nRan 1 test for test/imd-proof-248eeeac/Proof_ba9038344265.t.sol:ReturnBombSettlementTest\n[PASS] testSettlementGasStaysBoundedWhenPushedRecipientsRevert() (gas: 16356047)\nLogs:\n  settleSqueek gas with 20 reverting recipients: 1947342\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.82ms (2.58ms CPU time)\n\nRan 1 test suite in 3.56ms (2.82ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true},{"durationMs":5541,"exitCode":0,"name":"proof e5619d400895","output":"2026-11-06T12:53:07.854175Z ERROR foundry_compilers_artifacts_solc::sources: error=\"/tmp/imd-verify-oAnQ2H/repo/test/imd-proof-248eeeac/Proof_ba9038344265.t.sol\": No such file or directory (os error 2)\nCompiling 1 files with Solc 0.8.26\nSolc 0.8.26 finished in 5.05s\nCompiler run successful!\n\nRan 1 test for test/imd-proof-236ba38c/Proof_e5619d400895.t.sol:LateBetDecidedTest\n[PASS] testNoStakeAcceptedAfterCertifiedLedgerDecidedTheMarket() (gas: 6687475)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 957.68µs (699.14µs CPU time)\n\nRan 1 test suite in 2.67ms (957.68µs CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true},{"durationMs":5890,"exitCode":0,"name":"proof 266da2ad5a80","output":"2026-11-06T12:53:13.499394Z ERROR foundry_compilers_artifacts_solc::sources: error=\"/tmp/imd-verify-oAnQ2H/repo/test/imd-proof-236ba38c/Proof_e5619d400895.t.sol\": No such file or directory (os error 2)\nCompiling 1 files with Solc 0.8.26\nSolc 0.8.26 finished in 5.28s\nCompiler run successful!\n\nRan 1 test for test/imd-proof-34fe0699/Proof_266da2ad5a80.t.sol:BettableCountTest\n[PASS] testAtLeastTwelveBettableMarketsWithHealthySources() (gas: 42840116)\nLogs:\n  minimum bettable markets over 48h: 14\n  first hour at that minimum: 0\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 44.50ms (42.85ms CPU time)\n\nRan 1 test suite in 45.23ms (44.50ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"f05a174cee06025e3911caf5d07f63fb667393f2fa3b33a13eb7e8f7a62f5749","verifiedTreeHash":"605a57af303fe4d34ae41eb9cc03cd89c4184906","verifierVersion":"0.1.0+18543036"}]}