{"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":"23a23277-83d7-4d83-959e-5bf659af2ab7","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"725aec2dd8352afd08a749fedea5b71785847afa9f4580fe0b5bb326504383d1","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":"1348a414b13db08edad2f61a41f87eed17cbbef31a622ed468fa101188e55d28","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":null,"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":"waiting"},{"acceptedSubmissionHash":"71d12f14b9fb33e0a3d54282a32a7e1c3d40fd5dbbcf63a9202035d6c8d79079","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":"da8273377324468945a2b6b7119293d33b621936cbf18f5e7cfd174a22e1a0b9","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":"f7ddbf5564ad7b425df25b32e7fae961ba8fdf11130dd3c1223036849fc6c762","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"6b8d2c2ebd3301cff2be8edb1e1cc6ba65c858c02fe7247be81722b969842265","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"6b8d2c2ebd3301cff2be8edb1e1cc6ba65c858c02fe7247be81722b969842265","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":null,"dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"failed"},{"acceptedSubmissionHash":null,"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":"ready"}],"objective":"Token name: Self Made\nToken symbol: SELF\nLogo: a bold, clean logo of a small robot hand drawing itself with a pencil, lime green on black, readable at 64 px, no real brands or people\n\nThe token that builds itself. Every trade pays a small fee in IMD into an on-chain workshop contract, and that contract hires the Identity.md swarm by itself, through the Identity.md Intake contract, to make things for the coin: memes, videos, research and audits. Nobody decides when; the fees do.\n\nWho can change it: nobody. No owner, no admin, no upgrade path, no setters on any contract after deploy.\n\nContracts:\n1. SelfHook, a Uniswap v4 hook on the SELF/IMD pool this launch opens. On every swap it takes a 2% hook fee from the IMD side of the swap and keeps it in IMD (do NOT sell it for ETH). The fee goes straight to the Workshop contract. No other effects: no max buy, no blacklist, no pausing, no owner functions. It never reverts a real swap.\n2. Workshop, the contract that hires the swarm. It holds the IMD from the hook.\n   - Intake (Identity.md's on-chain door, same address on every chain): 0xa43e6f75ee006411f79ac1c84120606c2330de82. Its function: request(bytes32 action, bytes body, (address target, bytes4 selector) callback, address asset, uint256 amount) returns (bytes32 requestId). action is the right-padded bytes32 string \"job.open@job-1\". body is the job.open JSON as UTF-8 bytes (max 16,384 bytes). The price is read from Intake.priceOf(action, IMD); never hardcode 0.5 IMD. Workshop approves Intake for exactly the price before each call.\n   - commission(): public, anyone can call it. It works only when the Workshop holds at least price + bounty IMD and at least 6 hours passed since the last commission. It picks the next work order from a fixed list of 4 templates stored as constants (rotating 0,1,2,3,0…), calls Intake.request with that body and a callback to Workshop.onResult, and pays the caller a bounty of 2% of the price in IMD for the gas. Max 4 commissions per UTC day.\n   - The 4 templates, fixed at deploy (nobody can change them), stored as constant bytes of these exact job.open JSON bodies (each already passes Identity.md's /requests/check):\n     a) {\"objective\":\"Make one original meme image for Self Made ($SELF), the token that builds itself: a coin whose trading fees hire an AI swarm to make its own content. Funny, crypto-native, lime green and black. No real people, brands or copyrighted characters.\",\"skill\":\"create-image\",\"outputs\":[{\"name\":\"image\",\"path\":\"artifacts/image.png\",\"mediaType\":\"image/png\"}],\"github\":false}\n     b) {\"objective\":\"Make one 8-15 second vertical video for Self Made ($SELF), the token whose trading fees hire an AI swarm to make its own content. Show a coin building itself. Lime green and black. No real people, brands or copyrighted characters.\",\"skill\":\"create-video\",\"outputs\":[{\"name\":\"video\",\"path\":\"artifacts/video.mp4\",\"mediaType\":\"video/mp4\"}],\"github\":false}\n     c) {\"objective\":\"Write a short public report on Self Made ($SELF) on Robinhood Chain: read its SELF/IMD pool and its Workshop contract's commissions and delivered work, and explain in plain words how the self-building loop is doing. Facts only, no price predictions, no financial advice.\",\"skill\":\"research-report\",\"outputs\":[{\"name\":\"report\",\"path\":\"artifacts/report.md\",\"mediaType\":\"text/markdown\"}],\"github\":false}\n     d) {\"objective\":\"Security review of the verified SelfHook and Workshop contracts of the Self Made ($SELF) launch on Robinhood Chain: report any findings in plain words with severity.\",\"skill\":\"research-report\",\"outputs\":[{\"name\":\"report\",\"path\":\"artifacts/report.md\",\"mediaType\":\"text/markdown\"}],\"github\":false}\n   - onResult(bytes32 requestId, bytes32 resultHash, string uri): callable only by the Intake contract. Stores the result and emits Built(requestId, templateIndex, resultHash, uri). Must use little gas (under 150k). Also a failure hook via requestWithFailure: onFailure(bytes32 requestId, uint8 status, bytes32 reason, uint16 agreed, uint16 answered, bytes signature), callable only by Intake, emits Failed(requestId, status, reason).\n   - No owner, no admin, no withdraw: the IMD in the Workshop can only leave through Intake.request (and the caller bounty). Views: nextTemplate(), lastCommissionAt(), commissionsToday(), canCommission(), works(i).\n3. The website reads the Built events and shows a live gallery: \"Everything this token made for itself\", newest first, each item with its link, plus the Workshop's IMD balance and a countdown to the next possible commission.\n\nTests: fork tests against Robinhood Chain that call the real Intake contract (price read from priceOf), check the hook fee lands in the Workshop in IMD, commission() reverts below the balance / within 6 hours / after 4 per day, the bounty is paid, onResult and onFailure reject any caller but Intake.\n\nToken name: Self Made\nToken symbol: SELF","parentJobId":null,"planHash":"0c8a86bd37a74173a1d68338cc8f7e689af2de4eb5be4af142fad4d040a4f181","previousHash":"9b4f8a41c9479648b7ea7b0d94591ac002aa33236840b9af82378cd82e676207","projectId":"23a23277-83d7-4d83-959e-5bf659af2ab7","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-1725-self-made"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51315","feedbackHash":"4212bb6e15ecc3f8c56f74b347d335ea7dc2cea6cd7cc2d811f07f8854d5a9f9","nodeKey":"audit_economics","submissionHash":"725aec2dd8352afd08a749fedea5b71785847afa9f4580fe0b5bb326504383d1","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52591","feedbackHash":"45a94e38585892277c365cfb2d4ff9bf32b6b9db8d6c811e570f43b1a90cba1b","nodeKey":"audit_flow","submissionHash":"1348a414b13db08edad2f61a41f87eed17cbbef31a622ed468fa101188e55d28","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52680","feedbackHash":"582ec4bf3e1471d207248d82734730818274caa53a89d31fc6a66f722ad6e96f","nodeKey":"audit_math","submissionHash":"71d12f14b9fb33e0a3d54282a32a7e1c3d40fd5dbbcf63a9202035d6c8d79079","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52255","feedbackHash":"ff0ebb8e3fae500c98b05f7d78a50ec29b97cada2daf6cb7e97b3cb4167e50bf","nodeKey":"audit_permissions","submissionHash":"da8273377324468945a2b6b7119293d33b621936cbf18f5e7cfd174a22e1a0b9","tag1":"review:submission","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"bff203036724a451005d8c951fd6158aafb31fc082ad45f79dfb102ed9480a43","state":"blocked","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"5061b3ff85c1f0c1","findings":[{"citation":"resolved","description":"Execution trace across the request/deliver boundary. Workshop.commission() spends the Intake price, then the only way a work order leaves State.Pending is a call from the Intake to onResult or onFailure. onResult reverts for any URI longer than MAX_URI_BYTES (2,048 bytes) before it reaches _pending, so if the network delivers a longer URI the delivery call fails, no Built event is emitted, the work stays Pending with its price already spent, and the gallery never shows it. The bound is tighter than the gas budget that motivated it: on a Robinhood Chain fork the live Intake's callbackGas() is 200,000, and a cold-storage onResult with a 2,048-byte URI measured 136,184 gas, so a URI of roughly 3,000 bytes would still fit the Intake's budget yet is refused. The brief fixes no URI length, and nothing in the Workshop can retry or re-request a rejected delivery; the same permanent-Pending outcome follows for onFailure with any status other than 1 or 2 (src/Workshop.sol:120), though the supplied references document only statuses 1 and 2. This is a liveness gap under a specific, externally controlled input rather than a loss of funds beyond the one price already paid; it is reported so the author can confirm with Identity.md that job.open delivery URIs are always short (a URL or content identifier) or raise the cap toward what 200k gas affords.","line":105,"path":"src/Workshop.sol","reproduction":"State: Workshop funded with >= price + price/50 IMD; commission() called once, returning requestId R (works[0].state == Pending). Call, as the Intake address 0xa43e6f75ee006411f79ac1c84120606c2330de82: onResult(R, keccak256(\"artifact\"), uri) where uri is any string of 2,049 bytes. Expected (brief): the result is stored and Built(R, 0, hash, uri) is emitted. Actual: the call reverts with URITooLong(); works[0] stays Pending, no Built event, and the 0.5 IMD already pulled by the Intake in commission() cannot be recovered or re-spent on a retry. The project's own test test_oversizedURIAndInvalidFailureStatusReject in test/Workshop.t.sol:224 exercises exactly this revert. On a fork of chain 4663 the same delivery with a 2,048-byte URI succeeds at 136,184 gas, under the Intake's 200,000-gas callback budget, which shows the cap, not the gas budget, is what rejects the delivery.","severity":"low","snippet":"        if (bytes(uri).length > MAX_URI_BYTES) revert URITooLong();","title":"A delivered result with a URI over 2,048 bytes is rejected by onResult and the paid work stays Pending forever"}],"hash":"1348a414b13db08edad2f61a41f87eed17cbbef31a622ed468fa101188e55d28","nodeId":"53e982cc-fbb0-4d70-9092-02682ca6c0dd","outcome":"completed","summary":"The review is complete and `.imd-findings.json` is written at the repository root with one finding and fifteen coverage rows, one for each of the eleven listed entry points plus four invariants.\n\n**Outcome:** no critical, high or medium defect survived tracing in the Execution Trace, Periphery and First Principles areas. The one substantiated finding is low severity.\n\n**What I verified**\n\n- **Hook control flow.** The self-quote in `beforeSwap` cannot recurse, because the vendored v4 `Hooks.beforeSwap` returns early when the hook itself is the swapper. The quote always reverts, so pool state and deltas roll back. I worked the floor arithmetic for all four swap modes in both currency orders, including price-limited partial fills, and the fee is 2% of gross IMD with exact-output sellers receiving exactly the requested net. The take-or-mint fee path nets the hook's delta to zero in every branch, and the redeem path can only send IMD to the immutable Workshop.\n- **Workshop against the live Intake.** On a fork of Robinhood Chain I ran `commission()` against the real Intake with full traces. It reads the price from `priceOf` (0.5 IMD today), the Intake pulls exactly the approved amount, the allowance is zeroed, the bounty is paid, and no synchronous callback happens inside the request, so the reentrancy leads from Slither and Aderyn are guarded and not findings. The three selectors the Workshop calls exist in the deployed Intake bytecode.\n- **Periphery.** The four job bodies match the brief byte-for-byte and parse as JSON. The miner script encodes the constructor arguments in the right order with the right flag mask. The website's event topic and nine selectors match the compiled ABI, and its web tests pass.\n\n**The finding.** A delivered result with a URI over 2,048 bytes makes `onResult` revert, so the paid work stays Pending forever with no retry path. The cap is tighter than the gas budget that motivated it: the Intake gives callbacks 200k gas and a maximum-length cold delivery measured about 136k on the fork. The author documents this as a chosen bound, so I reported it as low with the exact call that triggers it, for the author to confirm Identity.md's delivery URIs are always short or raise the cap.\n\nThe scratch probe test was removed; the tree is unchanged apart from the findings file.","treeHash":null,"usage":{"cachedInputTokens":1432283,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":46524,"runtime":"claude","turns":32,"wallClockMs":675699}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"55ad97181c96c1ba","findings":[],"hash":"2af29c7b709fd9d0e820861983aa78cfeef48aad3dc8250e4e5467b64704ff99","nodeId":"1d1a8177-ffbf-41c9-ac13-d96a1cdef59f","outcome":"failed","summary":"needs input: SelfHook's constructor requires a Workshop address as its third argument (workshop_). No deployed Workshop address is supplied; web/config.json has workshop set to null. The documented placeholders $poolManager, $token, $owner and $factory do not resolve Workshop. A complete launch.json cannot encode this required argument without an invented address or unsupported placeholder, and this assignment does not permit changing the contracts. — What is the deployed address of this launch's Workshop contract on Robinhood Chain to use as SelfHook's third constructor argument?","treeHash":null,"usage":{"cachedInputTokens":566912,"inputTokens":64663,"model":"gpt-6-astra","outputTokens":3408,"runtime":"codex","turns":3,"wallClockMs":121016}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"d7e6877fd9e388bb","findings":[{"citation":"resolved","description":"Seam: boundary x invariant. The invariant 'every hook fee lands in the Workshop in IMD' is kept only on the take() fast path. On the catch path the fee becomes an ERC-6909 claim owned by the hook and nothing in the hook ever converts it back: a later swap's _pay() only moves the new fee, even when the manager by then holds far more IMD than the outstanding claim. The launch factory seeds the pool with tokens only, so at launch the PoolManager holds zero IMD and the very first exact-input buy (the launch's normal first trade) always takes this path. The only way out is redeemFees(), which pays no bounty, is not called by Workshop.commission(), and is not wired into the website (web/app.mjs only sends commission()). Until someone volunteers, the Workshop balance, canCommission() and the site's 'Funding' countdown all understate the fees actually earned, and the brief's 'the fee goes straight to the Workshop contract' does not hold for those swaps. Funds are not lost (claims are only burnable to the fixed Workshop), so severity is low. Minimal fix that preserves the design: in _pay(), when take() succeeds (or in afterSwap), also burn any outstanding hook claim up to IMD.balanceOf(poolManager) and take it to the Workshop, so claims self-heal on the next funded swap; keep redeemFees() as a fallback.","line":156,"path":"src/SelfHook.sol","reproduction":"State: fresh PoolManager, pool initialized at tick 120000 (IMD = currency0) or -120000 (IMD = currency1) and seeded with a single-sided SELF position [-120000,120000], liquidity 1000e18, so IMD.balanceOf(poolManager) == 0 (the launch's token-only seed). Call 1: router exact-input buy of 100e18 IMD. beforeSwap quotes, fee = 2e18, take() reverts (manager holds 0 IMD), catch mints a 2e18 IMD claim to the hook: manager.balanceOf(hook, IMD.toId()) == 2e18, IMD.balanceOf(workshop) == 0. Call 2: identical 100e18 buy. Now IMD.balanceOf(poolManager) == 100e18 before the callback, take() succeeds and the Workshop receives only this swap's 2e18. Expected: the Workshop holds 4e18 after two 100 IMD buys (2% of gross each). Actual: Workshop 2e18, hook claim still 2e18, manager IMD 198e18. The claim persists through any number of further swaps until a third party calls redeemFees(). Reproduced with test/scratch/Seams.t.sol::test_deferredClaimsAreNotSweptByLaterSwaps (logs: 'claim after second buy: 2000000000000000000', 'shop after second buy: 2000000000000000000', 'manager IMD after second buy: 198000000000000000000').","severity":"low","snippet":"            poolManager.mint(address(this), IMD.toId(), fee);","title":"Deferred IMD fee claims are never swept by later swaps; first-buy fees on a token-only seeded pool sit in the hook until an unincentivized redeemFees() call"},{"citation":"resolved","description":"Math precision: all four fee formulas (floor(A/50) on exact-input buys, floor(N/49) on exact-output sells, floor(in/49) and floor(out/50) in afterSwap) truncate toward zero, so a swap whose gross IMD side is 1..49 wei pays no hook fee, and every swap under-pays by up to 1 wei against an exact 2%. The auditor checklist expects fees to round up. With 18-decimal IMD the leak is at most 1 wei per swap and splitting a trade into sub-50-wei pieces costs far more gas than it saves, so this is informational and the README already documents 'sub-50-unit fees round to zero'. No change is required; if an exact 2% is wanted, round the fee up ((gross + 49) / 50) and keep the budget cap.","line":119,"path":"src/SelfHook.sol","reproduction":"Same pool as finding 1 after one 1e18 buy so the manager holds IMD. Exact-input buy of 49 wei IMD: Workshop balance unchanged (fee 0). Exact-input buy of 50 wei: Workshop balance +1 wei. Reproduced with test/scratch/Seams.t.sol::test_tinyGrossPaysZero (logs 'fee on 49 wei: 0', 'fee on 50 wei: 1').","severity":"info","snippet":"            fee = raw < 0 ? uint256(-raw) / 49 : uint256(raw) / 50;","title":"Hook fee rounds down and is zero for any swap whose gross IMD is below 50 wei"}],"hash":"71d12f14b9fb33e0a3d54282a32a7e1c3d40fd5dbbcf63a9202035d6c8d79079","nodeId":"d10d12db-a03e-4264-b74b-caada5279030","outcome":"completed","summary":"Review complete. The findings file is written and validated, and no repository file was changed.\n\n**Result:** the fee and cadence arithmetic holds. I traced all four swap modes by hand and with targeted tests, including the budget-cap edges where the floor identities could break, and found no value leak. Two findings are recorded in `.imd-findings.json`, both low or informational, with a 14-row coverage record answering every listed entry point.\n\n- **Low, deferred fee claims never sweep themselves** (`src/SelfHook.sol:156`). On a token-only seeded pool the manager holds no IMD, so the launch's first buy always defers its fee as an ERC-6909 claim. Later swaps pay their own fee but never burn the old claim, even when the manager holds plenty of IMD. The only exit is an unincentivized call to redeemFees, which neither the Workshop nor the website ever makes. Funds are not at risk, but the Workshop balance and the site's countdown understate earned fees. Reproduced: two 100 IMD buys leave the Workshop with 2 IMD and the hook with a 2 IMD claim while the manager holds 198 IMD.\n- **Info, fee truncates to zero under 50 wei gross** (`src/SelfHook.sol:119`). Reproduced: 49 wei pays 0, 50 wei pays 1. Dust only.\n\n**Verified and holding:** exact-input and exact-output full and partial fills in both currency orders, the r=49 and s=48 cap boundaries, int128 cast bounds, the quote's revert decoding, zero-netting hook deltas, the Workshop's 6-hour and 4-per-day timing, overflow-safe affordability check, bounty rounding, and the URI gas budget. A 2048-byte delivery costs 137,593 gas with cold slots, so the 150k limit holds. Website amounts stay in BigInt and the countdown uses chain time.\n\n**Not reached in depth:** access-control and economic lenses outside my area beyond what the math traces touched, and the live Intake ABI beyond the repository's recorded fork evidence.","treeHash":null,"usage":{"cachedInputTokens":1830463,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":41268,"runtime":"claude","turns":36,"wallClockMs":590242}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"e3a598aae0640402","findings":[{"citation":"resolved","description":"Flow gap (periphery x first principles). The brief specifies onFailure(bytes32,uint8,bytes32,uint16,uint16,bytes) as 'callable only by Intake, emits Failed(requestId, status, reason)' with no restriction on the status value. The Workshop adds a check that reverts for every status except 1 and 2. The Intake delivers each callback once, inside a try with a 200k gas stipend, and does not retry a reverted delivery. Today the version 2 Intake sends only status 1 (refused) and 2 (no agreement), so nothing is lost at the moment; but the Workshop is immutable and the status is an unsigned, Intake-controlled enum that is appended to over time. The first time the Intake reports a different terminal status (expired, cancelled, delivery failed), the Workshop silently drops it: the price was already spent, the work stays Pending forever, the gallery never learns the job ended, and nothing on the Workshop side can clear the entry. The check does not protect any funds: onFailure moves no value and the state transition Pending -> Failed is the same for every status, so the restriction only loses information. Minimal fix: drop the status check (or restrict only status == 0 if a zero value must be excluded) and emit Failed with whatever status the Intake sends.","line":120,"path":"src/Workshop.sol","reproduction":"State: Workshop funded with >= price + price/50 IMD; commission() called once by anyone, returning requestId R (works[0] is Pending). Call, from the Intake address (vm.prank(0xa43e6F75ee006411F79Ac1C84120606C2330DE82)): shop.onFailure(R, 3, bytes32(\"expired\"), 0, 0, \"\") with 200_000 gas. Expected per brief: Failed(R, 3, \"expired\") emitted and works[0].state == Failed. Actual: reverts with InvalidFailureStatus(); works[0].state stays Pending (0); the same call with status 1 or 2 succeeds. Verified with a scratch Foundry test (mock Intake pulling the price, real Workshop): the status-3 delivery returns ok == false and works(0).state == Pending, while status 1 moves it to Failed.","severity":"low","snippet":"        if (status != 1 && status != 2) revert InvalidFailureStatus();","title":"onFailure refuses any failure status other than 1 or 2, so a paid request whose failure arrives with another status stays Pending forever and no Failed event is emitted"},{"citation":"resolved","description":"Invariant note, no funds at risk. The brief says the fee 'goes straight to the Workshop contract'. The hook pays during the swap, before the swapper settles, so the PoolManager must already hold at least `fee` IMD for `take` to succeed. The launch factory seeds the SELF/IMD pool with SELF only (README: 88% of supply to the pool; no IMD), so on the very first buy the manager holds 0 IMD and the fee becomes a hook-owned claim rather than Workshop balance. The same happens whenever the pool's IMD reserve is below 2% of a buy's IMD input (e.g. after heavy selling). Claims are safe (only redeemable to the fixed Workshop, anyone can call redeemFees()), but nothing redeems them automatically: Workshop.commission()/canCommission() read IMD.balanceOf(workshop) only, and the website shows the same balance, so deferred fees do not count toward the price + bounty threshold or the displayed balance until an external caller spends gas on redeemFees(). This is documented in the README and is acceptable under the brief; it is recorded so the gallery/keeper side plans for it (e.g. the site's commission button, or any keeper, calls redeemFees() first when the hook's claim balance is non-zero). An optional in-contract improvement: in _pay, when `take` succeeds and the hook already holds claims, burn and take those claims in the same unlock so later swaps sweep earlier deferred fees without a separate call.","line":153,"path":"src/SelfHook.sol","reproduction":"State: fresh PoolManager, SELF/IMD pool initialized by the hook and seeded with SELF-only liquidity above the current price (IMD.balanceOf(poolManager) == 0). Call: a router swaps exact-input 100e18 IMD for SELF (zeroForOne per currency order, amountSpecified = -100e18). Expected per brief: IMD.balanceOf(workshop) increases by 2e18 in the same transaction. Actual: beforeSwap computes fee = 2e18, poolManager.take reverts (manager holds no IMD yet), the catch mints a 2e18 IMD ERC-6909 claim to the hook and emits FeeDeferred(2e18); IMD.balanceOf(workshop) stays 0 and canCommission() stays false until someone calls hook.redeemFees(), after which the Workshop balance becomes 2e18. The repository's own test test_insufficientManagerIMDDefersAndAnyoneRedeemsToWorkshop shows the same mechanism with a large buy.","severity":"info","snippet":"        try poolManager.take(IMD, workshop, fee) {\n            emit FeePaid(fee);\n        } catch {\n            poolManager.mint(address(this), IMD.toId(), fee);\n            emit FeeDeferred(fee);\n        }","title":"Hook fees deferred as ERC-6909 claims are invisible to the Workshop (balance, canCommission, gallery) until someone calls redeemFees(); on the launch pool seeded with SELF only, the first buy's fee is"},{"citation":"resolved","description":"Economic-security note, no code change requested. SelfToken is a plain ERC-20 with no transfer restrictions, and the brief explicitly forbids any (no max buy, no blacklist, no owner). Consequently the 'every trade pays a small fee' funding loop only holds for trades routed through the hooked pool: anyone can initialize a second Uniswap v4 SELF/IMD or SELF/ETH pool without a hook (or a v3/v2 pool), and once that pool is deeper or cheaper (its traders save the 2%), routers and aggregators send volume there and the Workshop's income falls to the fee on whatever residual volume still crosses the hooked pool. The hook's beforeInitialize only prevents other pools from using *this hook*; it cannot prevent pools that do not name it. This is inherent to a hook-fee design on a free token and is the trade-off the brief chose; it is recorded so the economics are stated plainly: Workshop funding is proportional to hooked-pool volume, not to SELF volume.","line":7,"path":"src/SelfToken.sol","reproduction":"State: launch complete, hooked pool live. Call: any account calls PoolManager.initialize(PoolKey{currency0/currency1 = SELF and IMD in address order, fee = 3000, tickSpacing = 60, hooks = address(0)}, sqrtPrice) and adds liquidity, then swaps 100e18 IMD for SELF on that key. Expected under the brief's funding model: 2e18 IMD reaches the Workshop. Actual: 0 IMD reaches the Workshop; SelfHook is never called because the key does not name it, and SelfToken.transfer imposes nothing.","severity":"info","snippet":"contract SelfToken is ERC20 {\n    constructor() ERC20(\"Self Made\", \"SELF\") {\n        _mint(msg.sender, 1_000_000_000 ether);","title":"The 2% funding fee is bound to the one hooked SELF/IMD pool; SELF can be traded on any other venue with no fee to the Workshop"}],"hash":"725aec2dd8352afd08a749fedea5b71785847afa9f4580fe0b5bb326504383d1","nodeId":"e294db41-14da-47d9-9c0b-5fb852377b20","outcome":"completed","summary":"The review is complete. The findings file is written, its snippets match the tree, and the working tree is clean apart from `.imd-findings.json`.\n\n**Outcome: no fund-loss or invariant defect in the economics.** The hook's 2% fee math holds across all four swap modes and partial fills, the deferred-claim fallback nets to zero, and the Workshop's IMD can leave only through the Intake price and the 2% caller bounty. I confirmed against the live Robinhood Intake today that the request path and bounty work at the 0.5 IMD price read from `priceOf`.\n\nFindings reported, most severe first:\n\n- **Low, Workshop.onFailure.** It reverts for any status other than 1 or 2. The brief's callback has no such restriction, and the Intake delivers once without retry. A future terminal status leaves a paid job Pending forever with no Failed event. Confirmed with a scratch test: status 3 is refused, status 1 is accepted.\n- **Info, SelfHook fee deferral.** On the launch pool seeded with SELF only, the first buy's fee always becomes an ERC-6909 claim rather than Workshop balance. The Workshop and gallery do not see it until someone calls redeemFees. Safe, documented, but worth planning for on the keeper and website side.\n- **Info, fee bypass.** SELF is a plain ERC-20 by the brief's own rules, so a second pool without the hook pays nothing to the Workshop. Recorded as the economic trade-off, not as a change request.\n\nCoverage: all 11 listed entry points have rows, 8 hold, 3 point at the findings above, plus one invariant row and one honest unreached row for manifest wiring, which is outside my area. One note for the manifest step: the hook takes the Workshop address as a constructor argument with no placeholder, so the Workshop must be deployed first and its real address written literally.","treeHash":null,"usage":{"cachedInputTokens":1307273,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":38586,"runtime":"claude","turns":35,"wallClockMs":555791}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"8b35536ee4821baa","findings":[],"hash":"95400ce5229827af169fc7d822a85d25120f45b6eea7b8ce93125240dcacc481","nodeId":"c4f01cfa-499b-4b2d-9bef-a20028726dd6","outcome":"failed","summary":"This content was flagged for possible cybersecurity risk. If this seems wrong, try rephrasing your request. If you’re doing authorized security work that requires more cyber permissive safeguards, apply for Daybreak access via https://platform.openai.com/settings/organization/status-and-access before retrying.","treeHash":null,"usage":{"cachedInputTokens":0,"inputTokens":0,"model":"gpt-6-astra","outputTokens":0,"runtime":"codex","turns":4,"wallClockMs":650067}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"e06554fd2816f9d7","findings":[{"citation":"resolved","description":"Area: Asymmetry (Step 6, a defensive check that should not exist) and Access Control (the guard is stricter than the caller it trusts). The brief defines the failure hook as 'callable only by Intake, emits Failed(requestId, status, reason)'. The Intake is the sole trusted caller, and the only thing the Workshop should check about the call is who sent it and whether the request is its own and still pending. The extra whitelist on `status` means a failure the real Intake delivers with any status code outside {1, 2} (the oracle-consumer reference documents 1 and 2 today and does not promise the set is closed) reverts inside the Intake's single 200k-gas delivery attempt. The Intake records the callback as not delivered and never retries, so the work order is stuck in State.Pending forever, the Failed event is never emitted, the gallery and `works(i)` show a job that is still in progress, and no on-chain record of the failure reason exists. `onResult` has no equivalent restriction on `resultHash`, so the two callbacks are asymmetric: one trusts the Intake's data, the other second-guesses it. No funds are at stake (the price was already spent and failure callbacks never move value), which is why this is low. Fix: drop the status check (or only emit it as data), keeping `onlyIntake` and `_pending` as the sole guards, so every failure the Intake reports is recorded exactly as the brief specifies.","line":120,"path":"src/Workshop.sol","reproduction":"State: Workshop funded with >= price + bounty IMD; `commission()` called once, returning requestId R (works[0] is Pending). Call: from address 0xa43e6f75ee006411f79ac1c84120606c2330de82 (the Intake), `onFailure(R, 3, bytes32(\"expired\"), 0, 0, \"\")` with 200,000 gas. Expected (per brief): the call succeeds, works[0].state == Failed, `Failed(R, 3, \"expired\")` is emitted. Actual: the call reverts with `InvalidFailureStatus()`; works[0].state stays Pending (0) permanently and no Failed event is ever emitted. Scratch test `test/scratch/FailureStatus.t.sol::test_unknownFailureStatusIsRefusedAndWorkStaysPending` fails on the current code with 'a failure delivery from the Intake with status 3 is refused'.","severity":"low","snippet":"        if (status != 1 && status != 2) revert InvalidFailureStatus();","title":"onFailure refuses any Intake failure status other than 1 or 2, so such a delivery is lost and the work stays Pending with no Failed event"},{"citation":"resolved","description":"Area: Access Control / Asymmetry. The brief says onResult 'stores the result and emits Built' and 'must use little gas (under 150k)'; it names no URI cap. The Workshop adds a 2,048-byte cap so the 150k budget holds for every accepted input, which is a reasonable engineering choice and is documented in the README. It is recorded here because of its failure mode rather than as a defect: the Intake attempts delivery once under a 200k stipend and does not retry, so a result whose URI exceeds the cap reverts inside that attempt and the work stays Pending forever with no Built event, even though the price was paid and the job completed. Whether a real job.open delivery URI can exceed 2,048 bytes is a property of the Identity.md plane that could not be verified from here (the Intake source is not published on the explorer without an API key). If the plane ever delivers a long URI (for example a data: URI or a gateway URL with a long path), that work is lost from the gallery. The author should confirm the plane's maximum URI length with Identity.md, or raise the cap to what the 150k budget allows (the measured worst case at 2,048 bytes leaves headroom).","line":105,"path":"src/Workshop.sol","reproduction":"State: Workshop funded; `commission()` called once returning requestId R. Call: from the Intake address, `onResult(R, keccak256(\"artifact\"), <2,049 ASCII bytes>)` with 200,000 gas. Expected per brief: stored and `Built` emitted. Actual: reverts `URITooLong()`; works[0] stays Pending; no Built event. Scratch test `test/scratch/FailureStatus.t.sol::test_resultWithLongURIIsRefusedAndWorkStaysPending` fails on the current code.","severity":"info","snippet":"        if (bytes(uri).length > MAX_URI_BYTES) revert URITooLong();","title":"onResult reverts on a result URI longer than 2,048 bytes, so a delivery the Intake makes with a longer URI is permanently lost (design bound not in the brief)"}],"hash":"da8273377324468945a2b6b7119293d33b621936cbf18f5e7cfd174a22e1a0b9","nodeId":"56d11d59-4870-43fc-be6c-530922d24910","outcome":"completed","summary":"Review complete. The findings file is written and validated: both snippets match the tree line-for-line, and the coverage record answers all 11 listed entry points plus four invariants.\n\n**Outcome: no critical, high or medium defects in the Access Control, Trust Gap or Asymmetry area.** The permission model is as the brief demands: no owner, no setters, every v4 callback and the unlock callback gated on the PoolManager, the self-quote gated on the hook itself, and the two Intake callbacks gated on the Intake address plus an own-pending-request check. I confirmed the hook's reliance on v4's self-call skip in the vendored core, the ERC-6909 claim path's net-zero delta, and that a pool on the predicted hook address cannot be opened before deployment. Over the network I verified the live Intake at the pinned address exposes `requestWithFailure` and returns the 0.5 IMD price the contract reads at call time. A scratch test proved the four stored job bodies are byte-exact copies of the brief.\n\nTwo findings, both reproduced with scratch tests that fail on the current code:\n\n- **Low, Workshop.sol:120.** The failure hook refuses any status outside 1 or 2. The Intake delivers once and never retries, so a failure with another code leaves the work Pending forever and the Failed event is never emitted. The brief specifies only the caller check and the event. Fix is to drop the whitelist.\n- **Info, Workshop.sol:105.** The 2,048-byte URI cap is a documented, reasonable bound for the 150k gas budget, but a longer delivery is lost the same way. Recorded so the author can confirm the plane's maximum URI length.\n\nTwo notes for the judge, not findings: the Workshop is not deployed by the launch factory, so its real address must be deployed on Robinhood Chain before launch.json names it as a literal constructor argument (the README already says this). The Intake's verified source is not fetchable without an explorer API key, so the exact callback shape for job.open was checked against the brief and the live selectors, not the Intake source.","treeHash":null,"usage":{"cachedInputTokens":1569495,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":46308,"runtime":"claude","turns":51,"wallClockMs":606851}},{"artifacts":[],"attempt":1,"bundleHash":"f66bf6ce1cde1acd46a37f010b815140ea27659ff1d9426501e9e7bdee3309af","device":"3d55f5b0ed8fa3df","findings":[],"hash":"f7ddbf5564ad7b425df25b32e7fae961ba8fdf11130dd3c1223036849fc6c762","nodeId":"da06b1c1-30c0-4055-bf83-62090cb6c2ad","outcome":"completed","summary":"Implemented the immutable token, hook, Workshop, live gallery, SVG/PNG logo, and deployment documentation.\n\nValidation passed:\n\n- `forge build` and `forge fmt --check`\n- 64 local contract tests\n- 5 Robinhood fork tests\n- 8 website tests\n\n[README](README.md) documents deployment parameters, deferred IMD redemption, and callback limits. No live transactions were sent. The gallery awaits the deployed Workshop address and block.","treeHash":"3ed9e501a2cfff1e5bd94756203012e4bb06a9dc","usage":{"cachedInputTokens":6722048,"inputTokens":187152,"model":"gpt-6-astra","outputTokens":69040,"runtime":"codex","turns":10,"wallClockMs":1910407}}],"verification":[{"checks":[{"durationMs":3774,"exitCode":0,"name":"build","output":"Compiling 98 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.68s\nCompiler run successful!\n","passed":true},{"durationMs":618,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/WorkOrders.t.sol:WorkOrdersTest\n[PASS] test_templateBytesMatchBrief() (gas: 31664)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 299.34µs (186.09µs CPU time)\n\nRan 2 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] test_minerRangeChecks() (gas: 1835414)\n[PASS] test_offlineMinerUsesFactoryAndActualConstructorArguments() (gas: 7469645)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 11.80ms (11.90ms CPU time)\n\nRan 20 tests for test/Workshop.t.sol:WorkshopTest\n[PASS] testFuzz_priceAndBountyConservation(uint128) (runs: 256, μ: 890385, ~: 896247)\n[PASS] test_belowPricePlusBountyRevertsWithoutStateChanges() (gas: 195090)\n[PASS] test_callbacksRejectWrongCallerAndUnknownRequests() (gas: 933036)\n[PASS] test_dailyCapAndUTCRollover() (gas: 2033149)\n[PASS] test_duplicateIntakeRequestIdRollsBackSecondPayment() (gas: 1077223)\n[PASS] test_dynamicPriceExactApprovalBountyAndSelectors() (gas: 977316)\n[PASS] test_extremeExternalPriceDoesNotOverflowCanCommission() (gas: 113255)\n[PASS] test_failedIntakeOrBountyRollsBackEverything() (gas: 1065418)\n[PASS] test_failureAndReplayDoNotSpendOrResetClock() (gas: 988932)\n[PASS] test_firstCommissionAtTimestampZeroStillEnforcesCooldown() (gas: 797748)\n[PASS] test_midnightDoesNotResetCooldown() (gas: 882901)\n[PASS] test_outOfOrderResultAuthenticatesAndStores() (gas: 1346510)\n[PASS] test_oversizedURIAndInvalidFailureStatusReject() (gas: 897669)\n[PASS] test_priceChangesBetweenCommissions() (gas: 1130656)\n[PASS] test_reentrantCommissionCannotDoubleSpend() (gas: 901390)\n[PASS] test_requestFailureAfterApprovalIsAtomic() (gas: 1145250)\n[PASS] test_resultWorstCaseGasUnder150k() (gas: 1251331)\nLogs:\n  maximum URI callback gas: 138256\n\n[PASS] test_rotatesExactTemplatesAndStoresCommissionIndex() (gas: 4061346)\n[PASS] test_sixHourBoundary() (gas: 1171917)\n[PASS] test_unansweredRequestsDoNotBlockFutureCommissions() (gas: 1082355)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 12.20ms (22.66ms CPU time)\n\nRan 5 tests for test/SelfToken.t.sol:SelfTokenTest\n[PASS] testFuzz_transferConservesSupply(uint128) (runs: 256, μ: 105606, ~: 106032)\nLogs:\n  Bound result 2639421\n\n[PASS] test_fixedPolicySupplyAndIdentity() (gas: 52636)\n[PASS] test_noAdminOrMintFunctionsEvenForDeployer() (gas: 270631)\n[PASS] test_noRuntimeEscapeHatch() (gas: 752191)\n[PASS] test_transferFromHonorsAllowanceAndHasNoTax() (gas: 157967)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 13.12ms (14.56ms CPU time)\n\nRan 17 tests for test/SelfHook.t.sol:SelfHookTest\n[PASS] testFuzz_swapConservation(bool,bool,uint96,bool) (runs: 256, μ: 437353, ~: 416665)\nLogs:\n  Bound result 86222946166374765430300\n\n[PASS] test_buyExactInput() (gas: 455943)\n[PASS] test_buyExactOutput() (gas: 406232)\n[PASS] test_callbacksRejectNonManagerAndQuoteRejectsPublicCaller() (gas: 139455)\n[PASS] test_emptyLiquidityDoesNotTaxUnexecutedRequestedVolume() (gas: 944246)\n[PASS] test_factoryCanCollectWithZeroLiquidityAndWithdraw() (gas: 485626)\n[PASS] test_freshManagerNeedsNoETHAndWorkshopNeedNotHaveIntakeCode() (gas: 466579)\n[PASS] test_insufficientManagerIMDDefersAndAnyoneRedeemsToWorkshop() (gas: 517263)\n[PASS] test_partialBuyExactInput() (gas: 456095)\n[PASS] test_partialBuyExactOutput() (gas: 405702)\n[PASS] test_partialSellExactInput() (gas: 412214)\n[PASS] test_partialSellExactOutput() (gas: 467165)\n[PASS] test_permissionsAndInitializationSelector() (gas: 34262)\n[PASS] test_rejectsDifferentPoolOnlyDuringInitialization() (gas: 692555)\n[PASS] test_sellExactInput() (gas: 412188)\n[PASS] test_sellExactOutput() (gas: 467130)\n[PASS] test_workshopTransferFailureDefersInsteadOfBlockingTrade() (gas: 410671)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 32.52ms (38.46ms CPU time)\n\nRan 17 tests for test/SelfHook.t.sol:SelfHookReverseOrderTest\n[PASS] testFuzz_swapConservation(bool,bool,uint96,bool) (runs: 256, μ: 437007, ~: 415989)\nLogs:\n  Bound result 143259561715754\n\n[PASS] test_buyExactInput() (gas: 468709)\n[PASS] test_buyExactOutput() (gas: 411186)\n[PASS] test_callbacksRejectNonManagerAndQuoteRejectsPublicCaller() (gas: 138916)\n[PASS] test_emptyLiquidityDoesNotTaxUnexecutedRequestedVolume() (gas: 957340)\n[PASS] test_factoryCanCollectWithZeroLiquidityAndWithdraw() (gas: 498374)\n[PASS] test_freshManagerNeedsNoETHAndWorkshopNeedNotHaveIntakeCode() (gas: 479345)\n[PASS] test_insufficientManagerIMDDefersAndAnyoneRedeemsToWorkshop() (gas: 530409)\n[PASS] test_partialBuyExactInput() (gas: 468073)\n[PASS] test_partialBuyExactOutput() (gas: 411525)\n[PASS] test_partialSellExactInput() (gas: 406115)\n[PASS] test_partialSellExactOutput() (gas: 455309)\n[PASS] test_permissionsAndInitializationSelector() (gas: 34242)\n[PASS] test_rejectsDifferentPoolOnlyDuringInitialization() (gas: 692545)\n[PASS] test_sellExactInput() (gas: 405787)\n[PASS] test_sellExactOutput() (gas: 456828)\n[PASS] test_workshopTransferFailureDefersInsteadOfBlockingTrade() (gas: 423459)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 38.96ms (32.13ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:WorkshopInvariantTest\n[PASS]\nWorkshopInvariantTest invariants:\n[PASS] invariant_cadenceAndRotation\n[PASS] invariant_onlyIntakeAndBountyCanSpend\n WorkshopInvariantTest invariants (runs: 64, calls: 2048, reverts: 0)\n\n╭-----------------+----------------------+-------+---------+----------╮\n| Contract        | Selector             | Calls | Reverts | Discards |\n+=====================================================================+\n| WorkshopHandler | advanceAndCommission | 1037  | 0       | 0        |\n|-----------------+----------------------+-------+---------+----------|\n| WorkshopHandler | finish               | 1011  | 0       | 0        |\n╰-----------------+----------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 255\n  Bound result 21600\n  Bound result 27196\n  Bound result 7\n  Bound result 2248\n  Bound result 43199\n  Bound result 3913\n  Bound result 3000\n  Bound result 35605\n  Bound result 15073\n  Bound result 468\n  Bound result 43178\n  Bound result 266\n  Bound result 2\n  Bound result 32\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 255.71ms (254.59ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:HookInvariantTest\n[PASS] invariant_conservationAndExactFees() (runs: 64, calls: 2048, reverts: 0)\n\n╭-------------+----------+-------+---------+----------╮\n| Contract    | Selector | Calls | Reverts | Discards |\n+=====================================================+\n| SwapHandler | redeem   | 1037  | 0       | 0        |\n|-------------+----------+-------+---------+----------|\n| SwapHandler | trade    | 1011  | 0       | 0        |\n╰-------------+----------+-------+---------+----------╯\n\nLogs:\n  Bound result 999999999990000001659\n  Bound result 10000000001\n  Bound result 999999999990000000312\n  Bound result 968534599330833053369\n  Bound result 999999999990000007087\n  Bound result 999999999990000004425\n  Bound result 999999999991091820210\n  Bound result 999999999990000004172\n  Bound result 999999999990000009427\n  Bound result 999999999990000002511\n  Bound result 999999999990000000211\n  Bound result 999999999994228666474\n  Bound result 999999999990000015439\n  Bound result 466112051993343283942\n  Bound result 999999999990000005159\n  Bound result 999999999991510864672\n  Bound result 999999999990000003552\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 490.75ms (469.91ms CPU time)\n\nRan 8 test suites in 494.49ms (855.35ms CPU time): 64 tests passed, 0 failed, 0 skipped (64 total tests)\n","passed":true},{"durationMs":64,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SelfHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SelfHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SelfHook.quoteIMD((address,address,uint24,int24,address),(bool,int256,uint160))\",\"SelfHook.redeemFees()\",\"SelfHook.unlockCallback(bytes)\",\"SelfToken.approve(address,uint256)\",\"SelfToken.transfer(address,uint256)\",\"SelfToken.transferFrom(address,address,uint256)\",\"Workshop.commission()\",\"Workshop.onFailure(bytes32,uint8,bytes32,uint16,uint16,bytes)\",\"Workshop.onResult(bytes32,bytes32,string)\"],\"files\":{\".gitignore\":4,\"LICENSE\":21,\"NOTICE.md\":12,\"README.md\":107,\"docs/dependencies.json\":30,\"docs/dependencies.sha256\":451,\"docs/fork-evidence.json\":12,\"docs/security-review.md\":47,\"docs/verification.md\":26,\"docs/work-orders/0-meme.json\":1,\"docs/work-orders/1-video.json\":1,\"docs/work-orders/2-report.json\":1,\"docs/work-orders/3-audit.json\":1,\"foundry.toml\":28,\"remappings.txt\":4,\"script/MineSelfHook.s.sol\":32,\"scripts/check-fork.sh\":4,\"scripts/check.sh\":7,\"scripts/configure-gallery.py\":26,\"src/HookFlags.sol\":31,\"src/LaunchConstants.sol\":10,\"src/SelfHook.sol\":173,\"src/SelfToken.sol\":11,\"src/WorkOrders.sol\":24,\"src/Workshop.sol\":167,\"src/interfaces/IIntake.sol\":25,\"test/Deployment.t.sol\":74,\"test/Invariants.t.sol\":166,\"test/SelfHook.t.sol\":227,\"test/SelfToken.t.sol\":72,\"test/WorkOrders.t.sol\":22,\"test/Workshop.t.sol\":326,\"test/fork/Robinhood.t.sol\":143,\"test/helpers/HookFixture.sol\":58,\"test/helpers/PoolDriver.sol\":54,\"test/mocks/MockERC20.sol\":24,\"test/mocks/MockIntake.sol\":67,\"web/abi/SelfHook.json\":609,\"web/abi/SelfToken.json\":315,\"web/abi/Workshop.json\":494,\"web/app.mjs\":123,\"web/assets/self-logo-256.png\":37,\"web/assets/self-logo-512.png\":41,\"web/assets/self-logo-64.png\":12,\"web/assets/self-logo.svg\":15,\"web/chain.mjs\":104,\"web/config.json\":10,\"web/index.html\":51,\"web/style.css\":1,\"web/test/chain.test.mjs\":71},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1191,"exitCode":0,"name":"slither","output":"[medium/high] incorrect-equality at src/Workshop.sol:127: Workshop.commissionsToday() (src/Workshop.sol#127-129) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/Workshop.sol:161: Workshop._pending(bytes32) (src/Workshop.sol#161-166) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/Workshop.sol:68: Reentrancy in Workshop.commission() (src/Workshop.sol#68-101):\n[low/medium] reentrancy-benign at src/Workshop.sol:68: Reentrancy in Workshop.commission() (src/Workshop.sol#68-101):\n[low/medium] reentrancy-events at src/SelfHook.sol:137: Reentrancy in SelfHook.unlockCallback(bytes) (src/SelfHook.sol#137-147):\n[low/medium] reentrancy-events at src/SelfHook.sol:75: Reentrancy in SelfHook.beforeSwap(address,PoolKey,SwapParams,bytes) (src/SelfHook.sol#75-108):\n[low/medium] reentrancy-events at src/SelfHook.sol:149: Reentrancy in SelfHook._pay(uint256) (src/SelfHook.sol#149-159):\n[low/medium] reentrancy-events at src/SelfHook.sol:149: Reentrancy in SelfHook._pay(uint256) (src/SelfHook.sol#149-159):\n[low/medium] timestamp at src/Workshop.sol:140: Workshop.canCommission() (src/Workshop.sol#140-151) uses timestamp for comparisons\n[low/medium] timestamp at src/Workshop.sol:68: Workshop.commission() (src/Workshop.sol#68-101) uses timestamp for comparisons\n[low/medium] timestamp at src/Workshop.sol:161: Workshop._pending(bytes32) (src/Workshop.sol#161-166) uses timestamp for comparisons\n[low/medium] timestamp at src/Workshop.sol:127: Workshop.commissionsToday() (src/Workshop.sol#127-129) uses timestamp for comparisons\n[low/medium] timestamp at src/Workshop.sol:131: Workshop.nextCommissionAt() (src/Workshop.sol#131-137) uses timestamp for comparisons","passed":true},{"durationMs":384,"exitCode":0,"name":"aderyn","output":"[high] reentrancy-state-change at src/Workshop.sol:74: Reentrancy: State change after external call (3 places)\n[low] internal-function-used-once at src/HookFlags.sol:24: Internal Function Used Only Once\n[low] large-numeric-literal at src/SelfToken.sol:9: Large Numeric Literal\n[low] literal-instead-of-constant at src/SelfHook.sol:85: Literal Instead of Constant (8 places)\n[low] non-reentrant-not-first at src/Workshop.sol:104: `nonReentrant` is Not the First Modifier (2 places)\n[low] unused-public-function at src/SelfHook.sol:60: Public Function Not Used Internally","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"f7ddbf5564ad7b425df25b32e7fae961ba8fdf11130dd3c1223036849fc6c762","verifiedTreeHash":"3ed9e501a2cfff1e5bd94756203012e4bb06a9dc","verifierVersion":"0.1.0+67c588fa"}]}