{"assessments":[],"deployments":[],"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":"7f951ad8-ac62-41f0-b968-79737c96623e","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"85abc7673be1712995a81cb21fa78214e598a910ab92bcc0db1db39df85829e1","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"d93946569d90b0ad3a0e74e661e9a57ec8099776a6368f8567f5de091902659b","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"e93f62670640461dbb34a7ea38d798a7715d4a0de99b82f5ae6f2bc8a1990651","dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"a5b3ec7dd26a6479d6f2ad187083ef942256214029a1aa412b4a61c8fd8f2979","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"eec2d06dd79b6ae71580f143a9c3bb5ed754efd3f7ad9e07e2b559e93ced371f","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"25eb2bfbb9b8aa9ceac213211c09045fe26c2b171a9346e492b978bcfe5f670b","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"d59561da3b378461b7c749f6bd5084477d44993aa94972ddea90f542535751df","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"d59561da3b378461b7c749f6bd5084477d44993aa94972ddea90f542535751df","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"c7e7b6f2b30961e441503690d7121875bce7cdb71ea7a2fe64cdb4624b0d3e7b","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"b9695d268509b8316f3484291b3e3a13aaec33b5829014eb4af4017e0a25acdb","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"4c76766b00591dfe982fe85d3de589fdb2f810e5137c301155d1c5e36a1d3b23","skillId":"write-foundry-tests","tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"4c76766b00591dfe982fe85d3de589fdb2f810e5137c301155d1c5e36a1d3b23","skillId":"write-foundry-tests","state":"accepted"}],"objective":"What to build: two immutable contracts for The Hooks Store on Robinhood Chain (a second launch with chainId 1 deploys the same contracts on Ethereum), HookListingRegistry (a free on-chain listing registry) and FeaturedPlacementsV2 (a paid, refundable 24-hour featured-placement escrow in IMD). Upgrades: none. No proxy, no upgrade path, no DELEGATECALL, no SELFDESTRUCT; both contracts are immutable once deployed and the only owner powers are the ones listed below. Start from the audited source at https://github.com/identity-md-launches/launch-1811-build-technical-foundation-hooks/tree/46e17ea70534e8a7ac2c4ff78501a3c183d32da8/src/store (HookListingRegistry.sol unchanged; FeaturedPlacements.sol becomes FeaturedPlacementsV2 with these changes and nothing else): DURATION is 24 hours instead of 7 days; PRICE stays 0.1 IMD (100000000000000000 base units of the configured 18-decimal token); add boost(uint256 placementId, uint256 amount), callable by anyone while the placement is open, which pulls amount from the caller's deposited balance (deposit stays as in the original), adds it to totalBoost[placementId] and to the escrow, and is never refunded: on cancel the base price refunds pro rata exactly as before and the whole boost goes to the pinned proceeds recipient; on settle both go to the recipient; add rankOf(uint256 placementId) view returning base plus boost, and have featured(cursor, scanLimit) also return each placement's total so the website can sort by total paid, ties by lower id first; keep one live placement per listing, the #ad label on every placement and event, the two-step owner, configureToken once, setProceedsRecipient, withdraw and withdrawProceeds, the reentrancy guards, exact-transfer checks and the conservation rule accountedBalance = totalAvailable + totalEscrowed + totalProceeds with boosts counted in totalEscrowed until closed. Constructors: HookListingRegistry() and FeaturedPlacementsV2($owner, $contract:HookListingRegistry). Owner functions stay owner-only with $owner as the owner; no msg.sender ownership. Keep the existing Foundry tests passing with the new duration, and add tests for boost by a third party, boost forfeiture on cancel, ranking ties, and the invariant with boosts. No token, pool or liquidity is created. Dependencies: only the IMD token on Robinhood Chain (0x5f7bb59365ce557c26dbcaa4ee9d39a4b95b7127), set after deployment by the owner through configureToken, never a constructor dependency. Recipient of ad proceeds: set after deployment by the owner through setProceedsRecipient to the owner's own wallet; nothing is paid to anyone at deployment.","parentJobId":null,"planHash":"5e6ab266024c71a6cf2209dd06104a1a2a9bb71aaa9cc720ca681818e9ca575d","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"7f951ad8-ac62-41f0-b968-79737c96623e","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-2454-what-build-two-immutable"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"52706","feedbackHash":"712302c071001b94c7be9e2ecb55761537c93cf07af2a82de569063de11500e6","nodeKey":"audit_economics","submissionHash":"85abc7673be1712995a81cb21fa78214e598a910ab92bcc0db1db39df85829e1","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52478","feedbackHash":"bb3f8cfba97cf9ba9bae5fca1a5cdf811ddec3bc884e079a1da0ee4cb61a5aa4","nodeKey":"audit_flow","submissionHash":"d93946569d90b0ad3a0e74e661e9a57ec8099776a6368f8567f5de091902659b","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50995","feedbackHash":"d9703d0d11e5b4aab0b0e9c5879f3babde506ec926f6d4d0f379a9b98b22f528","nodeKey":"audit_judge","submissionHash":"e93f62670640461dbb34a7ea38d798a7715d4a0de99b82f5ae6f2bc8a1990651","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52609","feedbackHash":"8ec76c0dc07b6c4288cbe9c837c050733acb88f6715bc685b92ca646ca83a8e1","nodeKey":"audit_math","submissionHash":"a5b3ec7dd26a6479d6f2ad187083ef942256214029a1aa412b4a61c8fd8f2979","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51435","feedbackHash":"7908e164fb877251851c1124ea28ab965a8c0740f132b852609171c52b3d592e","nodeKey":"audit_permissions","submissionHash":"eec2d06dd79b6ae71580f143a9c3bb5ed754efd3f7ad9e07e2b559e93ced371f","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51180","feedbackHash":"cf8b9294c2683e31589897064f5c50ca3ec44c498698174d40ae9e55a4a6179e","nodeKey":"build_contract_project","submissionHash":"25eb2bfbb9b8aa9ceac213211c09045fe26c2b171a9346e492b978bcfe5f670b","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51606","feedbackHash":"d0d6a2bae5cf32746ad16c61d961934a42111a071a0d0a5aff79fbc992fc0a83","nodeKey":"manifest","submissionHash":"c7e7b6f2b30961e441503690d7121875bce7cdb71ea7a2fe64cdb4624b0d3e7b","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"50963","feedbackHash":"7860050fa8f8e663d0f0f2bde72a6e58b9e18a302b11d235fb0a8e9f1ce1deee","nodeKey":"write_foundry_tests","submissionHash":"b9695d268509b8316f3484291b3e3a13aaec33b5829014eb4af4017e0a25acdb","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"aef6ac042ff3e96411f737de2d2a9fd26a1684e5595ac177c32792c906a43ebf","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":"63d2f6e4189642589d95087afcff66f0f3efc7bc183f487dffc064174a8b63f6","device":"fef70a876212b4f0","findings":[],"hash":"25eb2bfbb9b8aa9ceac213211c09045fe26c2b171a9346e492b978bcfe5f670b","nodeId":"dde5e032-2701-4a92-9f9c-eab82038bc22","outcome":"completed","summary":"Implemented both contracts, preserving `HookListingRegistry` unchanged. V2 includes 24-hour placements, nonrefundable boosts, ranking totals, and boost-aware escrow accounting.\n\nValidation passed: `forge build`, all 61 tests with the specified fuzz seed, and `forge fmt --check`.\n\nDependencies are vendored. Deployment parameters and owner setup responsibilities are documented in [README.md](/home/imd/.identitymd/work/7f951ad8-ac62-41f0-b968-79737c96623e/dde5e032-2701-4a92-9f9c-eab82038bc22/README.md).","treeHash":"ed055a4858ffe029665f2b1955bbedc4d265e397","usage":{"cachedInputTokens":985344,"inputTokens":96931,"model":"gpt-6-astra","outputTokens":19345,"runtime":"codex","turns":5,"wallClockMs":451947}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"f3b611cf8afb513f","findings":[{"citation":"resolved","description":"Economic-incentive note (Economic Security guide: 'legitimate features turned against the protocol'; Flow Gap seam 3: execution x first principles). The brief specifies that boosts are never refunded and that on cancel the whole boost goes to the pinned proceeds recipient, and the code implements exactly that. The economic consequence is that a booster buys 24 hours of ranking but has no protection at all against the payer (or the current listing owner) cancelling: the base price refunds pro rata to the payer (in full when elapsed == 0), the boost goes entirely to the recipient, and the ad leaves the feed immediately. The payer bears no cost for doing this and can re-purchase the same listing in the same block, so a booster's funds can be moved to the store owner's wallet with zero seconds of service. Because the proceeds recipient is the store owner's own wallet, a listing owned by the same party that controls the recipient can collect third-party boosts and immediately cancel at a net gain of the entire boost; for any other listing owner the action is pure griefing of the booster with the value accruing to the owner. This is spec-conformant, so it is reported as a design tradeoff for the author to confirm, not as a code error. Fix options require a scope decision: (a) on cancel, refund boosts pro rata to each booster (needs per-booster accounting); (b) forbid cancel once totalBoost[id] > 0; or (c) document prominently in the website that boosting carries this risk. Any of (a) or (b) changes the agreed economics.","line":201,"path":"src/store/FeaturedPlacementsV2.sol","reproduction":"Token configured, recipient = treasury. Alice creates listing 1, deposits 0.1 IMD, calls purchase(1, 1, treasury, now) -> placement 1. Bob deposits 5 IMD and calls boost(1, 5e18). In the same block Alice calls cancel(1). Expected (from a booster's perspective): some exposure for the 5 IMD, or a refund. Actual: cancel returns 0.1e18 (elapsed = 0 so earned = 0), available[alice] = 0.1e18, available[bob] = 0, proceeds[treasury] = 5e18, placement.closedAt == placement.startsAt, and Alice can immediately call purchase(1, 1, treasury, now) again, receiving placement 2 with rankOf = 0.1e18. Verified with a scratch Foundry test (test/scratch/EconomicsScratch.t.sol, testImmediateCancelForfeitsThirdPartyBoostWithZeroFeaturedTime, removed after the run).","severity":"low","snippet":"        uint256 earned = PRICE * (block.timestamp - placement.startsAt) / DURATION;\n        refund = PRICE - earned;\n        earned += totalBoost[id];","title":"Payer can cancel in the same block as a third-party boost, forfeiting the whole boost with zero featured time while recovering the full base price"},{"citation":"resolved","description":"Cheapest griefing vector on the read path (Economic Security guide: 'find the cheapest griefing vector that blocks other users'). A cancelled placement never blocks a new purchase, and a cancel at elapsed == 0 refunds the full PRICE (earned = PRICE * 0 / DURATION = 0). A contract holding a single 0.1 IMD deposit can therefore loop purchase -> cancel in one transaction, minting one dead placement id per iteration without spending any IMD. featured() scans ids ascending from a cursor with MAX_SCAN = 100 and returns only active ones, so the website's ranking query cost is linear in placementCount forever: after 1,000,000 dead ids the frontend needs 10,000 featured() calls per page render to find the handful of live ads. Measured cost is about 308,000 gas per dead id (~0.3e12 gas per million ids), which on a low-fee L2 is a feasible nuisance budget. No funds are lost and the conservation rule holds throughout; this is a scalability/griefing concern inherited from the original design (the original contract has the same shape), and the registry's createListing has an analogous free-listing spam surface. Possible mitigations, all design changes: charge a minimum non-refundable fee on cancel (e.g. earned = max(1 second's share, pro rata)), rate-limit purchases per listing per block, or maintain an on-chain index of live placements so the feed does not depend on scanning history.","line":162,"path":"src/store/FeaturedPlacementsV2.sol","reproduction":"Token configured, recipient set. Deploy a helper contract that creates a listing, deposits 0.1 IMD, then in one call runs 200 iterations of { id = purchase(listingId, 1, proceedsRecipient, block.timestamp); cancel(id); }. Expected: a cost that bounds spam. Actual: placementCount == 200, available[helper] == 0.1e18 (unchanged), totalProceeds == 0, and featured(1, 100) returns an empty page with nextCursor == 101, so a reader must page through all 200 ids. Measured ~308,259 gas per iteration. Verified with a scratch Foundry test (test/scratch/EconomicsScratch.t.sol, testGasOnlyChurnInflatesPlacementCountWithoutLosingFunds, removed after the run).","severity":"low","snippet":"        if (previous.status == Status.Open && block.timestamp < previous.endsAt) revert AlreadyFeatured();","title":"Same-block purchase/cancel loop inflates placementCount at gas cost only, growing the id range the website must scan to rank featured ads"}],"hash":"85abc7673be1712995a81cb21fa78214e598a910ab92bcc0db1db39df85829e1","nodeId":"747d47e6-1f5d-4fa2-bf76-1154b07b2a8e","outcome":"completed","summary":"Review complete. The findings file is written and the scratch test is removed; the tree is otherwise untouched.\n\n**Outcome:** no defect in the economics of either contract. The conservation rule, the escrow of boosts until closure, pro-rata base refunds, boost forfeiture to the pinned recipient, settlement, recipient pinning, and view/write agreement at the expiry boundary all hold on every path I traced. All 61 existing tests pass, including the invariant run seeded with third-party boosts.\n\n**Two low findings recorded in `.imd-findings.json`, both reproduced with a scratch Foundry test:**\n\n- **Immediate cancel forfeits a third-party boost with zero featured time.** Alice buys placement 1, Bob boosts it with 5 IMD, Alice cancels in the same block. Alice gets her full 0.1 IMD back, Bob's 5 IMD goes to the proceeds recipient, and Alice can re-purchase at once. This is exactly what the brief specifies, so it is reported as a design tradeoff for the author to confirm rather than a code error. Any mitigation changes the agreed economics.\n- **Gas-only purchase/cancel loop inflates the placement id space.** A cancel at elapsed zero refunds in full and never blocks the next purchase. A helper contract created 200 dead ids for about 308k gas each without spending any IMD. The website's featured scan is linear in placement count forever, so this is a read-path griefing vector inherited from the original design. No funds are at risk.\n\n**Coverage:** all 18 entry points have a row, plus three invariant rows. Static-analysis leads were checked: the strict-equality and timestamp warnings are the intended status and time-term checks, the unused balance return in configureToken is a probe, and the aderyn reentrancy flag on purchase refers to a view call into the immutable registry inside the guard.\n\n**Noted but not filed as findings:** the proceeds recipient can boost listings at zero net cost since boosts return as proceeds, and tokens sent directly to the contract are unrecoverable. Both are admin or self-affecting by design.","treeHash":null,"usage":{"cachedInputTokens":675829,"inputTokens":258,"model":"claude-fable-5-1","outputTokens":20748,"runtime":"claude","turns":27,"wallClockMs":320309}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"8e25456c4ae69acf","findings":[{"citation":"resolved","description":"Seam between precision and invariant (a view leaves out a term that the write path applies). cancel() refunds `refund = PRICE - floor(PRICE * elapsed / DURATION)` to the payer and stores it in placement.refund, but rankOf() always returns PRICE + totalBoost for every non-None status. For a Cancelled placement, the amount actually paid is (PRICE - refund) + totalBoost, so the NatSpec claim 'Total paid ... Retained after closure' is false by up to PRICE (0.1 IMD). The brief literally asks for 'base plus boost', and featured() only returns live placements, so the website's sort is unaffected. The risk is to off-chain consumers that trust the documented 'Total paid' meaning for closed placements, for example historical leaderboards or earnings reconciliation. The existing test testImmediateCancelRefundsOnlyBaseAndForfeitsAllBoosts asserts the current value (5*PRICE/2), so this is intended. The fix is documentation only: say it is the nominal base plus boosts, or return PRICE - placement.refund + totalBoost for Cancelled placements. Changing the return value would also need the test and the brief's wording updated.","line":249,"path":"src/store/FeaturedPlacementsV2.sol","reproduction":"Fixture: alice owns listing 1 and has deposited PRICE (1e17); bob has deposited 5e16. (1) alice purchase(1, rev, treasury, now) -> id 1. (2) bob boost(1, 5e16). (3) In the same block, alice cancel(1) -> refund = 1e17 (elapsed 0, earned base 0). proceeds[treasury] = 5e16 and available[alice] = 1e17. Expected under the NatSpec 'Total paid': rankOf(1) == 5e16. Actual: rankOf(1) == 1.5e17, which overstates the amount paid by the full refunded 1e17.","severity":"info","snippet":"    /// @notice Total paid (base plus boosts), not an ordinal position. Retained after closure.\n    function rankOf(uint256 placementId) public view returns (uint256) {\n        if (_placements[placementId].status == Status.None) revert UnknownPlacement();\n        return PRICE + totalBoost[placementId];","title":"rankOf documents 'Total paid ... Retained after closure' but reports the full base price for cancelled placements whose base was refunded"}],"hash":"a5b3ec7dd26a6479d6f2ad187083ef942256214029a1aa412b4a61c8fd8f2979","nodeId":"ef445adf-20a3-4345-af5d-79f27f9a13a0","outcome":"completed","summary":"I found no math defect worth fixing in the boost and placement contracts. The only finding is info-level: `rankOf` reports a placement's total differently from what its comment promises. The full test suite passes (`forge test`: 61 passed, 0 failed), and no repository files were changed.\n\n**Scope:** I compared `FeaturedPlacementsV2` line by line against the original contract at the specified commit. The changes match the brief and add nothing else: the 24-hour duration, `boost`, boost handling on cancel and settle, `rankOf`, totals in `featured`, and the `#ad` label on events. The other three contract files are unchanged.\n\n**Math precision:**\n- **Refund rounding:** the fee kept on cancel is `0.1 IMD × elapsed / 86,400`, rounded down. Elapsed time is always under 86,400 seconds, so it can't overflow, and refund plus kept fee always equals exactly 0.1 IMD. Rounding favours the payer by under 1 wei per cancel, as in the original.\n- **Boost amounts:** there are no casts to smaller types and no division in the boost logic. Base price plus boosts can't overflow because it never exceeds the tokens the contract actually holds. Token decimals are checked to be exactly 18 when the token is configured.\n\n**Boundaries:**\n- **Expiry:** a placement can be boosted or cancelled only before its end time, and settled only at or after it, so there's no gap or overlap at expiry. The purchase deadline includes its last second.\n- **Bad inputs:** zero amounts, placement id 0 and unknown ids are all rejected.\n- **External calls:** the registry lookup during purchase goes to a fixed, read-only contract. Token transfers check the exact balance change on both sides and are protected against reentrancy.\n\n**Conservation and ranking:**\n- **Balance rule:** every function moves value between available, escrowed and earned without creating or losing any, or makes an exact-checked transfer in or out. Boosts stay in escrow until the placement closes.\n- **Ranking:** `featured` keeps each total paired with its placement after filtering, and returns placements in ascending id order, which gives the lower-id-first tie-break.\n\n**Finding 1 (info), `src/store/FeaturedPlacementsV2.sol:249`:** `rankOf` is documented as \"Total paid … Retained after closure\", but for a cancelled placement it still adds the full 0.1 IMD base, even though some or all of it was refunded.\n- **Reproduction:** buy a placement, boost it with 0.05 IMD, then cancel in the same block. The payer gets the full 0.1 IMD back and the recipient earns 0.05 IMD, but `rankOf` returns 0.15 IMD.\n- **Impact:** the website's sort isn't affected because `featured` only returns live placements. Only off-chain readers relying on the \"total paid\" wording would be misled.\n- **Why it's only info:** the brief literally asks for \"base plus boost\", and an existing test asserts this value. The simplest fix is to correct the comment.\n\n**Static-analysis leads, all rejected:**\n- The \"strict equality\" warnings are ordinary status comparisons.\n- The \"state change after external call\" warning in `purchase` is a read-only call to the fixed registry, inside the reentrancy guard.\n- The \"ignored return value\" in `configureToken` only checks that the token answers a balance query correctly.\n- The timestamp warnings are the intended 24-hour timing rules.\n\n**Coverage:** the coverage record answers all 18 entry points (17 hold, none left unchecked), plus rows for the balance rule, the `featured` totals and `rankOf`. I didn't write a proof test, since one is only required for critical or high findings.\n\nEverything is in `.imd-findings.json` in the repository root.","treeHash":null,"usage":{"cachedInputTokens":590333,"inputTokens":22,"model":"claude-opus-5-5","outputTokens":11742,"runtime":"claude","turns":15,"wallClockMs":141799}},{"artifacts":[],"attempt":1,"bundleHash":"4d15055b007f3b38a222badb7557d580ed72de83fc63d14dcbec8a95cb19f578","device":"bfffb5246a55ee5d","findings":[],"hash":"b9695d268509b8316f3484291b3e3a13aaec33b5829014eb4af4017e0a25acdb","nodeId":"c6d89fd4-fde6-49d6-8596-dbd6691a6aeb","outcome":"completed","summary":"Added 17 tests covering failure paths, arithmetic and metadata boundaries, pagination, and 24,576 randomized escrow calls.\n\n`forge build` and `forge test` pass with the required seed: **78 passed, 0 failed, 0 skipped**.\n\nOnly `test/` changed. No reproducible contract defects found. Live IMD integration remains unverified.","treeHash":"7643afb4a7b24e1b0fda5742fc95ef96a0241a30","usage":{"cachedInputTokens":1794176,"inputTokens":117124,"model":"gpt-6-astra","outputTokens":20809,"runtime":"codex","turns":7,"wallClockMs":515696}},{"artifacts":[],"attempt":1,"bundleHash":"40f6984d32c0dd622d32fdfe30a894a77858b8fff4de5263ac7b1821470bbeff","device":"a12c1a6f9430f757","findings":[],"hash":"c7e7b6f2b30961e441503690d7121875bce7cdb71ea7a2fe64cdb4624b0d3e7b","nodeId":"091bf24c-5106-4856-acde-41faae753644","outcome":"completed","summary":"Created [launch.json](/home/imd-worker/.identitymd/work/7f951ad8-ac62-41f0-b968-79737c96623e/091bf24c-5106-4856-acde-41faae753644/launch.json) with both contracts in dependency order and verified constructor arguments.\n\nValidation passed: manifest checks, offline `forge build`, and all 61 tests using the supplied fuzz seed. Only `launch.json` is changed for submission.","treeHash":"5780c2dfead148b662dea6ecbae64e0fd638bd58","usage":{"cachedInputTokens":369024,"inputTokens":31157,"model":"gpt-6-astra","outputTokens":3838,"runtime":"codex","turns":4,"wallClockMs":129599}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"2bdd77125ec75e6c","findings":[{"citation":"resolved","description":"Execution-trace / first-principles finding. cancel() charges floor(PRICE * elapsed / DURATION); when purchase() and cancel() land in the same block, elapsed is 0, earned is 0 and the full PRICE is credited back to available[payer]. The only state that does not roll back is placementCount (incremented in purchase at line 167), the new Placement record, latestPlacement and the emitted PlacementPurchased/PlacementCancelled events. Nothing in the contract bounds how often this can repeat: the same 0.1 IMD deposit can be recycled indefinitely and the attacker pays gas only. featured(cursor, scanLimit) (lines 258-282) is the only on-chain feed and is a dense ascending-id scan capped at MAX_SCAN = 100 ids per call, so every 100 dead ids cost the website one extra eth_call per feed render, forever, and the live ads of every other advertiser are pushed to later pages. Real live ads are not lost and no funds move to the wrong party, hence low. This is inherited from the pinned FeaturedPlacements design (pro-rata refund is a stated requirement), so a fix is a scope decision: e.g. a minimum non-refundable charge (one second of DURATION) or a minimum elapsed time before cancel, or the website must build its live set from PlacementPurchased/Cancelled/Settled events rather than scanning featured().","line":201,"path":"src/store/FeaturedPlacementsV2.sol","reproduction":"Setup: token configured, recipient set, alice owns listing L and has deposited exactly PRICE (0.1 IMD). In one block repeat N times: vm.prank(alice); id = ads.purchase(L, 1, recipient, block.timestamp); vm.prank(alice); refund = ads.cancel(id). Expected (if churn were bounded): some cost or limit. Actual, verified in test/scratch/ReviewRepro.t.sol testFreeIdChurn with N = 1000: every refund == PRICE, ads.available(alice) == PRICE after all cycles, ads.totalProceeds() == 0, ads.placementCount() == 1000, and walking featured(cursor,100) from cursor 1 until nextCursor > placementCount takes 10 calls that all return empty pages. Measured cost about 460k gas per purchase+cancel pair inside the test harness, zero IMD. At 1,000,000 cycles the feed needs 10,000 eth_calls to render.","severity":"low","snippet":"        uint256 earned = PRICE * (block.timestamp - placement.startsAt) / DURATION;\n        refund = PRICE - earned;","title":"Same-block purchase+cancel is free, so one listing owner can mint unbounded placement ids and degrade the featured() scan"},{"citation":"resolved","description":"Trust-assumption note, not a permission bypass. boost() accepts any caller with an available balance, and every boost is credited on cancel/settle to the placement's pinned recipient (lines 203/210 and 221/223). When the caller is that recipient (per the brief, the owner's own wallet), the boost round-trips back to them: the only cost is gas and a lock of at most 24 hours. rankOf() and featured() totals therefore cannot be read by third parties as 'IMD actually paid by advertisers': the operator can place any listing at the top of the ranking, or keep the recorded historical total of any closed placement inflated, for free. Since the same party already controls the website ordering, the practical impact is limited to anyone treating the on-chain totals as an independent signal. Recorded so the judge and author can decide whether to document it (README does not mention it) or to reject boosts from placement.recipient.","line":180,"path":"src/store/FeaturedPlacementsV2.sol","reproduction":"Setup: recipient T set via setProceedsRecipient(T); alice owns listing L, deposits PRICE and purchases placement P (recipient pinned to T). T deposits X = 1000 IMD and calls boost(P, X). rankOf(P) == PRICE + X. Warp 24 hours, anyone calls settle(P): proceeds[T] == PRICE + X. T calls withdrawProceeds(PRICE + X, T): token.balanceOf(T) == PRICE + X, i.e. X returned in full, and rankOf(P) still reports PRICE + X. Verified in test/scratch/ReviewRepro.t.sol testRecipientBoostsForFree. The same holds if alice cancels early: the whole X is credited to T on line 210.","severity":"info","snippet":"    function boost(uint256 placementId, uint256 amount) external nonReentrant {\n        Placement storage placement = _openPlacement(placementId);","title":"Pinned proceeds recipient can boost any placement at zero net cost, so 'total paid' rank is operator-manipulable"}],"hash":"d93946569d90b0ad3a0e74e661e9a57ec8099776a6368f8567f5de091902659b","nodeId":"1b735bd5-c050-4781-b68a-c37ff064279e","outcome":"completed","summary":"The review is complete and `.imd-findings.json` is written with 2 findings and 21 coverage rows, all 18 listed entry points answered.\n\n**Outcome.** No critical, high or medium defect in the control flow. The V2 delta is exactly the brief: a diff against the pinned upstream commit shows only the rename, the 24-hour duration, boost and its accounting, rankOf, featured totals, and the #ad label on the three lifecycle events. The three untouched files hash-match upstream. All 61 existing tests pass with the README seed, and the upstream test files differ only by rename and duration constants.\n\n**What I traced and confirmed holds.** Every custody path keeps accountedBalance equal to available plus escrowed plus proceeds, with boosts in escrow until closure. Cancel refunds the original payer pro rata and sends the whole boost to the pinned recipient. Settle is permissionless and transfer-free, so a hostile recipient cannot block it. The reentrancy guard covers all seven custody entry points, including boost. Owner powers are limited to the two-step handover, one-shot configureToken and setProceedsRecipient, and the constructor makes no external call. I also probed the IMD token the brief names on Robinhood Chain: it has code, 18 decimals, symbol IMD, and no EIP-1967 proxy slots, so the irreversible configureToken and the exact-transfer checks should accept it.\n\n**Findings reported.**\n- **Low:** a purchase and cancel in the same block costs nothing, so one listing owner with 0.1 IMD can mint unlimited placement ids. The featured feed is a dense 100-id scan, so each 100 dead ids cost the website one extra call forever. Verified with 1,000 cycles leaving the payer's balance intact and 10 empty pages. The fix needs a scope decision because pro-rata refunds are a stated requirement.\n- **Info:** the pinned proceeds recipient can boost any placement and get the boost back on settle or cancel, so on-chain \"total paid\" totals are operator-manipulable at zero cost. Recorded as a trust assumption, not a bypass.\n\nNeither finding carries a proof file, since both are below high severity. The scratch test under `test/scratch/` that verified the numbers is not part of the submission.","treeHash":null,"usage":{"cachedInputTokens":1070084,"inputTokens":290,"model":"claude-fable-5-1","outputTokens":35114,"runtime":"claude","turns":37,"wallClockMs":630745}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"b5e3297a04468fd3","findings":[{"citation":"resolved","description":"Merged from audit_flow (same-block churn) and audit_economics (placementCount inflation): same root cause. cancel() charges floor(PRICE * elapsed / DURATION); with purchase() and cancel() in one block elapsed is 0, earned is 0 and the full PRICE returns to available[payer]. Nothing bounds how often this repeats: placementCount (line 167) grows by one per cycle, no IMD is spent, and a cancelled placement never blocks the next purchase. featured(cursor, scanLimit) is a dense ascending-id scan capped at MAX_SCAN = 100 per call and only returns live placements, so each 100 dead ids cost the website one extra eth_call per render, permanently, and push every other advertiser's live ad onto later pages. No funds move to the wrong party and the conservation rule holds throughout, so this is a read-path griefing/scalability concern rather than a loss. It is inherited from the pinned design (pro-rata refund is a stated requirement), so the fix is a scope decision for the requester: a minimum non-refundable charge on cancel (for example one second's share), a minimum elapsed time before cancel, or an explicit decision that the website builds its live set from PlacementPurchased/PlacementCancelled/PlacementSettled events instead of paging featured(). The registry's free createListing has the same shape but is unchanged upstream code.","line":201,"path":"src/store/FeaturedPlacementsV2.sol","reproduction":"Fixture: token configured, recipient set, alice owns listing L and has deposited exactly PRICE (1e17). In one block repeat 300 times: vm.prank(alice); id = ads.purchase(L, 1, recipient, block.timestamp); vm.prank(alice); refund = ads.cancel(id). Expected if churn were bounded: some IMD cost or a revert. Actual (test/scratch/JudgeRepro.t.sol testFreeIdChurn, passes on this code): every refund == PRICE, ads.available(alice) == PRICE after all cycles, ads.totalProceeds() == 0, ads.placementCount() == 300, and walking featured(cursor, 100) from cursor 1 until nextCursor > placementCount takes 3 calls that all return empty pages. Roughly 300k-450k gas per cycle and zero IMD; at 1,000,000 cycles the feed needs 10,000 featured() calls per render.","severity":"low","snippet":"        uint256 earned = PRICE * (block.timestamp - placement.startsAt) / DURATION;","title":"Same-block purchase+cancel is free in IMD, so one listing owner can mint unbounded dead placement ids that every featured() scan must page through"},{"citation":"resolved","description":"Merged from audit_economics (immediate cancel forfeits third-party boost) and audit_permissions (owner harvests boosts at zero cost): same root cause. Only the payer or the current listing owner may cancel (line 197); boosters have no say. On cancel the base refunds pro rata (full refund at elapsed 0) while 100% of totalBoost[id] goes to the pinned recipient regardless of elapsed time, and the ad leaves the feed immediately. The canceller bears no cost and can re-purchase the same listing in the same block. When the listing owner is also the proceeds recipient (the brief sets the recipient to the owner's own wallet, and the owner may list), the owner collects the boost with the base price returned and the ad live again; for any other listing owner it is pure griefing of the booster, with the value still accruing to the recipient. This is spec-conformant: the brief says boosts are never refunded and go wholly to the recipient on cancel, and README line 117-119 tells boosters to accept it. Reported as a design trust assumption with a concrete reproduction so the requester can decide; options that narrow it change the agreed economics (forbid cancel once totalBoost[id] > 0, forfeit boosts pro rata, or refund boosts to boosters on early cancel).","line":203,"path":"src/store/FeaturedPlacementsV2.sol","reproduction":"Fixture: token configured, setProceedsRecipient(alice); alice creates listing 1, deposits 1e17, purchase(1, 1, alice, START) -> placement 1. bob deposits 10e18 and calls boost(1, 10e18): available[bob] = 0, totalEscrowed = 10.1e18. Same block, alice calls cancel(1). Expected from a booster's perspective: some exposure for the canceller or a refund. Actual (test/scratch/JudgeRepro.t.sol testImmediateCancelForfeitsBoostWithZeroService, passes on this code): refund == 1e17, available[alice] == 1e17, available[bob] == 0, proceeds[alice] == 10e18, placement.closedAt == placement.startsAt, and alice immediately purchases placement 2 which is active with rankOf == 1e17. Conservation holds throughout.","severity":"low","snippet":"        earned += totalBoost[id];","title":"Payer or listing owner can cancel in the same block as a third-party boost: full base refund, whole boost forfeited to the recipient, zero seconds of ranking delivered"},{"citation":"resolved","description":"From audit_permissions, reproduced. purchase() lets the payer pin revision, recipient and a deadline (line 156); boost() pins nothing and its only time check is block.timestamp < endsAt. A boost is non-refundable and its only benefit is ranking for the remaining window, which shrinks every second between signing and inclusion. A booster who signs expecting most of 24 hours can be included at endsAt - 1 and pay in full for one second, with no on-chain way to bound that. The brief does not ask for a boost deadline, so this is an asymmetry/usability gap, not a bypass. A fix that preserves the design is an extra uint256 deadline (or minRemaining) parameter on boost() reverting with DeadlineExpired, mirroring purchase().","line":182,"path":"src/store/FeaturedPlacementsV2.sol","reproduction":"Fixture: token configured, recipient = treasury. alice creates listing 1, deposits 1e17, purchase(1, 1, treasury, START) -> placement 1 with endsAt = START + 86400. bob deposits 1e18. vm.warp(START + 86399); vm.prank(bob); ads.boost(1, 1e18). Expected if boost mirrored purchase: bob could pass a deadline and the late inclusion reverts, leaving available[bob] == 1e18. Actual (test/scratch/JudgeRepro.t.sol testBoostHasNoDeadline, passes on this code): boost succeeds, available[bob] == 0; at START + 86400 isActive(1) is false and settle(1) credits proceeds[treasury] == 1e17 + 1e18. bob paid 1 IMD for one second of ranking.","severity":"low","snippet":"        if (block.timestamp >= placement.endsAt) revert PlacementExpired();","title":"boost() has no deadline or minimum-remaining-time argument, so a delayed boost pays the full non-refundable amount for seconds of ranking"},{"citation":"resolved","description":"From audit_flow, reproduced. boost() accepts any caller with an available balance, and every boost is credited on cancel or settle to the placement's pinned recipient (lines 210 and 223). When the caller is that recipient (per the brief, the owner's own wallet) the boost round-trips back to them; the only cost is gas and a lock of at most 24 hours. rankOf() and featured() totals therefore cannot be read by third parties as IMD actually paid by advertisers: the operator can place any listing at the top of the ranking, or keep a closed placement's recorded total inflated, for free. The same party already controls the website ordering, so practical impact is limited to anyone treating the on-chain totals as an independent signal. Not a permission bypass; recorded so the author can document it (README does not mention it) or reject boosts from placement.recipient.","line":180,"path":"src/store/FeaturedPlacementsV2.sol","reproduction":"Fixture: recipient = treasury; alice owns listing 1, deposits PRICE and purchases placement 1 (recipient pinned to treasury). treasury deposits 1000e18 and calls boost(1, 1000e18): rankOf(1) == PRICE + 1000e18. Warp 24 hours, settle(1): proceeds[treasury] == PRICE + 1000e18. treasury calls withdrawProceeds(PRICE + 1000e18, treasury): token.balanceOf(treasury) == PRICE + 1000e18, i.e. the 1000 IMD returned in full, and rankOf(1) still reports PRICE + 1000e18 (test/scratch/JudgeRepro.t.sol testRecipientBoostsForFree, passes on this code).","severity":"info","snippet":"    function boost(uint256 placementId, uint256 amount) external nonReentrant {","title":"Trust assumption: the pinned proceeds recipient can boost any placement at zero net cost, so on-chain 'total paid' ranks are operator-manipulable"},{"citation":"resolved","description":"From audit_math, reproduced. cancel() refunds PRICE - floor(PRICE * elapsed / DURATION) to the payer and stores it in placement.refund, but rankOf() returns PRICE + totalBoost for every non-None status. For a Cancelled placement the amount actually paid is (PRICE - refund) + totalBoost, so the NatSpec claim 'Total paid' is wrong by up to PRICE. The brief asks for 'base plus boost' and featured() only returns live placements, so website sorting is unaffected and the existing test testImmediateCancelRefundsOnlyBaseAndForfeitsAllBoosts asserts the current value. Only off-chain consumers that trust the documented meaning for closed placements (historical leaderboards, earnings reconciliation) are affected. Documentation fix: say nominal base plus boosts, or return PRICE - placement.refund + totalBoost for Cancelled placements (which would also need the test and brief wording updated).","line":252,"path":"src/store/FeaturedPlacementsV2.sol","reproduction":"Fixture: alice owns listing 1 and deposited 1e17; bob deposited 5e16. alice purchase(1, 1, treasury, now) -> id 1. bob boost(1, 5e16). Same block, alice cancel(1) -> refund == 1e17, proceeds[treasury] == 5e16. Expected under the NatSpec 'Total paid': rankOf(1) == 5e16. Actual (test/scratch/JudgeRepro.t.sol testRankOfCancelledOverstatesPaid, passes on this code): rankOf(1) == 1.5e17.","severity":"info","snippet":"        return PRICE + totalBoost[placementId];","title":"rankOf NatSpec says 'Total paid ... Retained after closure' but a cancelled placement reports the full nominal base even when the base was refunded"}],"hash":"e93f62670640461dbb34a7ea38d798a7715d4a0de99b82f5ae6f2bc8a1990651","nodeId":"aff9c73c-528c-44ca-abeb-8c0706bf6289","outcome":"completed","summary":"Review complete. The findings file is written and validated, and the scratch tests are removed so the tree is clean apart from the required .imd-findings.json.\n\n**Outcome:** no critical, high or medium defects. All seven specialist findings reproduced against the code and merged into five, three low and two info. My own pass found nothing further.\n\n**What I verified**\n- The three unchanged files are byte-identical to the pinned upstream commit, and the FeaturedPlacements to V2 diff contains only the changes the brief asked for.\n- The full suite passes, including the boost, tie-ranking, forfeiture and boosted-invariant tests the brief requested.\n- Runtime sizes are about 9 KB each, constructors make no external calls, and launch.json has only the schema keys with arguments in declaration order.\n- Every entry point has a coverage row, plus three invariant rows (conservation, diff scope, deployability).\n\n**Findings kept, all reproduced in a scratch Foundry test that passed on this code**\n1. **Low, cancel line 201.** Same-block purchase and cancel refunds the full price, so one deposit mints unbounded dead ids that featured() must page through. Merged from audit_flow and audit_economics.\n2. **Low, cancel line 203.** Payer or listing owner can cancel right after a third-party boost: full base refund, whole boost to the recipient, zero ranking delivered. Merged from audit_economics and audit_permissions. Spec-conformant and documented in the README, so flagged as a scope decision.\n3. **Low, boost line 182.** boost() has no deadline parameter, unlike purchase(), so a late-included boost pays in full for seconds of ranking.\n4. **Info, boost line 180.** The proceeds recipient can boost any placement at zero net cost, so on-chain totals are operator-manipulable.\n5. **Info, rankOf line 252.** NatSpec says \"Total paid\" but cancelled placements report the full nominal base even when refunded.\n\n**Static-analysis leads dropped:** the aderyn reentrancy line is a staticcall to the immutable registry behind a guard, the slither strict-equality lines are enum comparisons, and the unused-return in configureToken is a deliberate ABI probe whose revert is the check.","treeHash":null,"usage":{"cachedInputTokens":671211,"inputTokens":290,"model":"claude-fable-5-1","outputTokens":19996,"runtime":"claude","turns":20,"wallClockMs":287155}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"5fdca4f96f37a80d","findings":[{"citation":"resolved","description":"Asymmetry between the two paid entry points. purchase(listingId, expectedRevision, expectedRecipient, deadline) lets the payer pin every reviewable term and refuse execution after `deadline` (line 156). boost(placementId, amount) pins nothing: the only time check is `block.timestamp < endsAt`. A boost is a non-refundable payment whose only benefit is ranking for the remaining window, and that window shrinks every second between signing and inclusion. A booster who signs at t=0 expecting ~24h of ranking can be included at endsAt-1 and pay the full amount for one second, with no way to bound that outcome on-chain. Nothing in the brief asks for a boost deadline, so this is reported as a usability/asymmetry gap, not a bypass; the booster can only mitigate it off-chain. Fix that preserves the design: add `uint256 deadline` (or `uint256 minRemaining`) to boost() mirroring purchase(), reverting with DeadlineExpired when `block.timestamp > deadline`.","line":180,"path":"src/store/FeaturedPlacementsV2.sol","reproduction":"State: token configured, recipient = owner. alice creates listing 1, deposits 0.1 IMD, purchase(1, 1, owner, START) -> placement 1 with endsAt = START+86400. bob deposits 1e18 and signs boost(1, 1e18) at START. The tx is included at block.timestamp = START+86399. Actual: boost succeeds, available[bob] = 0, totalBoost[1] = 1e18; one second later isActive(1) is false and settle(1) credits 0.1e18 + 1e18 to the owner. bob paid 1 IMD for 1 second of ranking. Expected (if boost mirrored purchase): bob passes deadline = START+3600 and the late inclusion reverts with DeadlineExpired, leaving available[bob] = 1e18. Verified in a scratch Foundry test (test_boostHasNoDeadline): boost at START+24h-1 succeeds and proceeds(owner) == PRICE + 1 ether after settle.","severity":"low","snippet":"    function boost(uint256 placementId, uint256 amount) external nonReentrant {\n        Placement storage placement = _openPlacement(placementId);\n        if (block.timestamp >= placement.endsAt) revert PlacementExpired();\n        if (amount == 0) revert InvalidAmount();","title":"boost() has no deadline or minimum-remaining-time guard, unlike purchase(); a delayed boost pays in full for seconds of ranking"},{"citation":"resolved","description":"Trust-gap (access x economics x asymmetry), documented so the judge and author can weigh it rather than a permission bypass. Three facts combine: (1) access: only the payer or the current listing owner may cancel (line 197) and boosters have no say; (2) economics: the base price refunds pro rata but the boost is forfeited 100% regardless of elapsed time (line 203); (3) asymmetry: the forfeited boost always lands with the pinned proceeds recipient, which the brief sets to the owner's own wallet. So the party who cancels loses nothing at t=0, the booster loses everything, and the owner gains everything. Any advertiser can grief boosters this way (griefing only, no profit). But the owner is also free to be a listing owner and advertiser: they list, buy a placement for 0.1 IMD, wait for boosts, cancel in the same block for a full refund, and re-purchase; the boosts are now withdrawable proceeds and the ad is live again with zero cost incurred and zero ranking time delivered. The brief explicitly specifies 'never refunded: on cancel ... the whole boost goes to the pinned proceeds recipient', and the README states a booster must accept this, so this is reported as a design trust assumption, not a defect. If the author wants to narrow it without changing the economics, options are: forbid the payer (not the listing owner) from cancelling once totalBoost[id] > 0, or forfeit boosts pro rata like the base price; both are scope decisions for the requester.","line":203,"path":"src/store/FeaturedPlacementsV2.sol","reproduction":"State: token configured, proceedsRecipient = owner. owner creates listing 1, deposits 0.1e18, purchase(1, 1, owner, START) -> placement 1. bob deposits 10e18 and calls boost(1, 10e18): available[bob]=0, totalEscrowed=10.1e18. Same block, owner calls cancel(1): returns refund = 0.1e18, available[owner] = 0.1e18, proceeds[owner] = 10e18, available[bob] = 0. owner immediately calls purchase(1, 1, owner, START) -> placement 2 live, rankOf(2) = 0.1e18. Net: owner's custody unchanged (0.1e18 still available) plus 10e18 withdrawable proceeds; bob received zero seconds of boosted ranking. Verified in a scratch Foundry test (test_ownerHarvestsBoostsAtZeroCost): asserts refund == PRICE, proceeds(owner) == 10 ether, available(bob) == 0, isActive(2) == true.","severity":"info","snippet":"        earned += totalBoost[id];\n        placement.status = Status.Cancelled;\n        placement.closedAt = block.timestamp;\n        placement.refund = refund;\n        totalEscrowed -= PRICE + totalBoost[id];\n        available[placement.payer] += refund;","title":"Trust assumption: payer/listing owner can cancel at t=0 for a full base refund while every third-party boost is forfeited to the recipient; when the owner is also the advertiser this harvests boosts a"}],"hash":"eec2d06dd79b6ae71580f143a9c3bb5ed754efd3f7ad9e07e2b559e93ced371f","nodeId":"8e19ad8d-b509-4117-a91a-a3423d740185","outcome":"completed","summary":"The review is complete and `.imd-findings.json` holds the result. The tree is unchanged apart from that file, and the scratch test is removed.\n\n**Outcome: no permission bypass, trust gap, or asymmetry defect of medium or higher severity in either contract.** The full suite passes under the repository seed, and the three pinned files are byte-identical to the upstream commit. The V2 diff contains only the changes the brief lists.\n\nWhat I traced in the assigned area:\n\n- **Access control map.** Every state-changing entry point has the guard its effect needs. Deposits and proceeds are debitable only by their own holder. The owner has no sweep, pause, admin cancel, or early settle. Factory and pending owner are rejected everywhere. Ownership rotation is two-step and cannot reach zero.\n- **Trust-gap seams.** Recipient rotation cannot redirect in-flight value because every placement pins its recipient at purchase, and the payer's expected-recipient check blocks owner front-running. Listing transfers never move a refund away from the original payer. Boosts cannot spend allowances, other deposits, or proceeds.\n- **Asymmetry.** Purchase, boost, cancel and settle use the same expiry boundary. Escrow, available and proceeds deltas mirror across every pair, so the conservation rule holds with boosts counted in escrow until closure.\n\nTwo items are recorded, each with a concrete reproduction I ran:\n\n1. **Low.** The boost function takes no deadline, while purchase does. A boost signed at the start of a placement but included one second before expiry pays in full for one second of ranking. Suggested fix adds a deadline parameter that mirrors purchase.\n2. **Info, trust assumption.** Cancelling at time zero refunds the full base price but forfeits every boost to the recipient. Because the recipient is the owner's wallet, an owner who also advertises can collect third-party boosts at zero cost by cancelling and re-purchasing in the same block. The brief mandates this forfeiture rule, so it is documented for the requester's decision rather than reported as a defect.\n\nCoverage lists all 18 entry points as holding or tied to those findings, plus three invariant rows. Nothing was left unreached.","treeHash":null,"usage":{"cachedInputTokens":1102004,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":25818,"runtime":"claude","turns":33,"wallClockMs":415110}}],"verification":[{"checks":[{"durationMs":3059,"exitCode":0,"name":"build","output":"Compiling 35 files with Solc 0.8.26\nSolc 0.8.26 finished in 2.99s\nCompiler run successful!\n","passed":true},{"durationMs":4511,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 11 tests for test/HookListingRegistry.t.sol:HookListingRegistryTest\n[PASS] testAuditUrlAndAuditLabelNeverConferVerification() (gas: 1866252)\n[PASS] testFreeCreateAndEditPersistAllFieldsAndHistory() (gas: 1618302)\n[PASS] testFuzzUnauthorizedCallerCannotModifyListing(address) (runs: 256, μ: 842654, ~: 842726)\n[PASS] testFuzzValidLabelsRemainSearchable(uint128) (runs: 256, μ: 747238, ~: 746363)\n[PASS] testInvalidIdentityUnknownRecordsAndPagesRevert() (gas: 931804)\n[PASS] testInvalidMetadataIsAtomicAndReferencesMayBeAbsent() (gas: 1106108)\n[PASS] testLabelValidationBoundsAndDuplicates() (gas: 965451)\n[PASS] testSameHookCanHaveIndependentOwnersButNoOwnerDuplicates() (gas: 2284749)\n[PASS] testSearchByLabelChainOwnerAndVisibilityUsesBoundedAscendingCursor() (gas: 2329424)\n[PASS] testTransferCanBeCancelledOrReplaced() (gas: 1022369)\n[PASS] testTwoStepListingTransferAndDuplicateProtection() (gas: 1824321)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 20.77ms (42.03ms CPU time)\n\nRan 8 tests for test/FeaturedAdversarial.t.sol:FeaturedAdversarialTest\n[PASS] testBoostedClosureWorksWithBlockedTransfersAndFailedProceedsWithdrawalIsRetryable() (gas: 2460814)\n[PASS] testFalseRevertingMalformedAndNoMovementTokensCannotCreateCredit() (gas: 1202299)\n[PASS] testMissingTokenCodeAfterConfigurationCannotFakeTransfer() (gas: 68265)\n[PASS] testNoReturnTokenWorksForDepositsAndWithdrawals() (gas: 264264)\n[PASS] testProceedsWithdrawalAlsoGuardsReentrancy() (gas: 1623477)\n[PASS] testRecipientCannotBlockCancellationOrSettlementAndFailedClaimRollsBack() (gas: 2098547)\n[PASS] testTaxedOrFailedWithdrawalRollsBackAndRemainsRetryable() (gas: 1194937)\n[PASS] testTokenCallbacksCannotReenterAnyCustodyMutation() (gas: 3495787)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 26.26ms (4.30ms CPU time)\n\nRan 2 tests for test/StoreDeployment.t.sol:StoreDeploymentTest\n[PASS] testFactoryLaunchOnEthereum() (gas: 15478554)\n[PASS] testFactoryLaunchOnRobinhood() (gas: 15478576)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 36.23ms (60.60ms CPU time)\n\nRan 5 tests for test/FeaturedTransfer.t.sol:FeaturedTransferTest\n[PASS] testBuyerCanClearSellersAdAndBuyItsOwnWhileOnlySellerGetsRefund() (gas: 3191852)\n[PASS] testFuzzNewOwnerCancellationConservesAllCredits(uint256) (runs: 256, μ: 1715314, ~: 1716843)\nLogs:\n  Bound result 96\n\n[PASS] testIntermediateOwnerLosesCancellationPowerButOriginalPayerKeepsIt() (gas: 1871955)\n[PASS] testNewOwnerCannotCancelAtExpiryAndCanRenewBeforeOldSettlement() (gas: 2171958)\n[PASS] testProposedOwnerCannotCancelUntilAcceptingAndAdminCannotCancel() (gas: 1841259)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 43.70ms (31.91ms CPU time)\n\nRan 21 tests for test/FeaturedPlacements.t.sol:FeaturedPlacementsV2Test\n[PASS] testAdminRotationIsTwoStepAndDoesNotControlCustody() (gas: 805991)\n[PASS] testBadTokenConfigurationAndConstructorArguments() (gas: 1022370)\n[PASS] testCancelledPlacementCannotSettleOrCancelTwice() (gas: 1589471)\n[PASS] testDepositWithdrawBelongsToDepositorAndCannotBeSwept() (gas: 693554)\n[PASS] testDirectTransfersAreNotCreditsAndCannotBeSwept() (gas: 496842)\n[PASS] testEditingInvalidatesStalePurchaseAndFeeDisclosuresAreSnapshotted() (gas: 1995045)\n[PASS] testExpirySettlementIsPermissionlessAndCannotBeRepeatedOrRefunded() (gas: 1833508)\n[PASS] testFeaturedOrderingPaginationAndLabelsSkipCancelledAndExpired() (gas: 3112072)\n[PASS] testFuzzCancellationConservesFundsAndRoundsInPayersFavor(uint256) (runs: 256, μ: 1593263, ~: 1594792)\nLogs:\n  Bound result 96\n\n[PASS] testHalfDurationCancellationSplitsFeeAndProceedsArePullBased() (gas: 1745233)\n[PASS] testImmediateCancellationFullyRefundsThenOwnerCanWithdraw() (gas: 1651089)\n[PASS] testOnlyListingOwnerCanBuyAndUnrelatedAccountsCannotCancelIncludingAdmin() (gas: 1766137)\n[PASS] testPurchaseCostsExactlyPointOneImdAndHasTwentyFourHourAdLabel() (gas: 1481770)\n[PASS] testRecipientRotationInvalidatesStalePurchaseBeforeFundsAreLocked() (gas: 1453620)\n[PASS] testRecipientsAreSnapshottedAndChangingThemCannotTakeOldFunds() (gas: 2419926)\n[PASS] testSingleLivePlacementButCanRenewUnsettledExpiredOrCancelledPlacement() (gas: 2502718)\n[PASS] testSnapshotSurvivesEditingArchivingTransferAndRefundStaysWithPayer() (gas: 2402298)\n[PASS] testStaleTermsInactiveListingInsufficientBalanceAndDeadlineFailBeforeDebit() (gas: 1405005)\n[PASS] testUnknownIdsAndInvalidPagesFail() (gas: 229958)\n[PASS] testUnsetConfigurationAndTokenCanOnlyBeChosenOnce() (gas: 703534)\n[PASS] testZeroAmountsInvalidRecipientsAndExcessWithdrawalsFail() (gas: 629719)\nSuite result: ok. 21 passed; 0 failed; 0 skipped; finished in 49.28ms (40.63ms CPU time)\n\nRan 12 tests for test/FeaturedBoost.t.sol:FeaturedBoostTest\n[PASS] testBoostBoundaryAndClosedPlacements() (gas: 2549550)\n[PASS] testBoostCannotSpendWalletApprovalAnotherDepositOrProceeds() (gas: 2240564)\n[PASS] testBoostSurvivesArchivingAndDoesNotChangePinnedMetadata() (gas: 2252708)\n[PASS] testExpiredBoostedPlacementCanBeRenewedBeforeSettlementWithoutAffectingNewOne() (gas: 2434966)\n[PASS] testFuzzCancelBoostsPreservesOriginalRoundingAndConservation(uint256,uint128) (runs: 256, μ: 2110569, ~: 2110637)\nLogs:\n  Bound result 79016\n  Bound result 1898\n\n[PASS] testFuzzExpirySettlesBaseAndBoostToPinnedRecipient(uint128) (runs: 256, μ: 1973487, ~: 1973392)\nLogs:\n  Bound result 12648979423738\n\n[PASS] testImmediateCancelRefundsOnlyBaseAndForfeitsAllBoosts() (gas: 2394935)\n[PASS] testInvalidBoostAmountsAndIdsRevertWithoutDebiting() (gas: 1857858)\n[PASS] testNewListingOwnerCancellationDoesNotRefundBoosterOrRedirectProceeds() (gas: 2018551)\n[PASS] testPurchaseEventRetainsAdLabelAndOriginalPrice() (gas: 1303682)\n[PASS] testRankingTiesUseLowerIdAcrossPagesAndTotalsAlignAfterFiltering() (gas: 3194982)\n[PASS] testThirdPartyBoostUsesOnlyTheirDepositAndKeepsTermsPinned() (gas: 1903870)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 49.46ms (103.77ms CPU time)\n\nRan 1 test for test/FeaturedTransferInvariant.t.sol:FeaturedTransferInvariantTest\n[PASS] invariantEveryTokenIsAccountedForAndEveryCreditHasBacking() (runs: 128, calls: 16384, reverts: 0)\n\n╭-------------------------+------------------------+-------+---------+----------╮\n| Contract                | Selector               | Calls | Reverts | Discards |\n+===============================================================================+\n| FeaturedTransferHandler | advanceTime            | 1360  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | boost                  | 1345  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | cancel                 | 1375  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | cancelAsCurrentOwner   | 1352  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | claim                  | 1317  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | deposit                | 1374  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | purchaseAsCurrentOwner | 1370  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | rotateRecipient        | 1424  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | settle                 | 1362  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | toggleCurrentListing   | 1332  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | transferListing        | 1401  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | withdraw               | 1372  | 0       | 0        |\n╰-------------------------+------------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 300000000000000000\n  Bound result 300000000000000000\n  Bound result 33333333333333333\n  Bound result 50000000000000000\n  Bound result 125307\n  Bound result 763600050\n  Bound result 172800\n  Bound result 2002140241\n  Bound result 97\n  Bound result 100000000000000000\n  Bound result 154975\n  Bound result 290404790215019570\n  Bound result 2651282218499739\n  Bound result 915\n  Bound result 2354\n  Bound result 96\n  Bound result 91\n  Bound result 2734\n  Bound result 408\n  Bound result 1849\n  Bound result 79083\n  Bound result 254\n  Bound result 300000000000000000\n  Bound result 4137\n  Bound result 1\n  Bound result 4288\n  Bound result 8667\n  Bound result 144227975815786279\n  Bound result 66067\n  Bound result 228239293732799535\n  Bound result 193590374172124205\n  Bound result 4288\n  Bound result 12729\n  Bound result 6047\n  Bound result 16963463214819669\n  Bound result 174424073309946448\n  Bound result 127793\n  Bound result 253\n  Bound result 8909260023386885\n  Bound result 4816\n  Bound result 3518789280\n  Bound result 127\n  Bound result 1\n  Bound result 3879\n  Bound result 2488\n  Bound result 10\n  Bound result 4500\n  Bound result 71221745016430458\n  Bound result 3877\n  Bound result 30\n  Bound result 4\n  Bound result 1028\n  Bound result 525\n  Bound result 5\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.39s (4.39s CPU time)\n\nRan 1 test for test/FeaturedInvariant.t.sol:FeaturedInvariantTest\n[PASS] invariantEveryTokenIsAccountedForAndEveryCreditHasBacking() (runs: 128, calls: 16384, reverts: 0)\n\n╭-----------------+-----------------+-------+---------+----------╮\n| Contract        | Selector        | Calls | Reverts | Discards |\n+================================================================+\n| FeaturedHandler | advanceTime     | 1637  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| FeaturedHandler | boost           | 1706  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| FeaturedHandler | cancel          | 1686  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| FeaturedHandler | claim           | 1592  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| FeaturedHandler | deposit         | 1593  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| FeaturedHandler | purchase        | 1642  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| FeaturedHandler | rotateRecipient | 1630  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| FeaturedHandler | settle          | 1651  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| FeaturedHandler | toggleListing   | 1637  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| FeaturedHandler | withdraw        | 1610  | 0       | 0        |\n╰-----------------+-----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 300000000000000000\n  Bound result 300000000000000000\n  Bound result 33333333333333333\n  Bound result 50000000000000000\n  Bound result 2\n  Bound result 15478990920987757\n  Bound result 300000000000000000\n  Bound result 4692\n  Bound result 1\n  Bound result 4288\n  Bound result 566666666666657685\n  Bound result 3600\n  Bound result 4616\n  Bound result 282\n  Bound result 146963591191081044\n  Bound result 16383\n  Bound result 24600\n  Bound result 1\n  Bound result 5958\n  Bound result 174607431768211455\n  Bound result 1\n  Bound result 32904407924044806\n  Bound result 89067\n  Bound result 1359561208804943\n  Bound result 1\n  Bound result 5\n  Bound result 255\n  Bound result 1024\n  Bound result 7\n  Bound result 174607431768211455\n  Bound result 97\n  Bound result 2849\n  Bound result 277483790929074252\n  Bound result 65582401588505961\n  Bound result 254\n  Bound result 3967\n  Bound result 91\n  Bound result 255\n  Bound result 8940\n  Bound result 5\n  Bound result 15027\n  Bound result 42201\n  Bound result 13686\n  Bound result 3688\n  Bound result 127\n  Bound result 1591\n  Bound result 244\n  Bound result 244\n  Bound result 832\n  Bound result 5675\n  Bound result 8\n  Bound result 101811\n  Bound result 91\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.44s (4.44s CPU time)\n\nRan 8 test suites in 4.44s (9.05s CPU time): 61 tests passed, 0 failed, 0 skipped (61 total tests)\n","passed":true},{"durationMs":40,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"FeaturedPlacementsV2.acceptOwnership()\",\"FeaturedPlacementsV2.boost(uint256,uint256)\",\"FeaturedPlacementsV2.cancel(uint256)\",\"FeaturedPlacementsV2.cancelOwnershipTransfer()\",\"FeaturedPlacementsV2.configureToken(address)\",\"FeaturedPlacementsV2.deposit(uint256)\",\"FeaturedPlacementsV2.purchase(uint256,uint256,address,uint256)\",\"FeaturedPlacementsV2.setProceedsRecipient(address)\",\"FeaturedPlacementsV2.settle(uint256)\",\"FeaturedPlacementsV2.transferOwnership(address)\",\"FeaturedPlacementsV2.withdraw(uint256,address)\",\"FeaturedPlacementsV2.withdrawProceeds(uint256,address)\",\"HookListingRegistry.acceptListingOwnership(uint256)\",\"HookListingRegistry.cancelListingTransfer(uint256)\",\"HookListingRegistry.createListing(uint256,address,(string,string,string,string,string,uint160,string,string,string,string[]))\",\"HookListingRegistry.proposeListingOwner(uint256,address)\",\"HookListingRegistry.setListingActive(uint256,bool)\",\"HookListingRegistry.updateListing(uint256,(string,string,string,string,string,uint160,string,string,string,string[]))\"],\"files\":{\".gitignore\":3,\"LICENSE\":24,\"README.md\":196,\"docs/REVIEW.md\":32,\"docs/UPSTREAM.md\":39,\"foundry.toml\":26,\"remappings.txt\":2,\"src/store/ExactStoreToken.sol\":48,\"src/store/FeaturedPlacementsV2.sol\":303,\"src/store/HookListingRegistry.sol\":217,\"src/store/StoreAdmin.sol\":46,\"test/FeaturedAdversarial.t.sol\":191,\"test/FeaturedBoost.t.sol\":317,\"test/FeaturedInvariant.t.sol\":234,\"test/FeaturedPlacements.t.sol\":443,\"test/FeaturedTransfer.t.sol\":120,\"test/FeaturedTransferInvariant.t.sol\":82,\"test/HookListingRegistry.t.sol\":265,\"test/StoreDeployment.t.sol\":84,\"test/helpers/StoreFixture.sol\":81,\"test/helpers/StoreTokenMock.sol\":82,\"test/helpers/TestToken.sol\":13},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":803,"exitCode":0,"name":"slither","output":"[medium/high] incorrect-equality at src/store/FeaturedPlacementsV2.sol:239: FeaturedPlacementsV2.getPlacement(uint256) (src/store/FeaturedPlacementsV2.sol#239-242) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/store/FeaturedPlacementsV2.sol:250: FeaturedPlacementsV2.rankOf(uint256) (src/store/FeaturedPlacementsV2.sol#250-253) uses a dangerous strict equality:\n[medium/medium] unused-return at src/store/FeaturedPlacementsV2.sol:107: FeaturedPlacementsV2.configureToken(address) (src/store/FeaturedPlacementsV2.sol#107-116) ignores return value by ExactStoreToken.balance(token,address(this)) (src/store/FeaturedPlacementsV2.sol#113)\n[low/medium] reentrancy-benign at src/store/FeaturedPlacementsV2.sol:126: Reentrancy in FeaturedPlacementsV2.deposit(uint256) (src/store/FeaturedPlacementsV2.sol#126-134):\n[low/medium] timestamp at src/store/FeaturedPlacementsV2.sol:148: FeaturedPlacementsV2.purchase(uint256,uint256,address,uint256) (src/store/FeaturedPlacementsV2.sol#148-176) uses timestamp for comparisons\n[low/medium] timestamp at src/store/FeaturedPlacementsV2.sol:250: FeaturedPlacementsV2.rankOf(uint256) (src/store/FeaturedPlacementsV2.sol#250-253) uses timestamp for comparisons\n[low/medium] timestamp at src/store/FeaturedPlacementsV2.sol:195: FeaturedPlacementsV2.cancel(uint256) (src/store/FeaturedPlacementsV2.sol#195-213) uses timestamp for comparisons\n[low/medium] timestamp at src/store/FeaturedPlacementsV2.sol:239: FeaturedPlacementsV2.getPlacement(uint256) (src/store/FeaturedPlacementsV2.sol#239-242) uses timestamp for comparisons\n[low/medium] timestamp at src/store/FeaturedPlacementsV2.sol:180: FeaturedPlacementsV2.boost(uint256,uint256) (src/store/FeaturedPlacementsV2.sol#180-190) uses timestamp for comparisons\n[low/medium] timestamp at src/store/FeaturedPlacementsV2.sol:244: FeaturedPlacementsV2.isActive(uint256) (src/store/FeaturedPlacementsV2.sol#244-247) uses timestamp for comparisons\n[low/medium] timestamp at src/store/FeaturedPlacementsV2.sol:216: FeaturedPlacementsV2.settle(uint256) (src/store/FeaturedPlacementsV2.sol#216-226) uses timestamp for comparisons","passed":true},{"durationMs":293,"exitCode":0,"name":"aderyn","output":"[high] reentrancy-state-change at src/store/FeaturedPlacementsV2.sol:157: Reentrancy: State change after external call (2 places)\n[low] centralization-risk at src/store/FeaturedPlacementsV2.sol:107: Centralization Risk (4 places)\n[low] costly-loop at src/store/HookListingRegistry.sol:175: Costly operations inside loop (2 places)\n[low] large-numeric-literal at src/store/FeaturedPlacementsV2.sol:32: Large Numeric Literal\n[low] literal-instead-of-constant at src/store/ExactStoreToken.sol:19: Literal Instead of Constant (2 places)\n[low] non-reentrant-not-first at src/store/FeaturedPlacementsV2.sol:107: `nonReentrant` is Not the First Modifier\n[low] require-revert-in-loop at src/store/HookListingRegistry.sol:201: Loop Contains `require`/`revert` (3 places)\n[low] unchecked-return at src/store/FeaturedPlacementsV2.sol:113: Unchecked Return (4 places)\n[low] uninitialized-local-variable at src/store/HookListingRegistry.sol:175: Uninitialized Local Variable (5 places)\n[low] unsafe-erc20-operation at src/store/ExactStoreToken.sol:43: Unsafe ERC20 Operation\n[low] unused-public-function at src/store/FeaturedPlacementsV2.sol:250: Public Function Not Used Internally","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"25eb2bfbb9b8aa9ceac213211c09045fe26c2b171a9346e492b978bcfe5f670b","verifiedTreeHash":"ed055a4858ffe029665f2b1955bbedc4d265e397","verifierVersion":"0.1.0+560ab6a2"},{"checks":[{"durationMs":10789,"exitCode":0,"name":"build","output":"Compiling 39 files with Solc 0.8.26\nSolc 0.8.26 finished in 10.62s\nCompiler run successful!\n","passed":true},{"durationMs":22093,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/StorePaginationBoundary.t.sol:StorePaginationBoundaryTest\n[PASS] testSparsePagesAcrossMaxScanKeepIdsTotalsAndHistoricalRevisionsAligned() (gas: 77214630)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 47.03ms (45.55ms CPU time)\n\nRan 2 tests for test/StoreDeployment.t.sol:StoreDeploymentTest\n[PASS] testFactoryLaunchOnEthereum() (gas: 15478554)\n[PASS] testFactoryLaunchOnRobinhood() (gas: 15478576)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 84.71ms (112.29ms CPU time)\n\nRan 5 tests for test/FeaturedTransfer.t.sol:FeaturedTransferTest\n[PASS] testBuyerCanClearSellersAdAndBuyItsOwnWhileOnlySellerGetsRefund() (gas: 3191852)\n[PASS] testFuzzNewOwnerCancellationConservesAllCredits(uint256) (runs: 256, μ: 1715604, ~: 1716843)\nLogs:\n  Bound result 67962\n\n[PASS] testIntermediateOwnerLosesCancellationPowerButOriginalPayerKeepsIt() (gas: 1871955)\n[PASS] testNewOwnerCannotCancelAtExpiryAndCanRenewBeforeOldSettlement() (gas: 2171958)\n[PASS] testProposedOwnerCannotCancelUntilAcceptingAndAdminCannotCancel() (gas: 1841259)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 147.98ms (147.14ms CPU time)\n\nRan 12 tests for test/FeaturedBoost.t.sol:FeaturedBoostTest\n[PASS] testBoostBoundaryAndClosedPlacements() (gas: 2549550)\n[PASS] testBoostCannotSpendWalletApprovalAnotherDepositOrProceeds() (gas: 2240564)\n[PASS] testBoostSurvivesArchivingAndDoesNotChangePinnedMetadata() (gas: 2252708)\n[PASS] testExpiredBoostedPlacementCanBeRenewedBeforeSettlementWithoutAffectingNewOne() (gas: 2434966)\n[PASS] testFuzzCancelBoostsPreservesOriginalRoundingAndConservation(uint256,uint128) (runs: 256, μ: 2110563, ~: 2110637)\nLogs:\n  Bound result 49935\n  Bound result 10199592383306876479899228935165784845\n\n[PASS] testFuzzExpirySettlesBaseAndBoostToPinnedRecipient(uint128) (runs: 256, μ: 1973477, ~: 1973348)\nLogs:\n  Bound result 12648979423738\n\n[PASS] testImmediateCancelRefundsOnlyBaseAndForfeitsAllBoosts() (gas: 2394935)\n[PASS] testInvalidBoostAmountsAndIdsRevertWithoutDebiting() (gas: 1857858)\n[PASS] testNewListingOwnerCancellationDoesNotRefundBoosterOrRedirectProceeds() (gas: 2018551)\n[PASS] testPurchaseEventRetainsAdLabelAndOriginalPrice() (gas: 1303682)\n[PASS] testRankingTiesUseLowerIdAcrossPagesAndTotalsAlignAfterFiltering() (gas: 3194982)\n[PASS] testThirdPartyBoostUsesOnlyTheirDepositAndKeepsTermsPinned() (gas: 1903870)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 153.40ms (301.88ms CPU time)\n\nRan 8 tests for test/FeaturedAdversarial.t.sol:FeaturedAdversarialTest\n[PASS] testBoostedClosureWorksWithBlockedTransfersAndFailedProceedsWithdrawalIsRetryable() (gas: 2460814)\n[PASS] testFalseRevertingMalformedAndNoMovementTokensCannotCreateCredit() (gas: 1202299)\n[PASS] testMissingTokenCodeAfterConfigurationCannotFakeTransfer() (gas: 68265)\n[PASS] testNoReturnTokenWorksForDepositsAndWithdrawals() (gas: 264264)\n[PASS] testProceedsWithdrawalAlsoGuardsReentrancy() (gas: 1623477)\n[PASS] testRecipientCannotBlockCancellationOrSettlementAndFailedClaimRollsBack() (gas: 2098547)\n[PASS] testTaxedOrFailedWithdrawalRollsBackAndRemainsRetryable() (gas: 1194937)\n[PASS] testTokenCallbacksCannotReenterAnyCustodyMutation() (gas: 3495787)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 180.29ms (9.96ms CPU time)\n\nRan 11 tests for test/HookListingRegistry.t.sol:HookListingRegistryTest\n[PASS] testAuditUrlAndAuditLabelNeverConferVerification() (gas: 1866252)\n[PASS] testFreeCreateAndEditPersistAllFieldsAndHistory() (gas: 1618302)\n[PASS] testFuzzUnauthorizedCallerCannotModifyListing(address) (runs: 256, μ: 842654, ~: 842726)\n[PASS] testFuzzValidLabelsRemainSearchable(uint128) (runs: 256, μ: 747094, ~: 745886)\n[PASS] testInvalidIdentityUnknownRecordsAndPagesRevert() (gas: 931804)\n[PASS] testInvalidMetadataIsAtomicAndReferencesMayBeAbsent() (gas: 1106108)\n[PASS] testLabelValidationBoundsAndDuplicates() (gas: 965451)\n[PASS] testSameHookCanHaveIndependentOwnersButNoOwnerDuplicates() (gas: 2284749)\n[PASS] testSearchByLabelChainOwnerAndVisibilityUsesBoundedAscendingCursor() (gas: 2329424)\n[PASS] testTransferCanBeCancelledOrReplaced() (gas: 1022369)\n[PASS] testTwoStepListingTransferAndDuplicateProtection() (gas: 1824321)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 180.29ms (216.00ms CPU time)\n\nRan 21 tests for test/FeaturedPlacements.t.sol:FeaturedPlacementsV2Test\n[PASS] testAdminRotationIsTwoStepAndDoesNotControlCustody() (gas: 805991)\n[PASS] testBadTokenConfigurationAndConstructorArguments() (gas: 1022370)\n[PASS] testCancelledPlacementCannotSettleOrCancelTwice() (gas: 1589471)\n[PASS] testDepositWithdrawBelongsToDepositorAndCannotBeSwept() (gas: 693554)\n[PASS] testDirectTransfersAreNotCreditsAndCannotBeSwept() (gas: 496842)\n[PASS] testEditingInvalidatesStalePurchaseAndFeeDisclosuresAreSnapshotted() (gas: 1995045)\n[PASS] testExpirySettlementIsPermissionlessAndCannotBeRepeatedOrRefunded() (gas: 1833508)\n[PASS] testFeaturedOrderingPaginationAndLabelsSkipCancelledAndExpired() (gas: 3112072)\n[PASS] testFuzzCancellationConservesFundsAndRoundsInPayersFavor(uint256) (runs: 256, μ: 1593553, ~: 1594792)\nLogs:\n  Bound result 67962\n\n[PASS] testHalfDurationCancellationSplitsFeeAndProceedsArePullBased() (gas: 1745233)\n[PASS] testImmediateCancellationFullyRefundsThenOwnerCanWithdraw() (gas: 1651089)\n[PASS] testOnlyListingOwnerCanBuyAndUnrelatedAccountsCannotCancelIncludingAdmin() (gas: 1766137)\n[PASS] testPurchaseCostsExactlyPointOneImdAndHasTwentyFourHourAdLabel() (gas: 1481770)\n[PASS] testRecipientRotationInvalidatesStalePurchaseBeforeFundsAreLocked() (gas: 1453620)\n[PASS] testRecipientsAreSnapshottedAndChangingThemCannotTakeOldFunds() (gas: 2419926)\n[PASS] testSingleLivePlacementButCanRenewUnsettledExpiredOrCancelledPlacement() (gas: 2502718)\n[PASS] testSnapshotSurvivesEditingArchivingTransferAndRefundStaysWithPayer() (gas: 2402298)\n[PASS] testStaleTermsInactiveListingInsufficientBalanceAndDeadlineFailBeforeDebit() (gas: 1405005)\n[PASS] testUnknownIdsAndInvalidPagesFail() (gas: 229958)\n[PASS] testUnsetConfigurationAndTokenCanOnlyBeChosenOnce() (gas: 703534)\n[PASS] testZeroAmountsInvalidRecipientsAndExcessWithdrawalsFail() (gas: 629719)\nSuite result: ok. 21 passed; 0 failed; 0 skipped; finished in 187.16ms (185.05ms CPU time)\n\nRan 6 tests for test/RegistryBoundary.t.sol:RegistryBoundaryTest\n[PASS] testDuplicateLabelsAtLastPositionAreAtomicAndReorderingPreservesSearch() (gas: 1619095)\n[PASS] testDuplicateOwnershipAcceptanceCanBeRetriedAfterConflictingIdentityMoves() (gas: 2069728)\n[PASS] testFuzzEveryMetadataLimitRejectsCreateAndUpdateAtomically(uint256,uint256,bool) (runs: 1000, μ: 1209666, ~: 1232179)\nLogs:\n  Bound result 528\n\n[PASS] testFuzzLabelsRejectEveryByteOutsideLowercaseDigitHyphen(uint256,uint256) (runs: 1000, μ: 979789, ~: 981331)\nLogs:\n  Bound result 128\n\n[PASS] testMaximumMetadataAndSixteenMaximumLengthLabelsRoundTrip() (gas: 12659557)\n[PASS] testUnknownListingFailsEveryMutationWithoutCreatingGhostRecords() (gas: 534408)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 187.49ms (345.69ms CPU time)\n\nRan 9 tests for test/FeaturedTokenBoundary.t.sol:FeaturedTokenBoundaryTest\n[PASS] testEveryRejectedTokenTransferLeavesBoostedProceedsClaimRetryable() (gas: 3979447)\n[PASS] testFuzzFullUint256BoostPreservesRefundAndWithdrawalLiveness(uint256,uint256,bool) (runs: 1000, μ: 2047125, ~: 1978561)\nLogs:\n  Bound result 8582\n  Bound result 1\n\n[PASS] testMalformedBalanceAbiCannotConsumeOneTimeConfiguration() (gas: 514631)\n[PASS] testMalformedBalanceReadsBlockWithdrawalsAtomicallyAndAllowRetry() (gas: 2592521)\n[PASS] testMalformedDecimalsCannotConsumeOneTimeConfiguration() (gas: 666590)\n[PASS] testMaximumUint256EscrowCanCancelAndFullyExit() (gas: 2117378)\n[PASS] testMaximumUint256EscrowCanSettleAndFullyExit() (gas: 1974413)\n[PASS] testMissingAllowanceAndBalanceDoNotCreditOrConsumeUserFunds() (gas: 663055)\n[PASS] testPendingAndReplacedOwnersCannotConfigureOrRedirect() (gas: 561087)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 187.41ms (195.28ms CPU time)\n\nRan 1 test for test/FeaturedTransferInvariant.t.sol:FeaturedTransferInvariantTest\n[PASS] invariantEveryTokenIsAccountedForAndEveryCreditHasBacking() (runs: 128, calls: 16384, reverts: 0)\n\n╭-------------------------+------------------------+-------+---------+----------╮\n| Contract                | Selector               | Calls | Reverts | Discards |\n+===============================================================================+\n| FeaturedTransferHandler | advanceTime            | 1360  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | boost                  | 1345  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | cancel                 | 1375  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | cancelAsCurrentOwner   | 1352  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | claim                  | 1317  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | deposit                | 1374  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | purchaseAsCurrentOwner | 1370  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | rotateRecipient        | 1424  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | settle                 | 1362  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | toggleCurrentListing   | 1332  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | transferListing        | 1401  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | withdraw               | 1372  | 0       | 0        |\n╰-------------------------+------------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 300000000000000000\n  Bound result 300000000000000000\n  Bound result 33333333333333333\n  Bound result 50000000000000000\n  Bound result 125307\n  Bound result 763600050\n  Bound result 17\n  Bound result 2002140241\n  Bound result 256\n  Bound result 154975\n  Bound result 290404790215019570\n  Bound result 2651282218499739\n  Bound result 907\n  Bound result 2395\n  Bound result 1222\n  Bound result 242\n  Bound result 2667\n  Bound result 5838\n  Bound result 1827\n  Bound result 127\n  Bound result 107\n  Bound result 19\n  Bound result 3995\n  Bound result 1\n  Bound result 6\n  Bound result 7963\n  Bound result 144227975815786279\n  Bound result 66067\n  Bound result 90904080061668511\n  Bound result 193590374172124205\n  Bound result 12056\n  Bound result 5726\n  Bound result 127793\n  Bound result 106\n  Bound result 197672135571846364\n  Bound result 4651\n  Bound result 187340303074464591\n  Bound result 255\n  Bound result 1\n  Bound result 3417\n  Bound result 2488\n  Bound result 440002903777689215\n  Bound result 4146\n  Bound result 3386\n  Bound result 33\n  Bound result 9\n  Bound result 113504776116770685\n  Bound result 229\n  Bound result 595\n  Bound result 20\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 8.47s (8.46s CPU time)\n\nRan 1 test for test/FeaturedInvariant.t.sol:FeaturedInvariantTest\n[PASS] invariantEveryTokenIsAccountedForAndEveryCreditHasBacking() (runs: 128, calls: 16384, reverts: 0)\n\n╭-----------------+-----------------+-------+---------+----------╮\n| Contract        | Selector        | Calls | Reverts | Discards |\n+================================================================+\n| FeaturedHandler | advanceTime     | 1637  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| FeaturedHandler | boost           | 1706  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| FeaturedHandler | cancel          | 1686  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| FeaturedHandler | claim           | 1592  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| FeaturedHandler | deposit         | 1593  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| FeaturedHandler | purchase        | 1642  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| FeaturedHandler | rotateRecipient | 1630  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| FeaturedHandler | settle          | 1651  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| FeaturedHandler | toggleListing   | 1637  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| FeaturedHandler | withdraw        | 1610  | 0       | 0        |\n╰-----------------+-----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 300000000000000000\n  Bound result 300000000000000000\n  Bound result 33333333333333333\n  Bound result 50000000000000000\n  Bound result 3600\n  Bound result 1142648672808614\n  Bound result 19\n  Bound result 4692\n  Bound result 1\n  Bound result 6\n  Bound result 166666666666663065\n  Bound result 101\n  Bound result 4651\n  Bound result 338\n  Bound result 1800000000\n  Bound result 298857353127191119\n  Bound result 97\n  Bound result 5709\n  Bound result 31\n  Bound result 512\n  Bound result 31\n  Bound result 11507\n  Bound result 89067\n  Bound result 1359561208804943\n  Bound result 6353\n  Bound result 40\n  Bound result 99\n  Bound result 16383\n  Bound result 8\n  Bound result 95\n  Bound result 31\n  Bound result 256\n  Bound result 3145\n  Bound result 277483790929074252\n  Bound result 677374188650844421\n  Bound result 107\n  Bound result 4512\n  Bound result 242\n  Bound result 105\n  Bound result 205923570265742267\n  Bound result 40\n  Bound result 15437\n  Bound result 7457\n  Bound result 14518\n  Bound result 4195\n  Bound result 242\n  Bound result 18668874320293861\n  Bound result 243\n  Bound result 253\n  Bound result 11\n  Bound result 4114\n  Bound result 8453\n  Bound result 8972\n  Bound result 242\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 9.02s (9.02s CPU time)\n\nRan 1 test for test/FeaturedModelInvariant.t.sol:FeaturedModelInvariantTest\n[PASS] invariantIndividualEntitlementsAndPlacementHistoryMatchIndependentLedger() (runs: 256, calls: 24576, reverts: 0)\n\n╭----------------------+-----------------------+-------+---------+----------╮\n| Contract             | Selector              | Calls | Reverts | Discards |\n+===========================================================================+\n| FeaturedModelHandler | advanceTime           | 2079  | 0       | 0        |\n|----------------------+-----------------------+-------+---------+----------|\n| FeaturedModelHandler | boost                 | 2106  | 0       | 0        |\n|----------------------+-----------------------+-------+---------+----------|\n| FeaturedModelHandler | close                 | 2039  | 0       | 0        |\n|----------------------+-----------------------+-------+---------+----------|\n| FeaturedModelHandler | deposit               | 2040  | 0       | 0        |\n|----------------------+-----------------------+-------+---------+----------|\n| FeaturedModelHandler | donate                | 2060  | 0       | 0        |\n|----------------------+-----------------------+-------+---------+----------|\n| FeaturedModelHandler | purchase              | 2101  | 0       | 0        |\n|----------------------+-----------------------+-------+---------+----------|\n| FeaturedModelHandler | rejectOverspend       | 2033  | 0       | 0        |\n|----------------------+-----------------------+-------+---------+----------|\n| FeaturedModelHandler | rejectPlacementAction | 2019  | 0       | 0        |\n|----------------------+-----------------------+-------+---------+----------|\n| FeaturedModelHandler | rotateRecipient       | 1979  | 0       | 0        |\n|----------------------+-----------------------+-------+---------+----------|\n| FeaturedModelHandler | toggleListing         | 2043  | 0       | 0        |\n|----------------------+-----------------------+-------+---------+----------|\n| FeaturedModelHandler | transferListing       | 2007  | 0       | 0        |\n|----------------------+-----------------------+-------+---------+----------|\n| FeaturedModelHandler | withdraw              | 2070  | 0       | 0        |\n╰----------------------+-----------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 2000000000000000000\n  Bound result 2000000000000000000\n  Bound result 2000000000000000000\n  Bound result 2000000000000000000\n  Bound result 100000000000000000\n  Bound result 200000000000000000\n  Bound result 1\n  Bound result 86400\n  Bound result 1\n  Bound result 1\n  Bound result 243\n  Bound result 16\n  Bound result 185443455\n  Bound result 1849832962821823010\n  Bound result 17\n  Bound result 513\n  Bound result 32\n  Bound result 7134\n  Bound result 254\n  Bound result 7416506693389813048\n  Bound result 2637\n  Bound result 1128\n  Bound result 49102\n  Bound result 8534\n  Bound result 2\n  Bound result 50881\n  Bound result 14602\n  Bound result 20\n  Bound result 1800000000\n  Bound result 2845\n  Bound result 9486\n  Bound result 80060922183010354\n  Bound result 2309\n  Bound result 96\n  Bound result 1044103712\n  Bound result 2525133408\n  Bound result 131274776240181911\n  Bound result 3\n  Bound result 11699\n  Bound result 10693\n  Bound result 3762\n  Bound result 19601\n  Bound result 513\n  Bound result 427\n  Bound result 2146\n  Bound result 9849832962821816591\n  Bound result 1995050928770007245\n  Bound result 188665458984215123\n  Bound result 50167037178175862\n  Bound result 200281250000002892\n  Bound result 11544885046155150350\n  Bound result 9116225440864679522\n  Bound result 109895835858469049\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 21.94s (21.92s CPU time)\n\nRan 12 test suites in 21.94s (40.78s CPU time): 78 tests passed, 0 failed, 0 skipped (78 total tests)\n","passed":true},{"durationMs":80,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"FeaturedPlacementsV2.acceptOwnership()\",\"FeaturedPlacementsV2.boost(uint256,uint256)\",\"FeaturedPlacementsV2.cancel(uint256)\",\"FeaturedPlacementsV2.cancelOwnershipTransfer()\",\"FeaturedPlacementsV2.configureToken(address)\",\"FeaturedPlacementsV2.deposit(uint256)\",\"FeaturedPlacementsV2.purchase(uint256,uint256,address,uint256)\",\"FeaturedPlacementsV2.setProceedsRecipient(address)\",\"FeaturedPlacementsV2.settle(uint256)\",\"FeaturedPlacementsV2.transferOwnership(address)\",\"FeaturedPlacementsV2.withdraw(uint256,address)\",\"FeaturedPlacementsV2.withdrawProceeds(uint256,address)\",\"HookListingRegistry.acceptListingOwnership(uint256)\",\"HookListingRegistry.cancelListingTransfer(uint256)\",\"HookListingRegistry.createListing(uint256,address,(string,string,string,string,string,uint160,string,string,string,string[]))\",\"HookListingRegistry.proposeListingOwner(uint256,address)\",\"HookListingRegistry.setListingActive(uint256,bool)\",\"HookListingRegistry.updateListing(uint256,(string,string,string,string,string,uint160,string,string,string,string[]))\"],\"files\":{\".gitignore\":3,\"LICENSE\":24,\"README.md\":196,\"docs/REVIEW.md\":32,\"docs/UPSTREAM.md\":39,\"foundry.toml\":26,\"remappings.txt\":2,\"src/store/ExactStoreToken.sol\":48,\"src/store/FeaturedPlacementsV2.sol\":303,\"src/store/HookListingRegistry.sol\":217,\"src/store/StoreAdmin.sol\":46,\"test/ADDITIONAL_TESTS.md\":41,\"test/FeaturedAdversarial.t.sol\":191,\"test/FeaturedBoost.t.sol\":317,\"test/FeaturedInvariant.t.sol\":234,\"test/FeaturedModelInvariant.t.sol\":391,\"test/FeaturedPlacements.t.sol\":443,\"test/FeaturedTokenBoundary.t.sol\":252,\"test/FeaturedTransfer.t.sol\":120,\"test/FeaturedTransferInvariant.t.sol\":82,\"test/HookListingRegistry.t.sol\":265,\"test/RegistryBoundary.t.sol\":216,\"test/StoreDeployment.t.sol\":84,\"test/StorePaginationBoundary.t.sol\":114,\"test/helpers/StoreFixture.sol\":81,\"test/helpers/StoreTokenMock.sol\":82,\"test/helpers/TestToken.sol\":13},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"b9695d268509b8316f3484291b3e3a13aaec33b5829014eb4af4017e0a25acdb","verifiedTreeHash":"7643afb4a7b24e1b0fda5742fc95ef96a0241a30","verifierVersion":"0.1.0+560ab6a2"},{"checks":[{"durationMs":3273,"exitCode":0,"name":"build","output":"Compiling 35 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.19s\nCompiler run successful!\n","passed":true},{"durationMs":4690,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 8 tests for test/FeaturedAdversarial.t.sol:FeaturedAdversarialTest\n[PASS] testBoostedClosureWorksWithBlockedTransfersAndFailedProceedsWithdrawalIsRetryable() (gas: 2460814)\n[PASS] testFalseRevertingMalformedAndNoMovementTokensCannotCreateCredit() (gas: 1202299)\n[PASS] testMissingTokenCodeAfterConfigurationCannotFakeTransfer() (gas: 68265)\n[PASS] testNoReturnTokenWorksForDepositsAndWithdrawals() (gas: 264264)\n[PASS] testProceedsWithdrawalAlsoGuardsReentrancy() (gas: 1623477)\n[PASS] testRecipientCannotBlockCancellationOrSettlementAndFailedClaimRollsBack() (gas: 2098547)\n[PASS] testTaxedOrFailedWithdrawalRollsBackAndRemainsRetryable() (gas: 1194937)\n[PASS] testTokenCallbacksCannotReenterAnyCustodyMutation() (gas: 3495787)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 25.52ms (4.35ms CPU time)\n\nRan 2 tests for test/StoreDeployment.t.sol:StoreDeploymentTest\n[PASS] testFactoryLaunchOnEthereum() (gas: 15478554)\n[PASS] testFactoryLaunchOnRobinhood() (gas: 15478576)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 33.99ms (63.53ms CPU time)\n\nRan 5 tests for test/FeaturedTransfer.t.sol:FeaturedTransferTest\n[PASS] testBuyerCanClearSellersAdAndBuyItsOwnWhileOnlySellerGetsRefund() (gas: 3191852)\n[PASS] testFuzzNewOwnerCancellationConservesAllCredits(uint256) (runs: 256, μ: 1715314, ~: 1716843)\nLogs:\n  Bound result 96\n\n[PASS] testIntermediateOwnerLosesCancellationPowerButOriginalPayerKeepsIt() (gas: 1871955)\n[PASS] testNewOwnerCannotCancelAtExpiryAndCanRenewBeforeOldSettlement() (gas: 2171958)\n[PASS] testProposedOwnerCannotCancelUntilAcceptingAndAdminCannotCancel() (gas: 1841259)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 39.78ms (40.64ms CPU time)\n\nRan 21 tests for test/FeaturedPlacements.t.sol:FeaturedPlacementsV2Test\n[PASS] testAdminRotationIsTwoStepAndDoesNotControlCustody() (gas: 805991)\n[PASS] testBadTokenConfigurationAndConstructorArguments() (gas: 1022370)\n[PASS] testCancelledPlacementCannotSettleOrCancelTwice() (gas: 1589471)\n[PASS] testDepositWithdrawBelongsToDepositorAndCannotBeSwept() (gas: 693554)\n[PASS] testDirectTransfersAreNotCreditsAndCannotBeSwept() (gas: 496842)\n[PASS] testEditingInvalidatesStalePurchaseAndFeeDisclosuresAreSnapshotted() (gas: 1995045)\n[PASS] testExpirySettlementIsPermissionlessAndCannotBeRepeatedOrRefunded() (gas: 1833508)\n[PASS] testFeaturedOrderingPaginationAndLabelsSkipCancelledAndExpired() (gas: 3112072)\n[PASS] testFuzzCancellationConservesFundsAndRoundsInPayersFavor(uint256) (runs: 256, μ: 1593263, ~: 1594792)\nLogs:\n  Bound result 4671\n\n[PASS] testHalfDurationCancellationSplitsFeeAndProceedsArePullBased() (gas: 1745233)\n[PASS] testImmediateCancellationFullyRefundsThenOwnerCanWithdraw() (gas: 1651089)\n[PASS] testOnlyListingOwnerCanBuyAndUnrelatedAccountsCannotCancelIncludingAdmin() (gas: 1766137)\n[PASS] testPurchaseCostsExactlyPointOneImdAndHasTwentyFourHourAdLabel() (gas: 1481770)\n[PASS] testRecipientRotationInvalidatesStalePurchaseBeforeFundsAreLocked() (gas: 1453620)\n[PASS] testRecipientsAreSnapshottedAndChangingThemCannotTakeOldFunds() (gas: 2419926)\n[PASS] testSingleLivePlacementButCanRenewUnsettledExpiredOrCancelledPlacement() (gas: 2502718)\n[PASS] testSnapshotSurvivesEditingArchivingTransferAndRefundStaysWithPayer() (gas: 2402298)\n[PASS] testStaleTermsInactiveListingInsufficientBalanceAndDeadlineFailBeforeDebit() (gas: 1405005)\n[PASS] testUnknownIdsAndInvalidPagesFail() (gas: 229958)\n[PASS] testUnsetConfigurationAndTokenCanOnlyBeChosenOnce() (gas: 703534)\n[PASS] testZeroAmountsInvalidRecipientsAndExcessWithdrawalsFail() (gas: 629719)\nSuite result: ok. 21 passed; 0 failed; 0 skipped; finished in 43.96ms (37.21ms CPU time)\n\nRan 11 tests for test/HookListingRegistry.t.sol:HookListingRegistryTest\n[PASS] testAuditUrlAndAuditLabelNeverConferVerification() (gas: 1866252)\n[PASS] testFreeCreateAndEditPersistAllFieldsAndHistory() (gas: 1618302)\n[PASS] testFuzzUnauthorizedCallerCannotModifyListing(address) (runs: 256, μ: 842654, ~: 842726)\n[PASS] testFuzzValidLabelsRemainSearchable(uint128) (runs: 256, μ: 747238, ~: 746363)\n[PASS] testInvalidIdentityUnknownRecordsAndPagesRevert() (gas: 931804)\n[PASS] testInvalidMetadataIsAtomicAndReferencesMayBeAbsent() (gas: 1106108)\n[PASS] testLabelValidationBoundsAndDuplicates() (gas: 965451)\n[PASS] testSameHookCanHaveIndependentOwnersButNoOwnerDuplicates() (gas: 2284749)\n[PASS] testSearchByLabelChainOwnerAndVisibilityUsesBoundedAscendingCursor() (gas: 2329424)\n[PASS] testTransferCanBeCancelledOrReplaced() (gas: 1022369)\n[PASS] testTwoStepListingTransferAndDuplicateProtection() (gas: 1824321)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 52.64ms (50.96ms CPU time)\n\nRan 12 tests for test/FeaturedBoost.t.sol:FeaturedBoostTest\n[PASS] testBoostBoundaryAndClosedPlacements() (gas: 2549550)\n[PASS] testBoostCannotSpendWalletApprovalAnotherDepositOrProceeds() (gas: 2240564)\n[PASS] testBoostSurvivesArchivingAndDoesNotChangePinnedMetadata() (gas: 2252708)\n[PASS] testExpiredBoostedPlacementCanBeRenewedBeforeSettlementWithoutAffectingNewOne() (gas: 2434966)\n[PASS] testFuzzCancelBoostsPreservesOriginalRoundingAndConservation(uint256,uint128) (runs: 256, μ: 2110569, ~: 2110637)\nLogs:\n  Bound result 79016\n  Bound result 1898\n\n[PASS] testFuzzExpirySettlesBaseAndBoostToPinnedRecipient(uint128) (runs: 256, μ: 1973487, ~: 1973392)\nLogs:\n  Bound result 12648979423738\n\n[PASS] testImmediateCancelRefundsOnlyBaseAndForfeitsAllBoosts() (gas: 2394935)\n[PASS] testInvalidBoostAmountsAndIdsRevertWithoutDebiting() (gas: 1857858)\n[PASS] testNewListingOwnerCancellationDoesNotRefundBoosterOrRedirectProceeds() (gas: 2018551)\n[PASS] testPurchaseEventRetainsAdLabelAndOriginalPrice() (gas: 1303682)\n[PASS] testRankingTiesUseLowerIdAcrossPagesAndTotalsAlignAfterFiltering() (gas: 3194982)\n[PASS] testThirdPartyBoostUsesOnlyTheirDepositAndKeepsTermsPinned() (gas: 1903870)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 54.21ms (112.01ms CPU time)\n\nRan 1 test for test/FeaturedInvariant.t.sol:FeaturedInvariantTest\n[PASS] invariantEveryTokenIsAccountedForAndEveryCreditHasBacking() (runs: 128, calls: 16384, reverts: 0)\n\n╭-----------------+-----------------+-------+---------+----------╮\n| Contract        | Selector        | Calls | Reverts | Discards |\n+================================================================+\n| FeaturedHandler | advanceTime     | 1637  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| FeaturedHandler | boost           | 1706  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| FeaturedHandler | cancel          | 1686  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| FeaturedHandler | claim           | 1592  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| FeaturedHandler | deposit         | 1593  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| FeaturedHandler | purchase        | 1642  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| FeaturedHandler | rotateRecipient | 1630  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| FeaturedHandler | settle          | 1651  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| FeaturedHandler | toggleListing   | 1637  | 0       | 0        |\n|-----------------+-----------------+-------+---------+----------|\n| FeaturedHandler | withdraw        | 1610  | 0       | 0        |\n╰-----------------+-----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 300000000000000000\n  Bound result 300000000000000000\n  Bound result 33333333333333333\n  Bound result 50000000000000000\n  Bound result 2\n  Bound result 15478990920987757\n  Bound result 300000000000000000\n  Bound result 4692\n  Bound result 1\n  Bound result 4288\n  Bound result 566666666666657685\n  Bound result 3600\n  Bound result 4616\n  Bound result 282\n  Bound result 146963591191081044\n  Bound result 16383\n  Bound result 24598\n  Bound result 1\n  Bound result 5958\n  Bound result 174607431768211455\n  Bound result 1\n  Bound result 32904407924044806\n  Bound result 89067\n  Bound result 1359561208804943\n  Bound result 1\n  Bound result 5\n  Bound result 255\n  Bound result 1024\n  Bound result 7\n  Bound result 174607431768211455\n  Bound result 97\n  Bound result 2849\n  Bound result 277483790929074252\n  Bound result 565690403615618577\n  Bound result 254\n  Bound result 3967\n  Bound result 91\n  Bound result 255\n  Bound result 7155\n  Bound result 5\n  Bound result 14880\n  Bound result 7328\n  Bound result 13590\n  Bound result 3257\n  Bound result 127\n  Bound result 241\n  Bound result 244\n  Bound result 244\n  Bound result 3600\n  Bound result 8025\n  Bound result 8\n  Bound result 8073\n  Bound result 91\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.50s (4.50s CPU time)\n\nRan 1 test for test/FeaturedTransferInvariant.t.sol:FeaturedTransferInvariantTest\n[PASS] invariantEveryTokenIsAccountedForAndEveryCreditHasBacking() (runs: 128, calls: 16384, reverts: 0)\n\n╭-------------------------+------------------------+-------+---------+----------╮\n| Contract                | Selector               | Calls | Reverts | Discards |\n+===============================================================================+\n| FeaturedTransferHandler | advanceTime            | 1360  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | boost                  | 1345  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | cancel                 | 1375  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | cancelAsCurrentOwner   | 1352  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | claim                  | 1317  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | deposit                | 1374  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | purchaseAsCurrentOwner | 1370  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | rotateRecipient        | 1424  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | settle                 | 1362  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | toggleCurrentListing   | 1332  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | transferListing        | 1401  | 0       | 0        |\n|-------------------------+------------------------+-------+---------+----------|\n| FeaturedTransferHandler | withdraw               | 1372  | 0       | 0        |\n╰-------------------------+------------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 300000000000000000\n  Bound result 300000000000000000\n  Bound result 33333333333333333\n  Bound result 50000000000000000\n  Bound result 125307\n  Bound result 763600050\n  Bound result 172800\n  Bound result 2002140241\n  Bound result 97\n  Bound result 100000000000000000\n  Bound result 154975\n  Bound result 290404790215019570\n  Bound result 2651282218499739\n  Bound result 915\n  Bound result 2354\n  Bound result 96\n  Bound result 91\n  Bound result 2734\n  Bound result 408\n  Bound result 1849\n  Bound result 79083\n  Bound result 254\n  Bound result 300000000000000000\n  Bound result 4137\n  Bound result 1\n  Bound result 4288\n  Bound result 8667\n  Bound result 144227975815786279\n  Bound result 66067\n  Bound result 228239293732799535\n  Bound result 193590374172124205\n  Bound result 4288\n  Bound result 12729\n  Bound result 6047\n  Bound result 16963463214819669\n  Bound result 174424073309946448\n  Bound result 127793\n  Bound result 253\n  Bound result 84822221458145457\n  Bound result 4816\n  Bound result 1005\n  Bound result 127\n  Bound result 1\n  Bound result 238623218341477740\n  Bound result 2488\n  Bound result 10\n  Bound result 4548\n  Bound result 1745323478768382\n  Bound result 6976722610064109\n  Bound result 30\n  Bound result 4\n  Bound result 1028\n  Bound result 525\n  Bound result 5\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.61s (4.60s CPU time)\n\nRan 8 test suites in 4.61s (9.36s CPU time): 61 tests passed, 0 failed, 0 skipped (61 total tests)\n","passed":true},{"durationMs":45,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"FeaturedPlacementsV2.acceptOwnership()\",\"FeaturedPlacementsV2.boost(uint256,uint256)\",\"FeaturedPlacementsV2.cancel(uint256)\",\"FeaturedPlacementsV2.cancelOwnershipTransfer()\",\"FeaturedPlacementsV2.configureToken(address)\",\"FeaturedPlacementsV2.deposit(uint256)\",\"FeaturedPlacementsV2.purchase(uint256,uint256,address,uint256)\",\"FeaturedPlacementsV2.setProceedsRecipient(address)\",\"FeaturedPlacementsV2.settle(uint256)\",\"FeaturedPlacementsV2.transferOwnership(address)\",\"FeaturedPlacementsV2.withdraw(uint256,address)\",\"FeaturedPlacementsV2.withdrawProceeds(uint256,address)\",\"HookListingRegistry.acceptListingOwnership(uint256)\",\"HookListingRegistry.cancelListingTransfer(uint256)\",\"HookListingRegistry.createListing(uint256,address,(string,string,string,string,string,uint160,string,string,string,string[]))\",\"HookListingRegistry.proposeListingOwner(uint256,address)\",\"HookListingRegistry.setListingActive(uint256,bool)\",\"HookListingRegistry.updateListing(uint256,(string,string,string,string,string,uint160,string,string,string,string[]))\"],\"files\":{\".gitignore\":3,\"LICENSE\":24,\"README.md\":196,\"docs/REVIEW.md\":32,\"docs/UPSTREAM.md\":39,\"foundry.toml\":26,\"launch.json\":17,\"remappings.txt\":2,\"src/store/ExactStoreToken.sol\":48,\"src/store/FeaturedPlacementsV2.sol\":303,\"src/store/HookListingRegistry.sol\":217,\"src/store/StoreAdmin.sol\":46,\"test/FeaturedAdversarial.t.sol\":191,\"test/FeaturedBoost.t.sol\":317,\"test/FeaturedInvariant.t.sol\":234,\"test/FeaturedPlacements.t.sol\":443,\"test/FeaturedTransfer.t.sol\":120,\"test/FeaturedTransferInvariant.t.sol\":82,\"test/HookListingRegistry.t.sol\":265,\"test/StoreDeployment.t.sol\":84,\"test/helpers/StoreFixture.sol\":81,\"test/helpers/StoreTokenMock.sol\":82,\"test/helpers/TestToken.sol\":13},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"c7e7b6f2b30961e441503690d7121875bce7cdb71ea7a2fe64cdb4624b0d3e7b","verifiedTreeHash":"5780c2dfead148b662dea6ecbae64e0fd638bd58","verifierVersion":"0.1.0+560ab6a2"}]}