{"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":"f4029ee1-2df4-4bc2-a3de-17c5d283ea6f","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"adb5f99b63e94872604369a917d5bcc3998491252bae1f5710b1ae8731e6c62a","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":"1e9b70badf1b5544f778e65778f87e6e676237ef905ea124b82c9e09995bc1c8","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":"a0d32f0f1696c1c68632d52d975dc92092d2bd0f1d62316f7663c03e33204ee7","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":"985309337f07db507a816a9c9b01c5409e5908f611cb4b3ddb4ac9da21b3795c","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":"ef49dc776369a3edb4c2cb557c121b4a295bc97190108e52f9d101821bbf9c7b","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":"8ae268ce432162d694fa7f332d89eece7746819fe9c2264c6a0510e7e062921c","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":"1e301487dcc0d875c0fdf0c0818cba069bc5359259e652adf60910b358ed0508","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"6f64eb6b7bdf8c5834eeaf62c969b415367605a2e9f98eacca89d1ddbd7c5db6","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":"A contract on Ethereum with one function for buyers: buyWebsite(string words), payable. The buyer sends any amount of ETH that buys at least the Intake's job.open price (0.5 IMD today) with a few words describing a website, and everything else happens inside the call. There is no ETH price to set. The contract swaps all of the sent ETH for IMD (0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7) on the existing Uniswap v4 native ETH/IMD pool 0xb07d640fd9e2eb9dc81b953c8e4fd006bdfeaf276010fb5418eb763ca15abfb3, hardcoded as constants: PoolManager 0x000000000004444c5dc75cB358380D2e3dE08A90, currency0 native ETH (address zero), currency1 IMD, fee 10000, tickSpacing 200, no hooks. It reads the Intake's live price with priceOf, reverts if the swap returned less, pays exactly that price for a job.open from the IdentityMD Intake (v2, 0xa43e6f75ee006411f79ac1c84120606c2330de82), and sends every IMD above the price back to the buyer in the same call, so the contract holds no IMD or ETH between calls. The job.open body is {\"objective\":\"Build a one-page website: <words>\",\"skill\":\"build-website\",\"host\":true} with no owner field, because the plane refuses owner on job.open. The buyer's words are refused if they are empty, longer than 1000 bytes, or contain a double quote, a backslash or a control character. Each buyer pays the Intake through their own payer account, created on their first purchase, so their project stays theirs and only they can continue it. The owner can change the Intake address and pause purchases. Store and emit each request's result hash and URI from the Intake's result callback, and record refusals from its failure callback.","parentJobId":null,"planHash":"96879fbaea4f8bfae1acd720d829a732d1ab56968f02f84d6ac15972515a0dfc","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"f4029ee1-2df4-4bc2-a3de-17c5d283ea6f","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-2084-contract-ethereum-one-function"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"52171","feedbackHash":"08be4744c57f5d87b3881c8ed0d98eb83fd3af8119544959fd76082c84edcfe9","nodeKey":"audit_economics","submissionHash":"adb5f99b63e94872604369a917d5bcc3998491252bae1f5710b1ae8731e6c62a","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52152","feedbackHash":"0af50291da68260df861fb8a28604f83a76025405937045bf43f7aaa324e421f","nodeKey":"audit_flow","submissionHash":"1e9b70badf1b5544f778e65778f87e6e676237ef905ea124b82c9e09995bc1c8","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52420","feedbackHash":"d346accf40672f95035bc01e086189dc85914291bf676c5bb9c57bf658f796ab","nodeKey":"audit_judge","submissionHash":"a0d32f0f1696c1c68632d52d975dc92092d2bd0f1d62316f7663c03e33204ee7","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52160","feedbackHash":"9d133e9c8e0366c35f904567e7d85ec75baa12a2e62472866a42bd11c6ccb90e","nodeKey":"audit_judge","submissionHash":"c0e6d78f2cb3c9f597f4c3ad50aa100b669a8cf0a091308351df2bb4adebcf8b","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51070","feedbackHash":"c8175a4308b9ca9f2289b482394ea4b9d967d0816872547a23146ec0a7b1f361","nodeKey":"audit_math","submissionHash":"985309337f07db507a816a9c9b01c5409e5908f611cb4b3ddb4ac9da21b3795c","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"53924","feedbackHash":"1df05338733ac499dc818f8e9db78293857f0ed684676783c78115dda56b76df","nodeKey":"audit_permissions","submissionHash":"ef49dc776369a3edb4c2cb557c121b4a295bc97190108e52f9d101821bbf9c7b","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50990","feedbackHash":"654a8dda9e4dca8a8609ef883d8e0d5765c9e3178915aab5a4709af328d94c4c","nodeKey":"build_contract_project","submissionHash":"75dd260bf615ea48cfa37944b259d33eb430aba750a02660ccb8e091824dfc9f","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51476","feedbackHash":"5c5bc687ab22b05af4c0afe9654abc19b0fc3f5cb08456dec129d6b56c9d02e9","nodeKey":"build_contract_project","submissionHash":"8ae268ce432162d694fa7f332d89eece7746819fe9c2264c6a0510e7e062921c","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52179","feedbackHash":"a7eecd7ca7e46251bf949f2f177394c44a73a4fe4bfe55f2ea01d26ba4902504","nodeKey":"manifest","submissionHash":"1e301487dcc0d875c0fdf0c0818cba069bc5359259e652adf60910b358ed0508","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51145","feedbackHash":"eec1758127e4d913b142ca475b3803925e19b1477bea7bb321025cbc58e6c2a9","nodeKey":"manifest","submissionHash":"b3b8e0b13a4b399399904bde2207e65c129a5e110dcc70e651042683a8296d91","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51352","feedbackHash":"59e3f3aa5f2e977e9e74a6df41c4d9e4d6b90e51494a2657f4e06eeb7580c30f","nodeKey":"write_foundry_tests","submissionHash":"6f64eb6b7bdf8c5834eeaf62c969b415367605a2e9f98eacca89d1ddbd7c5db6","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51345","feedbackHash":"48ed9326b6fc42f21f62ad18a985f1d55d70c4211406fcbc8d2fccfaa2f24d4a","nodeKey":"write_foundry_tests","submissionHash":"394e0213184d98a0cb2ce87e8ce61d02aefe8f0322ab8069ab784cb469ddbb68","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"5f00ec78b5ac657a2834a0b0e12d76f8aa57eb266a8f4d6e1f8e8990e54813bb","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":"f636727b62fa0098005402fa617d2acc02b4e9b92ad2b8a90bc691b855adcdf9","device":"02f22d6f13810ca8","findings":[],"hash":"1e301487dcc0d875c0fdf0c0818cba069bc5359259e652adf60910b358ed0508","nodeId":"fb00331f-773f-4bc0-a2b2-6935c5dc2885","outcome":"completed","summary":"Done. The manifest is revised and only launch.json changed.\n\n**What I found**\n- The accepted contracts were revised in the latest round. WebsiteBuyer gained an owner-settable action id with `setAction`, a `buyWebsiteWithMinimum` purchase with a surplus floor and deadline, strict UTF-8 word validation, and a 128-byte cap on stored result URIs.\n- The constructor is unchanged: one `address owner_` parameter, no external calls, no code requirements. IntakeAccount is created per buyer at runtime and is not a launch contract.\n- The previous manifest's structure was already correct, but its notes described the older implementation.\n\n**What I changed**\n- Rewrote the notes in launch.json to match the accepted source: the mutable intake and action defaults, the new protected purchase function, the tightened setIntake checks, the setAction power, and the result URI storage rule.\n- Contracts list and constructor arguments are unchanged: WebsiteBuyer with the single argument `$owner`.\n\n**Verification**\n- Manifest has exactly the keys kind, contracts and notes. The contract name is a valid 12-character identifier, every argument is a string, and the notes are 2057 characters.\n- `forge build` succeeds and `forge test` passes all 73 tests with the verifier's fuzz seed.\n- `git status` shows launch.json as the only modified file.","treeHash":"85c99899c2bbcc960107b80ff9dbffa2f448a52e","usage":{"cachedInputTokens":361380,"inputTokens":258,"model":"claude-fable-5-1","outputTokens":4142,"runtime":"claude","turns":10,"wallClockMs":76215}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"7c748c02cd2ee98f","findings":[{"citation":"resolved","description":"buyWebsite swaps all of msg.value with sqrtPriceLimitX96 = MIN_SQRT_PRICE + 1 (no limit) and the only output check is `if (received < price) revert InsufficientIMD(received, price);` at src/WebsiteBuyer.sol:111, where price is the Intake's 0.5 IMD. The buyer has no way to pass a minimum output or a deadline, and nothing ties the accepted output to the ETH sent. Any ETH beyond what buys 0.5 IMD is therefore unprotected: a searcher who sees the transaction in the public mempool can buy IMD first, let the victim's swap execute at the worse price, and sell afterwards, keeping most of the victim's surplus, and the victim's call still succeeds because it still received more than 0.5 IMD. A transaction that lingers in the mempool for hours executes later at whatever the pool price then is for the same reason. The brief fixes the interface as buyWebsite(string) with 'reverts if the swap returned less [than the price]', so this is a design trade-off the README acknowledges; it is reported because the loss is real, measurable and borne by the buyer, and the author needs a scope decision: either add a caller-supplied minimum IMD output (and optionally a deadline) to the purchase call, or document loudly that buyers must send only slightly more ETH than the price needs (sending the minimum turns a sandwich into a revert instead of a loss) and submit privately. Reproduced on a mainnet fork at block ~26213209 against the live pool with the project's own contract.","line":150,"path":"src/WebsiteBuyer.sol","reproduction":"Mainnet fork, live PoolManager 0x0000...4444c5dc75cB358380D2e3dE08A90, live pool 0xb07d...bfb3, live Intake 0xa43e...DE82 (priceOf(job.open@job-1, IMD) = 500000000000000000). Deploy WebsiteBuyer(owner). Case A: alice calls buyWebsite{value: 1 ether}(\"a bakery\") -> alice receives 448301919229717317282 wei IMD surplus (~448.3 IMD) after the 0.5 IMD price. Case B, same fork state: attacker first swaps 200 ETH -> IMD through the same pool (plain unlock/swap/settle/take, zeroForOne, no limit), then alice sends the identical buyWebsite{value: 1 ether}(\"a bakery\"). Expected: the buyer is protected against a materially worse rate or the call reverts. Actual: the call succeeds and alice receives 226851845527942224241 wei IMD (~226.9 IMD), about half of case A; the attacker then sells its IMD back and keeps the difference. The only revert condition is received < 0.5 IMD, so with 1 ETH sent the attacker may take anything up to ~447.8 IMD of surplus. Scratch test used: test/scratch/Fork.t.sol::test_fork_sandwich (fork test, not submitted as proof).","severity":"medium","snippet":"                zeroForOne: true, amountSpecified: -int256(amount), sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1","title":"Swap has no price floor beyond the live Intake price, so a buyer's surplus is sandwichable and has no deadline"},{"citation":"resolved","description":"The live Intake (0xa43e...DE82, source verified on Sourcify) calls the result callback once with callbackGas() = 200000 and records delivered=false if it reverts; the request is then marked completed at the Intake and can never be delivered again. onIntakeResult writes state, resultHash, the length word and every 32-byte chunk of the URI to fresh storage slots (about 22.1k gas each) and emits the URI, so cost grows with URI length. Measured with the project's MockIntake (same 200000 stipend) in one warm transaction: 192-byte URI delivered, 224-byte URI not delivered. On mainnet the callback arrives in its own transaction with cold storage, so the real threshold is somewhat lower. The plane's documented URIs (78-byte ipfs://<CIDv1>/result.json, ~101-byte https fallback with a 0x-prefixed request id) fit with roughly 30-50k gas to spare; a longer CID encoding, a longer host path, or a future callbackGas reduction by the Intake owner leaves every such purchase stuck in State.Pending with no hash or URI on chain, and nothing in the hub can settle it afterwards. Consider storing only what is fixed-size (resultHash) and emitting the URI, or truncating/skipping the stored URI above a length budget, so the hash is always recorded.","line":191,"path":"src/WebsiteBuyer.sol","reproduction":"Using the repository's test harness (WebsiteBuyerBase): id = buyWebsite{value: 1 ether}(\"a bakery in Lisbon\") by alice; then as the writer call intake.complete(id, 0, abi.encode(id, keccak256(\"r\"), uri)) with uri = 224 bytes of 'u'. Expected: the hub records the result hash and URI (or at least the hash). Actual: complete returns delivered=false, buyer.requestOf(id).state remains Pending, resultHash remains zero, and no WebsiteResult event is emitted; the same call with a 192-byte URI delivers. The existing test only exercises a 4096-byte URI, far beyond the threshold.","severity":"low","snippet":"        request.uri = uri;","title":"Result callback stores the full URI under the Intake's 200k stipend; a URI of about 200 bytes or more is never recorded and the request stays Pending forever"},{"citation":"resolved","description":"bodyOf rejects quotes, backslashes and control bytes, which prevents JSON injection, but it accepts any byte 0x80-0xFF in any order. A lone continuation byte, a truncated multi-byte sequence or 0xFF is not valid UTF-8, and the Intake reference states the body must be the UTF-8 bytes of strict JSON, with body_not_json / body_invalid as refusal reasons whose price is spent and never refunded. The contract therefore lets a purchase through that is refused off chain, and the buyer loses the 0.5 IMD plus the swap. This matches the brief's literal refusal list (empty, >1000 bytes, quote, backslash, control character) and the reference example has the same gap, so it is reported as low: a cheap on-chain UTF-8 shape check (lead byte / continuation byte structure) would turn a guaranteed refusal into a revert before any ETH is swapped. The plane's refusal itself was not exercised live (the README notes the public check endpoint returned 404); the on-chain acceptance of the input was.","line":170,"path":"src/WebsiteBuyer.sol","reproduction":"alice calls buyWebsite{value: 1 ether}(string(hex\"ff\")) or buyWebsite{value: 1 ether}(string(hex\"c3\")) (a truncated 'é'). Expected: revert BadWords before any swap, as for a quote or a control byte. Actual: bodyOf returns {\"objective\":\"Build a one-page website: <0xff>\",\"skill\":\"build-website\",\"host\":true}, the ETH is swapped, 0.5 IMD is paid to the Intake's payTo and the request is recorded Pending; the plane cannot decode the body as UTF-8 JSON and refuses it (status 1), with the price already spent.","severity":"low","snippet":"            if (c < 0x20 || c == 0x7f || c == 0x22 || c == 0x5c) revert BadWords();","title":"Byte filter admits invalid UTF-8, so a buyer can pay for a body the plane cannot parse"}],"hash":"1e9b70badf1b5544f778e65778f87e6e676237ef905ea124b82c9e09995bc1c8","nodeId":"e97c19e9-1acc-4098-aa5d-b9715203e926","outcome":"completed","summary":"The review is complete and written to `.imd-findings.json` with three findings and a coverage row for all twelve entry points plus three invariants. No source files were changed; the scratch tests are removed.\n\n**What I verified.** I traced the whole purchase path on a mainnet fork against the live PoolManager, the live IMD token and the live Intake v2, not just the mocks. The pool id matches the hardcoded key, the pool is initialized with liquidity, job.open is priced at 0.5 IMD, and a real purchase ends with the hub and payer account holding nothing and no allowance left. I also fetched the Intake's verified source and drove both its result and failure callbacks into the hub from the real writer address, under its real 200k stipend. Both delivered and the encodings match.\n\n**Findings, most severe first:**\n\n- **Medium, buyWebsite has no output floor beyond the Intake price.** The swap runs with no price limit and the only check is received >= 0.5 IMD. On the fork, a 1 ETH purchase returned about 448 IMD surplus; after a 200 ETH front-run it returned about 227 IMD and still succeeded. The brief fixes the one-parameter interface, so I flagged it as a scope decision: add a minimum-output argument, or document that buyers should send only slightly more than the price needs.\n- **Low, result callback cost is close to the stipend.** Storing the full URI costs about 170k gas for a 101-byte URI. Delivery stops between 192 and 224 bytes, after which the request stays Pending forever with no hash recorded. Documented plane URIs fit, but with little margin.\n- **Low, invalid UTF-8 passes the words filter.** A lone 0xFF or 0xC3 byte is accepted, the ETH is swapped and the price is paid, and the plane will refuse the body off chain. The brief's literal refusal list does not cover it, so it is low.\n\n**Coverage.** All entry points in my area hold except the two carrying findings. The owner powers (setIntake, setPaused, renounceOwnership) are recorded as trust assumptions rather than defects. I could not exercise the plane's actual refusal of invalid UTF-8, only the on-chain acceptance; the finding says so.","treeHash":null,"usage":{"cachedInputTokens":2579808,"inputTokens":674,"model":"claude-fable-5-1","outputTokens":40887,"runtime":"claude","turns":47,"wallClockMs":625839}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"dedc96e38388cb45","findings":[],"hash":"21c5f7baa6e320f9c9bd5c2b07952682c830048ca0080611f56a4dfad386db27","nodeId":"11c61b5e-92dd-4128-9683-838373468f73","outcome":"failed","summary":"the local build failed, so this was not submitted:\nforge build exited with 1:\nCompiling 94 files with Solc 0.8.26\nSolc 0.8.26 finished in 551.73ms\nError: Compiler run failed:\nError (6546): Documentation tag @job-1` not valid for functions.\n   --> src/WebsiteBuyer.sol:331:5:\n    |\n331 |     /// @notice Points future purchases at another Intake action id, e.g. when `job.open@job-2`\n    |     ^ (Relevant source part starts here and spans across multiple lines).","treeHash":null,"usage":{"cachedInputTokens":2679560,"inputTokens":674,"model":"claude-fable-5-1","outputTokens":72518,"runtime":"claude","turns":67,"wallClockMs":892382}},{"artifacts":[],"attempt":1,"bundleHash":"c7f956c0b892ed00b6e57082aa2a256c5a624c89b343e72ec99f83153441e5b1","device":"fba19b641cdd6f27","findings":[{"description":"bodyOf refuses only empty or >1000-byte words, bytes below 0x20, 0x7f, '\"' and '\\\\'. Any byte 0x80-0xff is accepted without checking UTF-8 validity, so invalid sequences (lone continuation bytes, 0xc0/0xc1 overlongs, 0xf5-0xff) go into the job.open body. JSON text must be UTF-8 (RFC 8259 section 8.1), and the Intake reference says the body is 'the UTF-8 bytes of the same JSON the HTTP door takes', so the plane is expected to refuse such a body (body_not_json / body_invalid, status 1). By then the 0.5 IMD price has already gone to the treasury, and nothing is refunded. Words are fixed per call, so one bad byte wastes one purchase.","line":168,"path":"src/WebsiteBuyer.sol","reproduction":"With the suite's mocks (test/WebsiteBuyerBase.t.sol): vm.prank(alice); buyer.buyWebsite{value: 1 ether}(string(hex\"fffec080\")); Expected: revert BadWords(), because the words are not plain JSON string content. Actual: the call succeeds, a body with raw bytes ff fe c0 80 in the objective is sent to Intake.requestWithFailure, and 0.5 IMD is paid. Fix: reject bytes that are not part of a well-formed UTF-8 sequence, or limit the words to printable ASCII 0x20-0x7e.","severity":"low","title":"Words that are not valid UTF-8 pass the filter, so the plane can refuse the body after the price is spent"},{"description":"onIntakeResult stores the whole URI string in storage. Measured with the mock Intake's 200,000-gas try-call: URIs of up to 192 bytes are delivered, and 200 bytes or more run out of gas. The callback is then recorded as not delivered and the request stays Pending forever, with no on-chain path to record it later. The documented URIs (ipfs://<CID>/result.json, about 80 bytes, and the plane's /intake-results/<id> fallback, about 110 bytes) fit, so this is informational. test/WebsiteBuyerEdges.t.sol pins 0-192 bytes as delivered.","line":187,"path":"src/WebsiteBuyer.sol","reproduction":"bytes32 id = _buy(alice); vm.prank(writer); intake.complete(id, 0, abi.encode(id, keccak256(\"r\"), <200-byte string>)) returns false, and buyer.requestOf(id).state stays Pending. With a 192-byte string it returns true.","severity":"info","title":"A result URI longer than 192 bytes cannot be delivered within the Intake's 200,000-gas callback stipend"},{"description":"The brief asks for this ('There is no ETH price to set'), and the README documents it: buyWebsite swaps all msg.value with sqrtPriceLimit MIN+1 and reverts only if output < priceOf(job.open). A searcher can move the ETH/IMD pool so the buyer receives exactly the price and no surplus, keeping everything the buyer overpaid. Buyers should send close to the price. Reported so this is not mistaken for a tested guarantee.","line":111,"path":"src/WebsiteBuyer.sol","reproduction":"By construction (src/WebsiteBuyer.sol:111, swap limit TickMath.MIN_SQRT_PRICE + 1 at line 150): a front-run zeroForOne swap that lowers the ETH->IMD rate shrinks the buyer's surplus, and the purchase still succeeds while output >= priceOf(job.open). Expected (if surplus protection were wanted): a buyer-supplied minimum output. Actual: only output >= price is enforced, as the brief specifies.","severity":"info","title":"The only slippage bound is the live Intake price, so a sandwich can take the whole surplus"},{"description":"Purchases measure balances relative to the pre-swap balance, so IMD donated to the hub can never fund a purchase or reach a buyer (the invariant suite checks this). It also has no way out: there is no sweep. The brief says the contract holds no IMD between calls, which holds for every purchase but cannot hold for a third-party transfer. Plain ETH transfers are rejected, since the contract has no receive function.","line":106,"path":"src/WebsiteBuyer.sol","reproduction":"imd.mint(address(buyer), 10 ether); any number of purchases later, imd.balanceOf(address(buyer)) == 10 ether and no function can move it (invariant_hubIMDIsOnlyWhatWasDonated).","severity":"info","title":"IMD sent directly to the hub is stuck permanently"}],"hash":"394e0213184d98a0cb2ce87e8ce61d02aefe8f0322ab8069ab784cb469ddbb68","nodeId":"7244138b-9526-4c89-aa09-ab480abd674f","outcome":"completed","summary":"I added three test files. The whole suite passes with the verifier's seed: 75 tests in 10 suites (two are my throwaway probes in `test/scratch/`, which gets deleted). I found no critical or high defects, and I reported four smaller issues in `.imd-findings.json` instead of writing tests that treat them as correct.\n\n**What I added (all under `test/`):**\n- **`WebsiteBuyerInvariant.t.sol`**: random sequences against the existing mocks, about 10,000 calls per run with no unexpected reverts. Three buyers make good and bad purchases while results and failures arrive, callbacks are faked, the owner pauses, switches between up to three Intakes, and IMD and ETH are sent to the contract directly. Every expected refusal is checked for its exact reason. After every step these hold:\n  - the contract never holds ETH, and its IMD only ever equals what was sent to it directly;\n  - every wei a buyer spends ends up in the pool;\n  - the treasury gets exactly the prices, each buyer gets exactly their leftover IMD, and no IMD is created or lost;\n  - each buyer's payer account holds nothing, approves nothing, never changes and is never shared;\n  - each stored request matches who paid, through which account, at what price and on which Intake, and a result or failure is recorded only once.\n- **`RealPoolInvariant.t.sol`**: the same checks against the real Uniswap v4 PoolManager code with real liquidity, while another trader moves the price both ways. A purchase may only fail because the IMD came back below the price, or because the pool could not fill a tiny or out-of-range amount. In either case nothing moves.\n- **`WebsiteBuyerEdges.t.sol`**: 12 failure-path and edge tests, including:\n  - a buyer's future account address funded before their first purchase;\n  - purchases of the largest possible ETH amount and of 1 wei;\n  - pausing twice or unpausing when not paused;\n  - bad words refused before the price is read;\n  - no `owner` field in the body;\n  - a failed callback that the Intake cannot retry;\n  - result URIs of up to 192 bytes stored within the Intake's 200,000-gas callback limit.\n\n**Findings in `.imd-findings.json`:**\n- **Low:** words that aren't valid UTF-8 are accepted. For example, `buyWebsite(hex\"fffec080\")` succeeds and pays the 0.5 IMD. The resulting body isn't valid JSON, so the Intake will probably refuse it, and the price is not refunded.\n- **Info:** a result URI of 200 bytes or more runs out of the 200,000-gas callback limit, and that request stays Pending for good. The `ipfs://` and fallback URI formats in the docs are 80–110 bytes, so they fit.\n- **Info:** the only slippage protection is the live Intake price, so a sandwich attack can take a buyer's entire leftover IMD. The brief asks for this design.\n- **Info:** IMD sent straight to the contract can never be used or recovered, because there's no sweep function.\n\nStill owed: none of this ran against live mainnet. The Intake and IMD token are local mocks.","treeHash":"d80d2311a9bf5a9accae6b45f6b94b8940673974","usage":{"cachedInputTokens":2003048,"inputTokens":44,"model":"claude-opus-5-5","outputTokens":37233,"runtime":"claude","turns":23,"wallClockMs":457511}},{"artifacts":[],"attempt":1,"bundleHash":"b52733956739e7eabbf698f38700e6020d3bf709027929a5b5b662b376b911f6","device":"8f08088e7a7f557f","findings":[{"description":"onIntakeResult stores the URI only when it is at most MAX_STORED_URI_BYTES (128). A longer URI settles the request as Done with the hash, but requestOf(id).uri is the empty string, so an on-chain reader cannot tell a missing URI from one that was too long, and must go to logs. The brief asks for the URI to be stored as well as emitted. The previous revision stored URIs of at least 192 bytes within the Intake's 200 000 gas callback (the earlier test testFuzz_resultURIUpTo192BytesIsDelivered passed against it), so the cap is lower than the stipend requires. Realistic ipfs:// and plane fallback URIs (about 80-115 bytes) are under the cap, which is why this is low. Possible fixes that keep the stipend safe: raise the cap to the measured safe length, or store keccak256(uri) and a flag that the URI was truncated.","line":280,"path":"src/WebsiteBuyer.sol","reproduction":"Buy one website, then have the writer complete the request with status 0 and abi.encode(id, keccak256(\"b\"), 129 bytes of 'b'). Expected: requestOf(id).uri equals the 129-byte URI. Actual: state is Done and resultHash is set, but uri is \"\". A 128-byte URI is stored. See test_resultURIStorageCapBoundary in test/WebsiteBuyerEdges.t.sol, which pins the current behaviour at the boundary.","severity":"low","title":"Result URIs longer than 128 bytes are emitted but not stored, contrary to 'store and emit each request's result hash and URI'"}],"hash":"6f64eb6b7bdf8c5834eeaf62c969b415367605a2e9f98eacca89d1ddbd7c5db6","nodeId":"7244138b-9526-4c89-aa09-ab480abd674f","outcome":"completed","summary":"I've updated my tests to match the revised contract. `forge test` passes all 90 tests with the verifier's fuzz seed, and `forge build` succeeds.\n\nThe implementation changed after my last round, and three of my tests broke because they asserted the old behaviour. The changes to the contract were deliberate, so I changed the tests, not the expectations about correctness:\n\n**`test/WebsiteBuyerEdges.t.sol`**\n- **URI storage:** the old fuzz test expected every URI up to 192 bytes to be stored. It now covers 0–255 bytes and checks that every URI is emitted, the request is marked done with its hash, and the URI is stored only up to the 128-byte cap.\n- **New edge tests:** a boundary test at exactly 128 and 129 bytes, and a 4096-byte URI that still settles within the 200,000-gas callback limit.\n- **Failure status:** a status of 7 is now recorded as a failure. A status of 0 sent to the failure callback is now the case where the contract refuses delivery and the request stays pending.\n\n**`test/WebsiteBuyerInvariant.t.sol`**\n- **Updated handler:** result URIs now run up to 255 bytes, and the tracked state accounts for the storage cap. Failure delivery now covers statuses 1 and 2, any later nonzero status (recorded), and 0 (refused).\n- **New handler action:** random call sequences now include the new `buyWebsiteWithMinimum`. Its minimum surplus is set around the actual surplus so both sides of the threshold, and equality, get hit, and its deadline is one second ago, now, or later. The existing invariants apply to it: the contract never holds ETH, IMD is conserved, and a refused purchase moves nothing.\n\n**One finding, in `.imd-findings.json` (low severity):** result URIs longer than 128 bytes are emitted but not stored (`src/WebsiteBuyer.sol:280`). A request then shows as done with an empty URI, which goes against the brief's \"store and emit each request's result hash and URI\". The previous version stored URIs of at least 192 bytes within the gas limit, so the cap is lower than it needs to be. Realistic IPFS and fallback URIs are about 80–115 bytes, so they fit under it. `test_resultURIStorageCapBoundary` pins the current behaviour at the boundary so the report can be reproduced.","treeHash":"72e2d4c1aaaac5634a2dd9ab56107d0611f1051e","usage":{"cachedInputTokens":1189167,"inputTokens":36,"model":"claude-opus-5-5","outputTokens":13330,"runtime":"claude","turns":21,"wallClockMs":209663}},{"artifacts":[],"attempt":2,"bundleHash":"88b6d31da3ac9893b8aabaf48dba2919fc546d8a27bf44f78573b7f4ee8cd874","device":"1f805d33d69c340e","findings":[],"hash":"75dd260bf615ea48cfa37944b259d33eb430aba750a02660ccb8e091824dfc9f","nodeId":"11c61b5e-92dd-4128-9683-838373468f73","outcome":"completed","summary":"Implemented the contracts, vendored dependencies, tests, and deployment documentation.\n\nVerified with Solidity 0.8.26:\n\n- `forge build` passes.\n- All 59 tests pass with the required fuzz seed, including offline checks and real Uniswap v4 integration.\n- `forge fmt --check` passes.\n\nDeployment parameters and assumptions are in [README.md](/root/.identitymd/work/f4029ee1-2df4-4bc2-a3de-17c5d283ea6f/11c61b5e-92dd-4128-9683-838373468f73/README.md). The public HTTP preflight returned 404; live Intake acceptance remains unverified.","treeHash":"bac17e174a556889c2e898a9bd2e41dbb696ec84","usage":{"cachedInputTokens":3033216,"inputTokens":148804,"model":"gpt-6-astra","outputTokens":41411,"runtime":"codex","turns":8,"wallClockMs":1040344}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"c97fd6a73e3737dc","findings":[],"hash":"8781404eda617dc25a5ecb838f8695498f46430aa2a3488d5ec61350a487e119","nodeId":"11c61b5e-92dd-4128-9683-838373468f73","outcome":"failed","summary":"the local build failed, so this was not submitted:\nforge build exited with 1:\nCompiling 24 files with Solc 0.8.26\nSolc 0.8.26 finished in 103.74ms\nError: Compiler run failed:\nError (2527): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires \"view\".\n  --> lib/forge-std/src/console.sol:11:17:\n   |\n11 |             pop(staticcall(gas(), consoleAddress, add(payload, 32), mload(payload), 0, 0))\n   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nError (2527): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires \"view\".\n  --> lib/forge-std/src/console.sol:11:28:\n   |\n11 |             pop(staticcall(gas(), consoleAddress, add(payload, 32), mload(payload), 0, 0))\n   |                            ^^^^^","treeHash":null,"usage":{"cachedInputTokens":3090272,"inputTokens":802,"model":"claude-fable-5-1","outputTokens":130208,"runtime":"claude","turns":90,"wallClockMs":1513767}},{"artifacts":[],"attempt":2,"bundleHash":"f411345904994b60b379f1af645c7e4e48854f5f89156b71d4a1a72ef6ea6faf","device":"e9049e5b12ba53ba","findings":[],"hash":"8ae268ce432162d694fa7f332d89eece7746819fe9c2264c6a0510e7e062921c","nodeId":"11c61b5e-92dd-4128-9683-838373468f73","outcome":"completed","summary":"I made every change the review asked for, but none of it has been compiled or tested. Every shell command failed in this sandbox (`apply-seccomp: write /proc/self/setgroups … Permission denied`), so `forge build`, `forge test` and `forge fmt --check` never ran. I also couldn't commit; everything is in the working tree. The verifier's build will be the first real check.\n\nTo lower that risk, I left out the overload the reviewer suggested: an existing test uses `abi.encodeCall(buyer.buyWebsite, …)`, which won't compile if the name is overloaded. I added a separately named function instead. I kept `_buy` at the same number of local variables as the version that compiled last round, and moved the UTF-8 byte classification into a helper so `bodyOf` stays well clear of \"stack too deep\". I also checked that no NatSpec comment contains a stray `@`, which is what broke the last rejected attempt.\n\n**What changed in `src/WebsiteBuyer.sol`:**\n- **Sandwich protection (the medium finding):** new `buyWebsiteWithMinimum(words, minSurplus, deadline)`. It reverts `Expired` once the deadline has passed, and reverts `SurplusTooLow` after the swap but before any payment if the buyer would get back less than `minSurplus` IMD. This also covers a price raised between the buyer's quote and their transaction landing. `buyWebsite(string)` behaves as before; it is the same purchase with no minimum and no deadline.\n- **Retired action id:** `action` is now stored, starting at `job.open@job-1`, and the owner can change it with `setAction(bytes32)`, which emits `ActionChanged`. The asset stays fixed as IMD, because the hardcoded pool's IMD output is what pays the Intake.\n- **Long result URIs:** the result callback always records Done and the hash and always emits the full URI. It only stores the URI when it is 128 bytes or less, so URI length can no longer push the callback past its 200,000-gas limit.\n- **Failure status:** any nonzero status is now recorded; only 0 is rejected.\n- **Malformed UTF-8:** the words are now checked as well-formed UTF-8 (rejecting stray or truncated byte sequences, overlong encodings, surrogates, and code points above U+10FFFF). The existing quote, backslash and control-byte rules are unchanged.\n\n**Tests:**\n- New `test/WebsiteRevision.t.sol` covers the minimum and deadline, the price-raise case, pause, `setAction`, and UTF-8 (16 malformed sequences, boundary values, and a fuzz test over every valid code point).\n- `test/UniswapIntegration.t.sol` gains a sandwich test on the real vendored PoolManager, using the reviewer's setup.\n- `test/WebsiteCallbacks.t.sol` gets the new status and URI tests. The existing \"callback never delivered\" test now uses a 32 KiB URI, because 4096 bytes is now delivered.\n\n**Docs and answers:** the README and `docs/VALIDATION.md` describe the new function, the setter and the limits, and VALIDATION.md says plainly that this revision was not run locally. `.imd-responses.json` answers all seven findings: five fixed, and two disputed.\n- **Owner and Intake trust:** these powers were requested in the brief; they are documented, and the new minimum limits what a buyer can lose to them.\n- **IMD sent directly to the contract:** it stays stuck by design, because a sweep function would give the owner a power the brief didn't ask for.","treeHash":"58c07751af7d6481b2ae96fd28a847ea167eb564","usage":{"cachedInputTokens":3257861,"inputTokens":72,"model":"claude-opus-5-5","outputTokens":43001,"runtime":"claude","turns":60,"wallClockMs":490508}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"89214b73ec1e0b7b","findings":[{"citation":"resolved","description":"Boundary x economics. The only output floor on the exact-input swap is `received >= price` (0.5 IMD today), independent of msg.value. The brief promises that every IMD above the price is returned to the buyer, but nothing bounds how little IMD msg.value may buy, so a searcher who sees the pending buyWebsite in the public mempool can front-run (buy IMD with ETH to push the pool price), let the purchase execute at the degraded rate, and back-run (sell the IMD back). The purchase still succeeds as long as the degraded swap returns at least `price`; the buyer's expected surplus goes to the searcher and to the LP fee. Reproduced against the vendored real v4 PoolManager with the hardcoded pool key (fee 10000, tickSpacing 200, no hook). Pool initialised at 1000 IMD/ETH with full-range liquidity 3000e18. (a) Buyer sends 0.01 ETH: honest surplus 9.398966990634063379 IMD. After a 330.569836972699704343 ETH front-run, the same call succeeds with surplus 130621 wei of IMD (~0) and the buyer has still paid 0.01 ETH. (b) Buyer sends 1 ETH: honest surplus 979.275536336348340806 IMD. Front-run 2 ETH, buyer executes, back-run sells the IMD: buyer surplus 939.821872545572805227 IMD (loses 39.45 IMD, ~0.04 ETH of value), attacker nets +474912578453391 wei after both 1% legs, the rest of the buyer's loss goes to LP fees. The attack is profitable whenever the buyer's trade is large relative to pool liquidity (the live IMD pool is thin) and the losses are unbounded by the contract in every case. README lists this as an assumption; it is recorded here because it is a concrete loss-of-funds path for the buyer that the stated design ('sends every IMD above the price back to the buyer') does not deliver under adversarial ordering. Because the brief fixes the signature to buyWebsite(string) and says there is no ETH price to set, a fix needs a scope decision: add a caller-supplied minimum output (buyWebsite(string words, uint256 minImdOut)), or an owner-settable upper bound on the ETH accepted per purchase so the extractable surplus is capped, or at least have the client submit through a private relay. None of these change the owner, economics or intake flow.","line":111,"path":"src/WebsiteBuyer.sol","reproduction":"Foundry, real PoolManager deployed with deployCodeTo at 0x000000000004444c5dc75cB358380D2e3dE08A90, MockIMD at 0xD34a99Bc0f67aE1bbd63C660e6d0b0dd03E263B7, MockIntake at 0xa43e6F75ee006411F79Ac1C84120606C2330DE82 selling job.open@job-1 for 0.5e18 IMD. manager.initialize(buyer.poolKey(), uint160(316227766 * 2**96 / 1e7)); add liquidity ticks [-887200, 887200] amount 3000e18. Step 1 (snapshot): alice calls buyWebsite{value: 0.01 ether}(\"a cafe menu\") -> alice IMD balance 9398966990634063379. Revert snapshot. Step 2: attacker swaps zeroForOne exact input 330569836972699704343 wei ETH with sqrtPriceLimit MIN_SQRT_PRICE+1. Step 3: alice calls buyWebsite{value: 0.01 ether}(\"a cafe menu\") -> succeeds, alice IMD balance 130621 wei (expected ~9.4e18), treasury receives 0.5e18, alice has paid 0.01 ETH. Step 4: attacker sells its IMD back oneForZero. Variant with 1 ETH purchase and a 2 ETH front-run: alice surplus 939821872545572805227 instead of 979275536336348340806, attacker ETH balance +474912578453391 wei net of fees. Expected: a purchase that returns far less IMD than msg.value is worth should not execute, or the buyer should be able to bound it; actual: any output >= 0.5 IMD is accepted.","severity":"medium","snippet":"        if (received < price) revert InsufficientIMD(received, price);","title":"Minimum output is only the live Intake price, so a sandwich can take the entire IMD surplus the buyer was promised"},{"citation":"resolved","description":"Boundary on the writer's single 200,000-gas delivery attempt. onIntakeResult writes a new resultHash slot, the string length slot and one new 32-byte slot per 32 bytes of uri (~22,100 gas each when cold), plus the event. Measured with the project's MockIntake.complete (call{gas: 200_000}): a 192-byte URI is delivered, a 224-byte URI is not (delivered == false), and the record remains State.Pending forever because the Intake attempts delivery once. The documented formats (ipfs://<CID>/result.json, 78 bytes, or /intake-results/<id>) fit, so this only bites if the plane serves a longer gateway URL, but the failure mode is silent: the hash is lost too, although it is only 32 bytes, because the whole callback reverts. README notes extremely long URIs can exceed the stipend but gives no threshold. Minimal fix that keeps the brief's 'store and emit hash and URI': always store state and resultHash, then store the uri only if uri.length <= 160 and otherwise emit it only (or store keccak256(uri)), so the Done transition and the hash never depend on the URI length.","line":191,"path":"src/WebsiteBuyer.sol","reproduction":"Using the project's fixtures (MockIntake with writer set): id = buyWebsite{value: 0.01 ether}(\"a cafe menu\"); then as writer call intake.complete(id, 0, abi.encode(id, bytes32(uint256(1)), string(new bytes(224) filled with 'a'))). Returns delivered == false and buyer.requestOf(id).state == Pending with resultHash == 0. The same call with a 192-byte uri returns true and state == Done. Expected: a valid result with a longer URI still records the result hash and completes the request; actual: nothing is recorded and no retry exists.","severity":"low","snippet":"        request.uri = uri;","title":"Result URIs longer than about 200 bytes cannot be stored under the Intake's 200,000-gas callback and the request stays Pending"},{"citation":"resolved","description":"Boundary x invariant: the invariant 'every request eventually leaves Pending when the Intake answers' is only kept for the two status values known today. The failure selector is chosen by the writer for any non-zero status; if the plane ever reports a new terminal status (3, 4, ...) the callback reverts inside the Intake's single try, the Intake marks the request completed/not delivered, and the hub record stays Pending forever: no later call can move it, and the stored reason is lost. No funds depend on it (the price is already spent), so impact is a permanently inconsistent record for that buyer. Minimal fix: accept any nonzero status, store it as given and emit it, keeping the status 0 rejection if desired; the strict set membership check adds nothing because the sender is already authenticated as the accepting Intake.","line":199,"path":"src/WebsiteBuyer.sol","reproduction":"id = buyWebsite{value: 1 ether}(\"a bakery in Lisbon\"); then as writer: intake.complete(id, 3, abi.encode(id, uint8(3), bytes32(\"expired\"), uint16(0), uint16(0), bytes(\"\"))). The hub reverts InvalidFailureStatus(3); complete returns delivered == false and marks the request completed on the Intake side; buyer.requestOf(id).state remains Pending and reason remains 0 with no further path to change it. Expected: the refusal/failure is recorded with its status and reason; actual: it is dropped.","severity":"low","snippet":"        if (status != 1 && status != 2) revert InvalidFailureStatus(status);","title":"A failure callback with a status outside {1,2} reverts and leaves the request Pending with no way to settle it"},{"citation":"resolved","description":"Boundary on the bytes input. The brief lists empty, >1000 bytes, quote, backslash and control characters as the refused inputs and the filter implements exactly that; but the Intake protocol requires the body to be UTF-8 JSON, and a single byte 0xFF (or 0xC0 0x80, or a lone 0x80) is never valid UTF-8 while passing every check here. Such a purchase swaps the ETH, pays 0.5 IMD and sends a body the plane cannot parse as JSON; per the supplied protocol a refused body spends the price (status 1, body_not_json/body_invalid). Whether the plane replaces invalid sequences with U+FFFD instead of refusing could not be verified from here, so this is recorded as information: the loss, if any, is the buyer's own malformed input and README already states the filter is not a UTF-8 validator. A cheap on-chain structural UTF-8 check (lead byte classes and continuation counts) would close it if the author wants bodyOf to guarantee a parseable body.","line":170,"path":"src/WebsiteBuyer.sol","reproduction":"buyer.bodyOf(string(hex\"ff\")) returns {\"objective\":\"Build a one-page website: \\xff\",\"skill\":\"build-website\",\"host\":true} instead of reverting BadWords; buyWebsite{value: 1 ether}(string(hex\"ff\")) succeeds on chain and forwards the price. Expected per the protocol: only plain JSON string content reaches the Intake; actual: an invalid UTF-8 body is paid for.","severity":"info","snippet":"            if (c < 0x20 || c == 0x7f || c == 0x22 || c == 0x5c) revert BadWords();","title":"The words filter is a byte filter, so bytes that are never valid UTF-8 (0xC0, 0xC1, 0xF5-0xFF, lone continuation bytes) produce a body that is not valid UTF-8 JSON and the price is spent if the plane "}],"hash":"985309337f07db507a816a9c9b01c5409e5908f611cb4b3ddb4ac9da21b3795c","nodeId":"dcf49761-4357-4509-895e-a82a57744452","outcome":"completed","summary":"The review is complete and `.imd-findings.json` holds four findings plus coverage rows for all 12 entry points and three invariants. No source file was changed and the scratch tests were removed.\n\n**What I checked.** The Math Precision, Boundary and Numerical Gap guides against both contracts. There is no division, scaling or unbounded narrowing cast anywhere in `src/`. The msg.value bound, the full-fill check at the v4 price limit, the native sync-then-settle sequence, the balance equalities and the exact-debit check in the payer account all hold, and I confirmed them on the vendored real PoolManager. The hardcoded pool key hashes to the brief's pool id. All 59 existing tests pass.\n\n**Findings, by severity:**\n\n- **Medium, buyWebsite line 111.** The only output floor is the live Intake price, so a sandwich can take the whole surplus the brief promises back to the buyer. Reproduced on the real v4 math: a 0.01 ETH purchase that honestly returns 9.4 IMD returns 130621 wei of IMD after a front-run, and still succeeds. A 2 ETH front-run against a 1 ETH purchase is net profitable for the attacker. The README lists this as an assumption, and fixing it needs a scope decision because the brief fixes the signature, so I framed the options rather than prescribing one.\n- **Low, onIntakeResult line 191.** Result URIs longer than about 200 bytes exceed the 200k callback stipend. Measured: 192 bytes delivers, 224 does not, and the record then stays Pending with the hash lost too.\n- **Low, onIntakeFailure line 199.** A failure status outside 1 and 2 reverts and strands the record Pending with no settle path. No funds depend on it.\n- **Info, bodyOf line 170.** The byte filter admits bytes that are never valid UTF-8, so a buyer can pay for a body the plane may refuse. Whether the plane refuses or substitutes could not be verified from here, so it is recorded as information only.\n\nNo critical or high finding was found, so no proof test is attached.","treeHash":null,"usage":{"cachedInputTokens":1730271,"inputTokens":546,"model":"claude-fable-5-1","outputTokens":35822,"runtime":"claude","turns":28,"wallClockMs":551480}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"1559912e747bbcd4","findings":[],"hash":"a0d32f0f1696c1c68632d52d975dc92092d2bd0f1d62316f7663c03e33204ee7","nodeId":"ae52775d-4c33-4225-86ec-8b27f5223af5","outcome":"completed","summary":"Review complete. The findings file holds an empty findings list and a full coverage record, and the tree is unchanged apart from it.\n\n**Outcome: every prior finding is settled, nothing new reproduces.**\n\n- **Sandwich on the surplus (medium): fixed.** The new `buyWebsiteWithMinimum(words, minSurplus, deadline)` reverts on an expired deadline and on a surplus below the floor, after the swap and before any account creation or Intake payment. I reproduced the floor, the raised-price race and the deadline on the mock pool. The project's real PoolManager test with a 2000 ETH front-run of a 50 ETH purchase reverts at 99% of the quote and passes. The one-argument form keeps the live-price floor the brief specifies, and the README directs clients to the bounded form.\n- **Long result URI loses the result (low): fixed.** Done and the hash are always written, the URI is emitted always and stored only up to 128 bytes. With every storage slot cooled, URIs of 78 to 192 bytes all deliver under the 200k stipend. The 128-byte callback measured about 170k gas cold, so the test writer's claim that the cap is lower than needed does not hold once slots are cold. I dropped that item.\n- **Unknown failure status strands the request (low): fixed.** Status 3 is recorded as Failed with its reason, and a second callback is refused.\n- **Action id was a constant (low): fixed.** Owner-only `setAction` moves purchases to a new version and keeps the same payer account, reproduced through the retire-and-move scenario.\n- **Invalid UTF-8 accepted (low): fixed.** The structural RFC 3629 check refuses 0xff, overlongs, lone continuations, surrogates, truncated sequences and code points above U+10FFFF before any swap, and accepts every valid scalar.\n- **Trust assumptions and donated IMD (info): author's dispute accepted.** Both are requested or intended behaviour and are documented in the README. They are recorded in the file as settled rather than reopened.\n\nThe author noted they could not compile locally. It compiles here, and the full suite passes with 90 tests including 7 invariants. The manifest has only the three schema keys, one contract with `$owner`, and notes of 2057 characters. All 14 entry points are answered `holds`.","treeHash":null,"usage":{"cachedInputTokens":943852,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":16945,"runtime":"claude","turns":23,"wallClockMs":326743}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"2e0556e0bee1a109","findings":[{"citation":"resolved","description":"buyWebsite swaps all of msg.value with sqrtPriceLimitX96 = TickMath.MIN_SQRT_PRICE + 1 (src/WebsiteBuyer.sol:150) and accepts any output as long as received >= price (line 111). The price is 0.5 IMD (live priceOf on 0xa43e...de82 at block 26213204), so for any purchase above a few thousandths of an ETH the effective minimum output is a tiny fraction of fair value, and there is no deadline. A searcher who sees the transaction in the public mempool can front-run with an ETH->IMD swap on the same pool, let the buyer's exact-input swap execute at the pushed price, and sell the IMD back. The buyer's call still succeeds because the output is still above 0.5 IMD, and the surplus the brief says must be 'sent back to the buyer' goes to the searcher instead. Economics on the live pool (slot0 at block 26213204: sqrtPriceX96 0x1552a5955dcaaa14551f848523, about 455 IMD/ETH, liquidity 0x1c0a1c984be59b8eb0c = 8.28e21, so about 388 ETH / 176,500 IMD of virtual reserves in the active range; 1% LP fee + 0.1% protocol fee): a 5 ETH purchase is already marginally sandwichable, a 10 ETH purchase can be reduced from about 4,388 IMD to about 844 IMD (-81%) for about 3.2 ETH of searcher profit using a 506 ETH front-run (flash-loanable), and a 50 ETH purchase can be reduced from about 19,954 IMD to about 152 IMD (-99%) for about 41.7 ETH of profit. Purchases of about 1 ETH or less are protected only by the 1.1% round-trip fee, not by the contract. The README acknowledges this exposure, and the brief's one-argument interface is the reason no bound exists, so this is reported as a documented design gap with its concrete cost, for the requester to decide. A fix that keeps the design needs a caller-supplied bound: an overload buyWebsite(string words, uint256 minIMDOut, uint256 deadline) alongside the one-argument function (or a maximum ETH-per-IMD in the one-argument path), checked against received before the Intake is paid; the live Intake price alone cannot serve as the bound.","line":111,"path":"src/WebsiteBuyer.sol","reproduction":"Local, on the vendored PoolManager (test/UniswapIntegration.t.sol fixture: pool initialised at 1 IMD/ETH, 1000e18 liquidity in [-60000, 60000], MockIntake price 0.5 IMD). (1) Reference run: alice calls buyWebsite{value: 50 ether}(\"a bakery\") -> alice receives 46,665,316,817,532,158,170 wei IMD (about 46.67 IMD) of surplus. (2) Sandwich run from the same state: attacker swaps 2000 ETH -> IMD exact-input on the pool; alice calls buyWebsite{value: 50 ether}(\"a bakery\") -> the call SUCCEEDS (output 5.48 IMD >= 0.5 IMD price) and alice receives 4,982,996,618,264,914,027 wei IMD (about 4.98 IMD, -89%); attacker swaps all IMD back -> attacker ends with 2017.22 ETH, a net profit of 17.22 ETH after 1% fees. Expected per the brief: every IMD above the price from the buyer's ETH is sent back to the buyer. Actual: 89% of it is taken by the sandwich and the purchase does not revert. On mainnet the same sequence applies to any buyWebsite transaction of roughly 5 ETH or more sent through the public mempool (numbers above).","severity":"medium","snippet":"if (received < price) revert InsufficientIMD(received, price);","title":"Swap output is bounded only by the live Intake price, so a sandwich can take almost the whole IMD surplus"},{"citation":"resolved","description":"onIntakeResult writes the full URI string to storage and emits it. Measured with the project's MockIntake.complete (200,000 gas, as the live writer uses): a 192-byte URI is delivered, a 224-byte URI runs out of gas, the callback reverts, the Intake records it as not delivered and the request stays Pending forever (no retry). The protocol's documented URIs are 'ipfs://<CID>/result.json' (78 bytes) and the plane's '/intake-results/<id>' path, both under the limit, so this does not fail on today's inputs; it is recorded so the threshold is known if the plane ever emits longer URIs. The brief asks the URI to be stored, so no code change is proposed; operators should reconcile Pending records against GET /intake/{chainId}/{transactionHash} as the README already says.","line":191,"path":"src/WebsiteBuyer.sol","reproduction":"After _buy(alice) in the WebsiteBuyerBase fixture: vm.prank(writer); intake.complete(id, 0, abi.encode(id, keccak256(\"r\"), <224 'u' bytes>)) returns false and requestOf(id).state stays Pending; the same call with a 192-byte URI returns true and state becomes Done. Expected: every result the writer sends is recorded. Actual: results with URIs above ~192 bytes are lost on chain.","severity":"info","snippet":"request.uri = uri;","title":"A result URI longer than about 192 bytes cannot be stored within the writer's 200,000-gas callback stipend"}],"hash":"adb5f99b63e94872604369a917d5bcc3998491252bae1f5710b1ae8731e6c62a","nodeId":"9166c989-b24a-48af-bce4-39528012589f","outcome":"completed","summary":"The review is complete. The findings file is written at the repository root and the scratch tests are removed, so the tree contains only the review output.\n\n**What I checked**\n\nI audited the Economic Security, Invariant and Flow Gap failure modes against `src/WebsiteBuyer.sol` and `src/IntakeAccount.sol`. The existing suite passes with 59 tests. I also ran the full purchase against a mainnet fork through the local RPC, so the live Uniswap v4 pool, the live IMD token and the live Intake were exercised rather than mocked. A 0.00112 ETH purchase and a 1 ETH purchase both succeeded. The Intake pulled exactly 0.5 IMD to its treasury and returned a request id. The hub and the payer account ended with zero IMD and zero allowance. IMD moved exact amounts and returned true, so the fee-on-transfer and void-return guards are not triggered by the real token.\n\n**Findings written to `.imd-findings.json`**\n\n- **Medium: the only output bound is the live price, so the surplus is sandwichable.** The swap runs to the minimum tick limit with no deadline and accepts any output at or above 0.5 IMD. On the live pool, about 388 ETH of virtual reserves at roughly 455 IMD per ETH, a 10 ETH purchase can be cut from about 4,388 IMD to about 844 IMD for about 3.2 ETH of searcher profit. I reproduced it locally on the vendored PoolManager: the victim's surplus fell from 46.67 IMD to 4.98 IMD while the call still succeeded and the attacker netted about 17 ETH. The README acknowledges the exposure and the one-argument interface is the cause, so I framed it as a documented design gap with its concrete cost and a possible overload with a caller bound.\n- **Info: result URIs above about 192 bytes cannot be stored within the 200,000-gas callback.** A 224-byte URI fails and the record stays Pending forever. Today's protocol URIs are 78 to about 100 bytes, so nothing fails on current inputs.\n\n**Coverage**\n\nAll 12 listed entry points have a row. One is marked `finding` for buyWebsite. The other 11 hold, with the owner's Intake redirection, pause and renounce powers recorded as trust assumptions rather than bypasses. Four invariant rows cover zero custody between calls, one payer per buyer, the exact body bytes, and the live dependency state.\n\n**Not reached**\n\nI did not inspect the liquidity distribution outside the active tick range, so the sandwich figures use the active range's virtual reserves and are conservative if liquidity thins further out. No critical or high finding was found, so no proof test was attached.","treeHash":null,"usage":{"cachedInputTokens":1413966,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":38654,"runtime":"claude","turns":34,"wallClockMs":577666}},{"artifacts":[],"attempt":1,"bundleHash":"e9217fc0bd30442344d8b6478303f5bd2582f5d675f4331e96fffa02d4ee6414","device":"3b9f88f55251764d","findings":[],"hash":"b3b8e0b13a4b399399904bde2207e65c129a5e110dcc70e651042683a8296d91","nodeId":"fb00331f-773f-4bc0-a2b2-6935c5dc2885","outcome":"completed","summary":"The manifest is written, the build succeeds, and all 59 tests pass with the pinned fuzz seed. The only new file in the tree is launch.json.\n\n**What launch.json contains**\n\n- **One contract, WebsiteBuyer**, with the single constructor argument `$owner`. Its constructor takes only an owner address for Ownable2Step and calls no other contract, so it deploys on the empty rehearsal chain.\n- **IntakeAccount is deliberately not listed.** WebsiteBuyer creates one per buyer with CREATE2 on their first purchase, setting itself as hub and the buyer as user. A factory-deployed instance would have the factory as hub and would be unusable.\n- **Notes** describe the hardcoded constants (IMD, PoolManager, the native ETH/IMD pool key, the initial Intake v2 address), the buyWebsite flow, the owner's setIntake and setPaused powers, and the result and failure callbacks. The notes are well under the 4000-character limit.\n\n**Checks run**\n\n- The JSON parses with exactly the keys kind, contracts and notes.\n- `forge build` compiles cleanly, with only pre-existing lint hints.\n- `forge test` with the verifier's fuzz seed passed every test, including the deployment test that confirms the owner is the launch wallet and not the factory.\n- `git status --porcelain` shows only launch.json as new.","treeHash":"9e3d1e5fb06b1083cf4ac06ba33627b4a884728a","usage":{"cachedInputTokens":200983,"inputTokens":162,"model":"claude-fable-5-1","outputTokens":3258,"runtime":"claude","turns":10,"wallClockMs":50786}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"cd8076510aad8b8e","findings":[{"citation":"resolved","description":"Merged from audit_math, audit_permissions, audit_economics, audit_flow and the test writer's note (same root cause). buyWebsite swaps all of msg.value exact-input with sqrtPriceLimitX96 = TickMath.MIN_SQRT_PRICE + 1 (line 150) and the only floor on the output is `received >= price`, where price is the Intake's 0.5 IMD. Nothing ties the accepted output to the ETH sent, and there is no caller-supplied minimum output or deadline. The brief promises that every IMD above the price is sent back to the buyer, but an unprivileged searcher who sees the transaction in the public mempool can front-run with an ETH->IMD swap on the same hookless pool, let the purchase execute at the degraded rate (it still succeeds because output > 0.5 IMD), and back-run the IMD to ETH, keeping most of the buyer's surplus. The same mechanism applies to a transaction that lingers in the mempool. Reproduced on the vendored real PoolManager (test/scratch/Sandwich.t.sol). The README documents the exposure and the brief fixes the interface to buyWebsite(string) with no ETH price, so this is reported as a concrete buyer loss that needs a scope decision rather than a permission bypass: add a caller-supplied minimum IMD output (an overload buyWebsite(string,uint256 minImdOut[,uint256 deadline]) keeps the one-argument form), or cap the ETH accepted per purchase so the extractable surplus is bounded. Severity medium: loss of the buyer's surplus, not of the price paid, only for purchases large relative to pool depth submitted through the public mempool.","line":111,"path":"src/WebsiteBuyer.sol","reproduction":"Foundry, vendored PoolManager deployed with deployCodeTo at 0x000000000004444c5dc75cB358380D2e3dE08A90, MockIMD at 0xD34a99Bc0f67aE1bbd63C660e6d0b0dd03E263B7, MockIntake at 0xa43e6F75ee006411F79Ac1C84120606C2330DE82 selling job.open@job-1 for 0.5e18 IMD. Pool initialised at sqrtPrice 1<<96 (1 IMD per ETH), fee 10000, tickSpacing 200, 1000e18 liquidity in ticks [-60000, 60000]. (1) Baseline: alice calls buyWebsite{value: 50 ether}(\"a bakery in Lisbon\") -> alice receives 46665316817532158170 wei IMD surplus. (2) Revert to the same state; attacker contract unlocks the manager and swaps 2000 ETH zeroForOne exact-input; alice sends the identical call -> it SUCCEEDS (line 111 passes because output >= 0.5 IMD) and alice receives 4982996618264914027 wei IMD (-89%); attacker swaps all IMD back oneForZero and ends +17223831399440118686 wei ETH (17.2 ETH net of two 1% fees), taken from alice. Expected per the brief: every IMD above the price from alice's 50 ETH is returned to alice, or the call reverts. Actual: 89% of it goes to the searcher. With a 1 ETH purchase and a 40 ETH front-run on the same pool the surplus still drops (489020869339354039 -> 415143660860316165 wei) although the attacker loses on fees, so small purchases are protected only by the pool's fee, not by the contract.","severity":"medium","snippet":"        if (received < price) revert InsufficientIMD(received, price);","title":"Swap output is bounded only by the live Intake price, so a sandwich or a stale transaction can take almost the whole IMD surplus owed to the buyer"},{"citation":"resolved","description":"Merged from audit_math, audit_economics, audit_flow and the test writer. onIntakeResult writes state, resultHash, the string length word and one fresh 32-byte slot per 32 bytes of uri (~22,100 gas each) and emits the URI. The Intake attempts delivery once under callbackGas = 200,000 inside a try; a revert is recorded as not delivered and the request can never be delivered again, so the hub record remains State.Pending with resultHash zero and nothing can settle it later. Measured with the project's MockIntake.complete (same 200,000 stipend, warm transaction): 160 and 192 bytes delivered (state Done), 224 and 256 bytes not delivered (state Pending). On mainnet the callback arrives in its own transaction with cold slots, so the real threshold is somewhat lower. The documented URIs (ipfs://<CID>/result.json, 78 bytes; the plane's /intake-results/<id> fallback, ~110 bytes) fit, so this does not fail on today's inputs; a longer host path or a lower callbackGas would silently lose every result. Minimal fix that keeps the brief's 'store and emit hash and URI': set state and resultHash first and store the uri only when it fits a length budget (emit it always), so the Done transition and the hash never depend on URI length.","line":191,"path":"src/WebsiteBuyer.sol","reproduction":"WebsiteBuyerBase fixture: id = alice's buyWebsite{value: 1 ether}(\"a bakery in Lisbon\"); vm.prank(writer); intake.complete(id, 0, abi.encode(id, keccak256(\"r\"), <224 bytes of 'u'>)). Expected: delivered == true and buyer.requestOf(id).state == Done with the hash recorded. Actual: complete returns false, state stays Pending (1), resultHash stays 0, no WebsiteResult event; the same call with a 192-byte URI returns true and state becomes Done (2). Scratch test test/scratch/ReviewLows.t.sol::test_uriThreshold prints the threshold.","severity":"low","snippet":"        request.uri = uri;","title":"A result URI longer than about 192 bytes exhausts the Intake's 200,000-gas callback, so the result is never recorded and the request stays Pending forever"},{"citation":"resolved","description":"From audit_math; reproduced. The Intake selects the failure selector for any nonzero status and attempts delivery once. onIntakeFailure rejects every status outside {1,2}, so if the plane ever reports another terminal status the call reverts inside the Intake's try, the Intake marks the request completed and not delivered, and the hub record stays Pending forever with no reason stored; no later call can move it (a second complete is refused by the Intake). No funds depend on it (the price is already spent), so the impact is a permanently inconsistent record for that buyer. The sender is already authenticated as the accepting Intake by _pending, so the membership check adds no protection. Minimal fix: accept any nonzero status, store it as given and emit it.","line":199,"path":"src/WebsiteBuyer.sol","reproduction":"WebsiteBuyerBase fixture: id = alice's buyWebsite{value: 1 ether}(\"a bakery in Lisbon\"); vm.prank(writer); intake.complete(id, 3, abi.encode(id, uint8(3), bytes32(\"expired\"), uint16(0), uint16(0), bytes(\"\"))). Expected: the failure is recorded with its status and reason. Actual: the hub reverts InvalidFailureStatus(3), complete returns delivered == false, buyer.requestOf(id).state remains Pending and reason remains 0; a further intake.complete(id, 2, ...) reverts 'not pending' at the Intake. Scratch test test/scratch/ReviewLows.t.sol::test_failureStatus3.","severity":"low","snippet":"        if (status != 1 && status != 2) revert InvalidFailureStatus(status);","title":"A failure callback with a status other than 1 or 2 reverts and leaves the request Pending with no way to settle it"},{"citation":"resolved","description":"From audit_permissions; reproduced. The brief says the owner can change the Intake address and setIntake exists, but JOB_OPEN (job.open@job-1) and IMD are constants. The intake-client reference that governs protocol facts says the intake, the asset and the actions offered must be constructor input and owner-settable, never constants, because a new version (job-2) must not need a redeploy. When the Intake stops pricing job-1 and prices job-2 at the same address, priceOf returns 0 and every buyWebsite reverts ActionNotSold; setIntake cannot help because the action lives at the same address. The hub must be redeployed, abandoning accountOf: returning buyers get new CREATE2 accounts under the new hub and are new payers to the plane, so they can no longer have the hub pay for continuations of their existing projects. Fix: make the action id (and optionally the asset) owner-settable with an event, keeping the current defaults.","line":33,"path":"src/WebsiteBuyer.sol","reproduction":"WebsiteBuyerBase fixture: alice buys once (accountOf[alice] set). Then intake.setPrice(bytes32(\"job.open@job-1\"), IMD, 0) and intake.setPrice(bytes32(\"job.open@job-2\"), IMD, 0.5e18). alice calls buyWebsite{value: 1 ether}(\"a bakery\"). Expected: the owner can point the hub at the new action without redeploying. Actual: revert ActionNotSold (line 103) on every purchase; there is no setter for JOB_OPEN. Scratch test test/scratch/ReviewLows.t.sol::test_actionRetiredBricks.","severity":"low","snippet":"    bytes32 public constant JOB_OPEN = bytes32(\"job.open@job-1\");","title":"The action id and asset are compile-time constants, so retiring job-1 at the same Intake address bricks purchases with no owner recovery"},{"citation":"resolved","description":"Merged from the test writer, audit_math and audit_flow. bodyOf rejects bytes below 0x20, 0x7f, '\"' and '\\\\', which prevents JSON injection, but accepts any byte 0x80-0xff in any order. 0xff, 0xc0 0x80 (overlong), 0xf5-0xff and lone continuation bytes are never valid UTF-8; the Intake reference says the body is the UTF-8 bytes of strict JSON and lists body_not_json / body_invalid as refusals whose price is spent and never refunded. The contract therefore lets through a purchase that is refused off chain: the ETH is swapped, 0.5 IMD is paid and the buyer gets a failure callback. This matches the brief's literal refusal list and the loss is the buyer's own malformed input, so it is low; whether the plane refuses or substitutes U+FFFD could not be verified from here (README notes the public check endpoint returned 404). A structural UTF-8 check (lead-byte class and continuation count) in bodyOf would turn the refusal into a revert before any ETH is swapped.","line":170,"path":"src/WebsiteBuyer.sol","reproduction":"WebsiteBuyerBase fixture: buyer.bodyOf(string(abi.encodePacked(bytes1(0xff)))) returns 0x7b226f626a656374697665223a224275696c642061206f6e652d7061676520776562736974653a20ff222c... (a body containing the raw byte 0xff) instead of reverting BadWords. vm.prank(alice); buyer.buyWebsite{value: 1 ether}(string(abi.encodePacked(bytes1(0xff), bytes1(0xfe), bytes1(0xc0), bytes1(0x80)))) succeeds: the request is Pending and the treasury holds 0.5 IMD. Expected: revert BadWords before any swap, as for a quote or a control byte. Scratch test test/scratch/ReviewLows.t.sol::test_invalidUtf8Accepted.","severity":"low","snippet":"            if (c < 0x20 || c == 0x7f || c == 0x22 || c == 0x5c) revert BadWords();","title":"The words filter admits bytes that are never valid UTF-8, so a buyer can pay for a body the plane cannot parse"},{"citation":"resolved","description":"Merged from audit_permissions (two findings). These are requested powers, documented as trust assumptions, not bypasses. (a) The owner can call setIntake with any contract that has code (not self, not IMD). Every later purchase reads that contract's priceOf and the buyer's IntakeAccount approves and pays it `price`; a price equal to roughly the swap output collects everything a buyer sent, and a setIntake front-running a pending buyWebsite collects that buyer's payment with no work done. Pending callbacks stay bound to request.intake, so already-paid requests are unaffected. (b) The price is read live inside the purchase and is not bounded by any argument: a price move by the Intake operator (or a setIntake) landing between the buyer's quote and the mined transaction is paid in full up to `received`, turning the buyer's ETH margin into price. (c) setPaused(true) followed by the inherited renounceOwnership leaves whenNotPaused enforced forever; buyers keep control of their accounts. (d) Verified absent: the owner has no sweep or arbitrary-call power over the hub or over buyers' accounts (execute is user-only, the hub has no receive, no token rescue). Mitigation is operational (multisig/timelock owner, monitoring IntakeChanged) or the same extra argument as finding 1 (a maxPrice/minSurplus).","line":212,"path":"src/WebsiteBuyer.sol","reproduction":"(b) WebsiteBuyerBase fixture (mock pool returns 2 IMD per ETH): intake.setPrice(JOB_OPEN, IMD, 1.9e18) before alice's buyWebsite{value: 1 ether}(\"a bakery\") -> the treasury receives 1.9 IMD and alice 0.1 IMD instead of 1.5 IMD; the call does not revert. (c) vm.prank(OWNER); buyer.setPaused(true); buyer.renounceOwnership(); any buyWebsite reverts EnforcedPause and owner() is address(0) with no path to unpause. (a) By construction at lines 212-218 and 102: a FakeIntake whose priceOf returns 1.99e18 and whose requestWithFailure pulls `amount` collects 1.99 IMD per 1 ETH purchase. Scratch tests test/scratch/ReviewLows.t.sol::test_priceRaceTakesMargin and ::test_pauseThenRenounce.","severity":"info","snippet":"    function setIntake(IIntake next) external onlyOwner nonReentrant {","title":"Trust assumptions of the owner and the Intake operator: setIntake redirects every future price to arbitrary code, a live price change takes the buyer's whole margin, and pause plus renounceOwnership f"},{"citation":"resolved","description":"From the test writer; reproduced. Purchases measure IMD relative to the pre-swap balance and require the hub to end each purchase at exactly that balance, so IMD donated to the hub can never fund a purchase, reach a buyer or be swept: there is no rescue function. The brief's 'holds no IMD between calls' holds for every purchase but not for third-party transfers. Plain ETH transfers are rejected because the hub has no receive. No user funds are at stake unless someone sends IMD to the hub by mistake.","line":106,"path":"src/WebsiteBuyer.sol","reproduction":"WebsiteBuyerBase fixture: imd.mint(address(buyer), 10 ether); alice's buyWebsite{value: 1 ether}(\"a bakery in Lisbon\") succeeds; imd.balanceOf(address(buyer)) == 10 ether afterwards and no function can move it. Scratch test test/scratch/ReviewLows.t.sol::test_donatedImdStuck.","severity":"info","snippet":"        uint256 beforeSwap = token.balanceOf(address(this));","title":"IMD transferred directly to the hub is permanently stuck"}],"hash":"c0e6d78f2cb3c9f597f4c3ad50aa100b669a8cf0a091308351df2bb4adebcf8b","nodeId":"ae52775d-4c33-4225-86ec-8b27f5223af5","outcome":"completed","summary":"The review is complete and `.imd-findings.json` holds seven merged findings plus a 15-row coverage record.\n\n**What I did.** I read both contracts, the manifest, the mocks and the test base, built the project with sizes, ran the existing 71 non-invariant tests (all pass), and reproduced every specialist lead in two scratch suites under `test/scratch/`. The sandwich ran on the vendored real Uniswap v4 PoolManager at the hardcoded addresses.\n\n**Findings kept, after merging duplicates:**\n\n- **Medium, no output floor beyond the live price.** Four specialists and the tester reported the same root cause at the `received < price` check. Reproduced: a 2000 ETH front-run cut a 50 ETH buyer's surplus by 89 percent while the call still succeeded, and the attacker netted 17.2 ETH. Kept at medium because the loss is the buyer's surplus under public-mempool conditions and the brief fixed the one-argument interface, so it needs a scope decision.\n- **Low, URI above about 192 bytes exhausts the 200k callback stipend.** Reproduced the threshold: 192 bytes delivers, 224 does not, and the request stays Pending with no hash recorded.\n- **Low, failure status outside 1 or 2 reverts** and strands the record Pending. Reproduced with status 3.\n- **Low, action id and asset are constants.** Retiring job-1 at the same Intake address makes every purchase revert ActionNotSold with no owner setter. Reproduced.\n- **Low, invalid UTF-8 bytes pass the words filter.** Three reports merged. Reproduced on chain; the plane's refusal itself is stated as unverified.\n- **Info, owner and Intake operator trust assumptions,** including the live-price race that converts the buyer's ETH margin into price, and pause plus renounce freezing purchases. Two permission reports merged.\n- **Info, IMD sent directly to the hub is stuck.** Reproduced.\n\n**My own pass found nothing additional.** The unlock callback is gated to the PoolManager during the contract's own unlock, settles native ETH after a sync reset, and enforces a full exact-input fill. Both callbacks check the accepting Intake and Pending state. Runtime size is 10,426 bytes, the constructor touches no external code, and launch.json matches the constructor. The static-analysis leads did not reproduce as defects. No finding reached high or critical, so no proof file is attached.","treeHash":null,"usage":{"cachedInputTokens":733257,"inputTokens":258,"model":"claude-fable-5-1","outputTokens":23182,"runtime":"claude","turns":18,"wallClockMs":320324}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"a7ac9f7a7687c25e","findings":[{"citation":"resolved","description":"Trust-gap seam economics x asymmetry. buyWebsite swaps ALL of msg.value with sqrtPriceLimitX96 = TickMath.MIN_SQRT_PRICE + 1 (line 150) and the only output check is `received >= price` (line 111). The Intake price (0.5 IMD) is the floor for the protocol's share, but the buyer's share (`surplus = received - price`, line 132) has no floor at all and no caller-supplied bound, deadline or quote. The two parties to the same swap are therefore treated asymmetrically: the Intake always receives exactly its price, the buyer receives whatever is left after any unprivileged searcher moves the pool price in the same block. A searcher who sees `buyWebsite{value: V}` in the public mempool can front-run with an ETH->IMD swap sized so the victim's output lands just above `price`, let the purchase execute, and back-run the IMD back to ETH. The victim still pays all of V and gets ~0 surplus; the searcher captures the difference minus two 1% pool fees. The attack is profitable whenever the fee cost of moving the price far enough is below the victim's surplus, i.e. whenever the buyer's ETH is large relative to pool depth (any buyer who sends a comfortable margin over the price, as the README itself recommends in 'quote conservatively'). The README documents the exposure as a limit; it is still a concrete loss of buyer funds to an unprivileged actor on the single user-facing function. Suggested fix needs a scope decision because the requested interface is `buyWebsite(string)`: either add a caller-supplied `minSurplus`/`minImdOut` (an overload keeps the one-argument form), or swap exact-output for `price` with a caller-supplied `maxEthIn` and refund the unspent ETH. Either preserves the live-price design; neither is possible without one extra argument.","line":111,"path":"src/WebsiteBuyer.sol","reproduction":"Reproduced on the vendored real PoolManager (deployCodeTo at 0x0000...08A90, pool initialised at sqrtPrice 1<<96, fee 10000, tickSpacing 200, 20e18 liquidity in ticks [-60000, 60000], MockIntake price 0.5 IMD). Baseline: alice calls buyWebsite{value: 1 ether}(\"a bakery in Lisbon\") alone -> alice receives 443306336350643163 wei IMD surplus (0.443 IMD). Sandwich: (1) attacker contract unlocks the manager and swaps 7.6 ETH zeroForOne exact-input into the pool; (2) alice's identical buyWebsite{value: 1 ether} executes: the swap returns just over the price, the check on line 111 passes, alice's surplus is 4574724188497822 wei (0.0046 IMD) instead of 0.443 IMD; (3) attacker swaps all IMD it received back oneForZero and ends with 100.3387 ETH from a 100 ETH start: net profit 0.3387 ETH, taken from alice. Expected: a buyer who sends 1 ETH for a 0.5 IMD job keeps the surplus that the pool quoted at submission time (or can bound its loss). Actual: the only guard is `received >= price`, so the searcher chooses how much of the surplus the buyer keeps. Scratch test used: test/scratch/Sandwich.t.sol (two Trader contracts implementing IUnlockCallback, one as LP and one as attacker), run with `forge test --match-path test/scratch/Sandwich.t.sol -vv`.","severity":"medium","snippet":"        if (received < price) revert InsufficientIMD(received, price);","title":"Surplus IMD has no floor: a sandwich can take everything above the Intake price (economics x asymmetry seam)"},{"citation":"resolved","description":"Trust gap access x economics with a race amplifier. The price is read live from the Intake inside the purchase and is neither echoed back to the buyer for confirmation nor bounded by any argument. The body of a purchase is fixed, so the only thing the buyer chooses is msg.value; the brief says the buyer 'sends any amount of ETH that buys at least the price', which in practice means buyers send a margin. Any price change by the Intake operator (a trusted third party whose price is explicitly 'owner-settable' and 'may move' per the intake-client reference) or by this hub's owner via setIntake that lands between the buyer's quote and the buyer's transaction is paid in full, up to the entire swap output: the buyer's margin silently becomes price. The brief asks for the live price, so this is reported as a trust gap to document rather than a bypass; the fix is the same extra argument as finding 1 (a `maxPrice` or `minSurplus`), or at minimum emitting the quoted price in a view a frontend can show.","line":102,"path":"src/WebsiteBuyer.sol","reproduction":"State: MockIntake price for job.open@job-1 is 0.5 IMD, MockPoolManager configured to return 2 IMD for 1 ETH. Alice reads priceOf -> 0.5e18 and submits buyWebsite{value: 1 ether}(\"a bakery\"). Before it is mined the Intake owner calls setPrice(job.open@job-1, IMD, 1.9e18) (or the hub owner calls setIntake to an Intake with that price). Alice's transaction executes: price = 1.9e18, received = 2e18, line 111 passes, the account pays 1.9 IMD to the treasury, alice gets 0.1 IMD surplus instead of 1.5 IMD. Expected: a buyer who quoted 0.5 IMD can bound what the call pays. Actual: anything up to `received` is paid without the buyer's consent. The existing test test_livePriceChangesWithoutAnETHPriceSetting already drives this path and asserts the larger payment as intended behaviour.","severity":"low","snippet":"        uint256 price = to.priceOf(JOB_OPEN, IMD);","title":"Buyer has no cap on the live Intake price: all of the swap output up to `received` can be taken as price in the same call"},{"citation":"resolved","description":"Asymmetry between the admin surface and the dependency surface (Asymmetry guide step 5, admin-function variants). The brief says 'The owner can change the Intake address', and setIntake exists, but the action id `job.open@job-1` and the asset IMD are compile-time constants. The intake-client reference that governs protocol facts states that the intake, the asset and the actions offered must be 'constructor input and owner-settable, never a constant: a new version (job-2) or a moved price must not need a redeploy'. When the Intake stops pricing job-1 (a version bump to job-2 while the Intake address is unchanged), priceOf returns 0 and every buyWebsite reverts ActionNotSold forever; setIntake cannot fix it because the new action lives at the same Intake address. The hub is then dead and must be redeployed, abandoning accountOf state (new accounts are created at new CREATE2 addresses under the new hub, so returning buyers become new payers and lose the ability to have the hub pay for their existing project). The brief asked for constants for the pool parameters; it did not ask for the action id to be constant, and the reference asks for the opposite. Fix: make the action id (and optionally the asset) an owner-settable value emitted on change, keeping the current defaults.","line":33,"path":"src/WebsiteBuyer.sol","reproduction":"State: deployed hub, alice has bought once (accountOf[alice] set). The Intake operator retires job-1: intake.setPrice(bytes32(\"job.open@job-1\"), IMD, 0) and prices bytes32(\"job.open@job-2\") at 0.5 IMD at the same address. Alice calls buyWebsite{value: 1 ether}(\"a bakery\") -> reverts ActionNotSold (line 103). Owner calls setIntake(sameIntake) -> no effect on the action; there is no setter for JOB_OPEN. Expected: the owner can point the hub at the new action without redeploying. Actual: purchases are permanently unavailable from this hub.","severity":"low","snippet":"    bytes32 public constant JOB_OPEN = bytes32(\"job.open@job-1\");","title":"Only the Intake address is owner-settable; the action id and asset are constants, so a job-2 version or an unsold job-1 bricks purchases with no owner recovery"},{"citation":"resolved","description":"Documented as privileged powers, not a bypass; recorded here because the assignment asks for material owner risks to be stated with actor and preconditions. (a) The owner (0x047F606fD5b2BaA5f5C6c4aB8958E45CB6B054B7 per README) can call setIntake with any contract that has code. From then on every purchase reads that contract's priceOf and the buyer's IntakeAccount approves and pays it `price`; a contract returning a price equal to roughly the swap output collects everything a buyer sent, and a front-run of a pending buyWebsite with setIntake collects that buyer's payment with no work done (race amplifier, but the actor is the owner). Pending callbacks stay bound to the accepting Intake (request.intake), so already-paid requests are not affected. (b) setPaused(true) followed by the inherited renounceOwnership leaves whenNotPaused permanently enforced: no purchases ever again; buyers keep control of their IntakeAccounts. (c) The owner has no sweep or arbitrary-call power over the hub or over buyers' accounts (verified: execute is user-only, hub has no receive, no token rescue). No code change is required by the brief; mitigation is operational (multisig/timelock owner, monitoring IntakeChanged).","line":213,"path":"src/WebsiteBuyer.sol","reproduction":"(a) Owner deploys FakeIntake with priceOf returning 1.99e18 and requestWithFailure that pulls `amount` from msg.sender and returns a fresh id; owner calls setIntake(FakeIntake). Alice calls buyWebsite{value: 1 ether} with the mock pool returning 2 IMD: price = 1.99e18, line 111 passes, FakeIntake pulls 1.99 IMD from alice's account; alice gets 0.01 IMD and no website. (b) Owner calls setPaused(true) then renounceOwnership(); any buyWebsite reverts EnforcedPause; no address can unpause.","severity":"info","snippet":"        if (address(next).code.length == 0 || address(next) == address(this) || address(next) == IMD) {","title":"Trust assumptions of the owner role: setIntake can redirect every future price to arbitrary code; pause plus renounceOwnership freezes purchases forever"}],"hash":"ef49dc776369a3edb4c2cb557c121b4a295bc97190108e52f9d101821bbf9c7b","nodeId":"e60e182d-be96-48b4-9831-c815d573b159","outcome":"completed","summary":"The review is complete and written to `.imd-findings.json` at the repository root. No source, config or test files outside `test/scratch/` were changed.\n\n**Outcome.** The permission model is sound. Every entry point was traced: hub-only `ask`, user-only `execute`, PoolManager-only `unlockCallback` with a single-use flag, callbacks bound to the Intake that accepted each request, and OpenZeppelin two-step ownership with an explicit constructor owner. No permission bypass, confused deputy or inconsistent guard exists. The existing suite of 59 tests passes.\n\n**Findings reported**, all with concrete reproductions I ran locally:\n\n1. **Medium. Surplus IMD has no floor.** The only output check is `received >= price`, so an unprivileged searcher can sandwich a purchase and take everything above the Intake price. On the vendored real PoolManager with 20e18 liquidity, a 7.6 ETH front-run cut the buyer's surplus from 0.443 IMD to 0.0046 IMD and netted the attacker 0.339 ETH. The one-argument interface the brief asked for is the root cause, so the fix needs a scope decision on adding a bound argument.\n2. **Low. Live price is uncapped.** A price change landing before the buyer's transaction is paid in full, up to the whole swap output. Reproduced: price raised to 1.9 IMD, the buyer paid 1.9 IMD instead of 0.5.\n3. **Low. Action id is a constant.** If the Intake retires `job.open@job-1` for `job-2`, every purchase reverts `ActionNotSold` and `setIntake` cannot repair it. The intake-client reference requires the action to be owner-settable.\n4. **Info. Owner trust assumptions.** `setIntake` can redirect every future price to arbitrary code, and pause plus `renounceOwnership` freezes purchases permanently. Pending requests stay bound to their original Intake, so paid work is unaffected.\n\n**Coverage.** All 12 listed entry points have rows, plus three invariants and one row per assigned guide. Uniswap settlement math and token edge cases are marked unreached as outside my area.","treeHash":null,"usage":{"cachedInputTokens":1239515,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":38222,"runtime":"claude","turns":30,"wallClockMs":573955}}],"verification":[{"checks":[{"durationMs":4074,"exitCode":0,"name":"build","output":"Compiling 95 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.93s\nCompiler run successful!\n","passed":true},{"durationMs":231,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] test_constructorRejectsZeroOwner() (gas: 4059)\n[PASS] test_factoryConstructorWorksWithNoExternalContractsAndExplicitOwner() (gas: 8461286)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 68.53ms (41.91ms CPU time)\n\nRan 13 tests for test/WebsiteCallbacks.t.sol:WebsiteCallbacksTest\n[PASS] testFuzz_anyNonzeroFailureStatusIsRecorded(uint8) (runs: 512, μ: 1271414, ~: 1271383)\nLogs:\n  Bound result 169\n\n[PASS] test_failureCannotBeOverwrittenByEitherCallback() (gas: 1298430)\n[PASS] test_failureRejectsStatusZero() (gas: 1194883)\n[PASS] test_intakeMigrationKeepsOldAndNewCallbacksSeparate() (gas: 3591801)\n[PASS] test_longUriStillSettlesWithHashAndIsEmittedNotStored() (gas: 2040715)\n[PASS] test_missingAndUndeliverableCallbacksDoNotLockFuturePurchases() (gas: 8461330)\n[PASS] test_mockOnlyWriterCanComplete() (gas: 1179313)\n[PASS] test_pausedPurchasesDoNotBlockCallbacks() (gas: 2470104)\n[PASS] test_refusalAndFailureStoredAndEmittedWithin200000Gas() (gas: 2091960)\n[PASS] test_resultCannotBeOverwrittenByEitherCallback() (gas: 1404158)\n[PASS] test_resultStoredAndEmittedWithin200000Gas() (gas: 1486009)\n[PASS] test_uriAtStorageLimitIsStored() (gas: 1445346)\n[PASS] test_wrongSenderAndUnknownRequestsRejectedOnBothCallbacks() (gas: 1279503)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 70.41ms (121.08ms CPU time)\n\nRan 10 tests for test/WebsiteRevision.t.sol:WebsiteRevisionTest\n[PASS] testFuzz_everyScalarValueIsAccepted(uint32) (runs: 512, μ: 12181, ~: 11828)\nLogs:\n  Bound result 1114072\n\n[PASS] test_expiredDeadlineReverts() (gas: 59628)\n[PASS] test_malformedUTF8IsRefusedBeforeAnySwap() (gas: 1431964)\n[PASS] test_minimumSurplusMetIsAccepted() (gas: 1311269)\n[PASS] test_minimumVariantRespectsPauseAndBadWords() (gas: 136927)\n[PASS] test_ownerMovesPurchasesToNewActionVersionKeepingAccounts() (gas: 2018408)\n[PASS] test_priceRaisedAfterQuoteIsCaughtByMinimum() (gas: 402803)\n[PASS] test_setActionOnlyOwnerAndNonZero() (gas: 78511)\n[PASS] test_surplusBelowMinimumRevertsBeforeAnyPayment() (gas: 394146)\n[PASS] test_wellFormedUTF8BoundariesAreAccepted() (gas: 108287)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 77.74ms (29.96ms CPU time)\n\nRan 6 tests for test/WebsiteWords.t.sol:WebsiteWordsTest\n[PASS] testFuzz_invalidByteAtAnyPositionIsRefused(uint16,uint16,uint8) (runs: 512, μ: 74663, ~: 47556)\nLogs:\n  Bound result 473\n\n[PASS] testFuzz_validWordsCannotInjectJSON(bytes) (runs: 512, μ: 47478, ~: 40191)\n[PASS] test_UTF8AllowedAndLimitCountsBytes() (gas: 1808316)\n[PASS] test_everyASCIIControlByteIncludingDELIsRefused() (gas: 2401100)\n[PASS] test_oneByteAndExactly1000BytesAreAccepted() (gas: 3455125)\n[PASS] test_rejectsEmptyQuoteBackslashNewlineAndExcessBytes() (gas: 372142)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 80.49ms (170.13ms CPU time)\n\nRan 8 tests for test/IntakeAccount.t.sol:IntakeAccountTest\n[PASS] test_accountRejectsZeroUser() (gas: 3739)\n[PASS] test_buyerCanUseSamePayerForContinuationOtherBuyerCannot() (gas: 3507721)\n[PASS] test_contractBuyerGetsSurplusWithoutAnETHReceiveFunction() (gas: 1810813)\n[PASS] test_existingBuyerFundsInAccountAreNotSpentByNewPurchase() (gas: 1732019)\n[PASS] test_onlyHubAsksAndOnlyBuyerExecutes() (gas: 1285803)\n[PASS] test_pauseDoesNotTakeAwayBuyerControlOfPayer() (gas: 1458753)\n[PASS] test_payableExecuteAndReturnDataAndRevertBubbling() (gas: 1275166)\n[PASS] test_userCannotReenterOwnAccountExecution() (gas: 1759367)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 86.96ms (13.99ms CPU time)\n\nRan 26 tests for test/WebsiteBuyer.t.sol:WebsiteBuyerTest\n[PASS] testFuzz_conservationAndLivePrice(uint96,uint96,uint96) (runs: 512, μ: 1397369, ~: 1398737)\nLogs:\n  Bound result 4107274919\n  Bound result 3217\n  Bound result 1092\n\n[PASS] testFuzz_shortOutputAlwaysReverts(uint128) (runs: 512, μ: 492931, ~: 492660)\nLogs:\n  Bound result 1001\n\n[PASS] test_belowPriceRevertsEntireSwapEvenWithDonatedIMD() (gas: 481350)\n[PASS] test_buysExactBodyThroughOwnPayerAndReturnsEverySurplusToken() (gas: 1382727)\n[PASS] test_constructorAndExactPoolIdentity() (gas: 57097)\n[PASS] test_duplicateRequestCannotOverwriteAnotherBuyer() (gas: 2116447)\n[PASS] test_exactPriceNoSurplusTransferRequired() (gas: 1327505)\n[PASS] test_failedApprovalAndTreasuryTransferRollBack() (gas: 1903352)\n[PASS] test_failedRefundRollsBackRequestPaymentAndAccountDeployment() (gas: 1354226)\n[PASS] test_feeOnTransferRejected() (gas: 420159)\n[PASS] test_intakeRevertAndMissingPaymentRollBack() (gas: 2251148)\n[PASS] test_invalidIntakeAddressesRejected() (gas: 143008)\n[PASS] test_livePriceChangesWithoutAnETHPriceSetting() (gas: 1836370)\n[PASS] test_missingOrRepeatedUnlockCallbackReverts() (gas: 534918)\n[PASS] test_noDirectETHDeposits() (gas: 38923)\n[PASS] test_onlyOwnerCanPauseAndChangeIntake() (gas: 1315323)\n[PASS] test_ownershipHandoverRequiresAcceptance() (gas: 226249)\n[PASS] test_partialFillAndZeroOutputRevert() (gas: 615272)\n[PASS] test_repeatBuyerReusesAccountAndOtherBuyerHasDifferentPayer() (gas: 2989490)\n[PASS] test_shortTokenDeliveryDetected() (gas: 404907)\n[PASS] test_swapsAllETHWithCanonicalPoolAndNoHookData() (gas: 1202192)\n[PASS] test_tokenAndIntakeCannotReenterPurchase() (gas: 1572137)\n[PASS] test_tokensWithEmptyReturnDataWork() (gas: 1309086)\n[PASS] test_unlockOnlyFromManagerDuringPurchase() (gas: 1226931)\n[PASS] test_unpricedRevertsBeforeAnySwap() (gas: 117887)\n[PASS] test_zeroAndUnrepresentableETHRejected() (gas: 83228)\nSuite result: ok. 26 passed; 0 failed; 0 skipped; finished in 88.48ms (161.35ms CPU time)\n\nRan 8 tests for test/UniswapIntegration.t.sol:UniswapIntegrationTest\n[PASS] testFuzz_realPoolConservation(uint96) (runs: 512, μ: 1476400, ~: 1476453)\nLogs:\n  Bound result 9400000000000000267\n\n[PASS] test_realManagerStaleERC20SyncDoesNotBlockNativeSettlement() (gas: 1647006)\n[PASS] test_realPoolMultiplePurchasesDoNotAccumulateCustody() (gas: 3181574)\n[PASS] test_realPoolPartialFillAtLiquidityBoundaryReverts() (gas: 631363)\n[PASS] test_realPoolSandwichIsRefusedByBuyerMinimum() (gas: 3305240)\n[PASS] test_realPoolShortOutputRollsBackPoolPriceAndBalances() (gas: 539781)\n[PASS] test_realPoolSwapsPaysAndRefundsWithAllDeltasSettled() (gas: 1484413)\n[PASS] test_realPoolWithoutLiquidityCannotChargeBuyer() (gas: 243115)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 89.88ms (103.79ms CPU time)\n\nRan 7 test suites in 91.37ms (562.48ms CPU time): 73 tests passed, 0 failed, 0 skipped (73 total tests)\n","passed":true},{"durationMs":69,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IntakeAccount.ask(address,bytes32,bytes,(address,bytes4),bytes4,address,uint256)\",\"IntakeAccount.execute(address,uint256,bytes)\",\"IntakeAccount.receive()\",\"WebsiteBuyer.acceptOwnership()\",\"WebsiteBuyer.buyWebsite(string)\",\"WebsiteBuyer.buyWebsiteWithMinimum(string,uint256,uint256)\",\"WebsiteBuyer.onIntakeFailure(bytes32,uint8,bytes32,uint16,uint16,bytes)\",\"WebsiteBuyer.onIntakeResult(bytes32,bytes32,string)\",\"WebsiteBuyer.renounceOwnership()\",\"WebsiteBuyer.setAction(bytes32)\",\"WebsiteBuyer.setIntake(address)\",\"WebsiteBuyer.setPaused(bool)\",\"WebsiteBuyer.transferOwnership(address)\",\"WebsiteBuyer.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":235,\"docs/DEPENDENCIES.md\":33,\"docs/VALIDATION.md\":78,\"foundry.toml\":21,\"launch.json\":12,\"remappings.txt\":4,\"src/IntakeAccount.sol\":71,\"src/WebsiteBuyer.sol\":325,\"src/interfaces/IIntake.sol\":21,\"test/Deployment.t.sol\":51,\"test/IntakeAccount.t.sol\":142,\"test/UniswapIntegration.t.sol\":273,\"test/WebsiteBuyer.t.sol\":339,\"test/WebsiteBuyerBase.t.sol\":77,\"test/WebsiteCallbacks.t.sol\":200,\"test/WebsiteRevision.t.sol\":170,\"test/WebsiteWords.t.sol\":85,\"test/mocks/MockIMD.sol\":67,\"test/mocks/MockIntake.sol\":129,\"test/mocks/MockPoolManager.sol\":87},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"1e301487dcc0d875c0fdf0c0818cba069bc5359259e652adf60910b358ed0508","verifiedTreeHash":"85c99899c2bbcc960107b80ff9dbffa2f448a52e","verifierVersion":"0.1.0+c3fba9eb"},{"checks":[{"durationMs":7907,"exitCode":0,"name":"build","output":"Compiling 97 files with Solc 0.8.26\nSolc 0.8.26 finished in 7.75s\nCompiler run successful!\n","passed":true},{"durationMs":6666,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] test_constructorRejectsZeroOwner() (gas: 4059)\n[PASS] test_factoryConstructorWorksWithNoExternalContractsAndExplicitOwner() (gas: 7821008)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 69.86ms (28.20ms CPU time)\n\nRan 8 tests for test/IntakeAccount.t.sol:IntakeAccountTest\n[PASS] test_accountRejectsZeroUser() (gas: 3739)\n[PASS] test_buyerCanUseSamePayerForContinuationOtherBuyerCannot() (gas: 3502347)\n[PASS] test_contractBuyerGetsSurplusWithoutAnETHReceiveFunction() (gas: 1807970)\n[PASS] test_existingBuyerFundsInAccountAreNotSpentByNewPurchase() (gas: 1727433)\n[PASS] test_onlyHubAsksAndOnlyBuyerExecutes() (gas: 1283204)\n[PASS] test_pauseDoesNotTakeAwayBuyerControlOfPayer() (gas: 1456021)\n[PASS] test_payableExecuteAndReturnDataAndRevertBubbling() (gas: 1272567)\n[PASS] test_userCannotReenterOwnAccountExecution() (gas: 1756612)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 80.96ms (10.63ms CPU time)\n\nRan 12 tests for test/WebsiteBuyerEdges.t.sol:WebsiteBuyerEdgesTest\n[PASS] testFuzz_resultURIUpTo192BytesIsDelivered(uint8,bytes32) (runs: 256, μ: 1354142, ~: 1313165)\nLogs:\n  Bound result 163\n\n[PASS] test_badWordsRefusedBeforeReadingAnUnsoldPrice() (gas: 75898)\n[PASS] test_bodyNeverCarriesAnOwnerField() (gas: 54515)\n[PASS] test_failedDeliveryLeavesRequestPendingAndWriterCannotRetry() (gas: 1288380)\n[PASS] test_failureWithPanelFieldsAndLongSignatureIsRecordedWithinStipend() (gas: 1563823)\n[PASS] test_largestRepresentableETHAmountIsAccepted() (gas: 1294011)\n[PASS] test_oneWeiPurchaseWorksWhenThePoolPaysThePrice() (gas: 1258966)\n[PASS] test_ownerAndOtherBuyerCannotSpendFromABuyersAccount() (gas: 1773279)\n[PASS] test_pauseTransitionsAreStrictAndPreviewStillWorks() (gas: 186259)\n[PASS] test_planeFallbackURIIsDeliveredWithinTheStipend() (gas: 1383011)\n[PASS] test_prefundedAccountAddressStillOpensAndFundsStayWithBuyer() (gas: 1453886)\n[PASS] test_requestRecordsTheIntakeTakenNotTheCurrentOne() (gas: 2758560)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 81.07ms (79.30ms CPU time)\n\nRan 6 tests for test/WebsiteWords.t.sol:WebsiteWordsTest\n[PASS] testFuzz_invalidByteAtAnyPositionIsRefused(uint16,uint16,uint8) (runs: 512, μ: 80954, ~: 48174)\nLogs:\n  Bound result 838\n\n[PASS] testFuzz_validWordsCannotInjectJSON(bytes) (runs: 512, μ: 47096, ~: 39822)\n[PASS] test_UTF8AllowedAndLimitCountsBytes() (gas: 1728605)\n[PASS] test_everyASCIIControlByteIncludingDELIsRefused() (gas: 2394905)\n[PASS] test_oneByteAndExactly1000BytesAreAccepted() (gas: 3426489)\n[PASS] test_rejectsEmptyQuoteBackslashNewlineAndExcessBytes() (gas: 371736)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 83.50ms (166.37ms CPU time)\n\nRan 10 tests for test/WebsiteCallbacks.t.sol:WebsiteCallbacksTest\n[PASS] testFuzz_failureRejectsOtherStatusCodes(uint8) (runs: 512, μ: 1193058, ~: 1193060)\n[PASS] test_failureCannotBeOverwrittenByEitherCallback() (gas: 1296053)\n[PASS] test_intakeMigrationKeepsOldAndNewCallbacksSeparate() (gas: 3587126)\n[PASS] test_missingAndUndeliverableCallbacksDoNotLockFuturePurchases() (gas: 4212754)\n[PASS] test_mockOnlyWriterCanComplete() (gas: 1176780)\n[PASS] test_pausedPurchasesDoNotBlockCallbacks() (gas: 2465040)\n[PASS] test_refusalAndFailureStoredAndEmittedWithin200000Gas() (gas: 2087333)\n[PASS] test_resultCannotBeOverwrittenByEitherCallback() (gas: 1401621)\n[PASS] test_resultStoredAndEmittedWithin200000Gas() (gas: 1483321)\n[PASS] test_wrongSenderAndUnknownRequestsRejectedOnBothCallbacks() (gas: 1276989)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 90.49ms (101.07ms CPU time)\n\nRan 7 tests for test/UniswapIntegration.t.sol:UniswapIntegrationTest\n[PASS] testFuzz_realPoolConservation(uint96) (runs: 512, μ: 1473674, ~: 1473730)\nLogs:\n  Bound result 9400000000000002996\n\n[PASS] test_realManagerStaleERC20SyncDoesNotBlockNativeSettlement() (gas: 1644257)\n[PASS] test_realPoolMultiplePurchasesDoNotAccumulateCustody() (gas: 3170593)\n[PASS] test_realPoolPartialFillAtLiquidityBoundaryReverts() (gas: 629161)\n[PASS] test_realPoolShortOutputRollsBackPoolPriceAndBalances() (gas: 537365)\n[PASS] test_realPoolSwapsPaysAndRefundsWithAllDeltasSettled() (gas: 1481744)\n[PASS] test_realPoolWithoutLiquidityCannotChargeBuyer() (gas: 240892)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 105.34ms (115.20ms CPU time)\n\nRan 26 tests for test/WebsiteBuyer.t.sol:WebsiteBuyerTest\n[PASS] testFuzz_conservationAndLivePrice(uint96,uint96,uint96) (runs: 512, μ: 1394862, ~: 1396280)\nLogs:\n  Bound result 4294967294\n  Bound result 7400\n  Bound result 4725\n\n[PASS] testFuzz_shortOutputAlwaysReverts(uint128) (runs: 512, μ: 490560, ~: 490278)\nLogs:\n  Bound result 169\n\n[PASS] test_belowPriceRevertsEntireSwapEvenWithDonatedIMD() (gas: 479050)\n[PASS] test_buysExactBodyThroughOwnPayerAndReturnsEverySurplusToken() (gas: 1380062)\n[PASS] test_constructorAndExactPoolIdentity() (gas: 57026)\n[PASS] test_duplicateRequestCannotOverwriteAnotherBuyer() (gas: 2111290)\n[PASS] test_exactPriceNoSurplusTransferRequired() (gas: 1324888)\n[PASS] test_failedApprovalAndTreasuryTransferRollBack() (gas: 1898192)\n[PASS] test_failedRefundRollsBackRequestPaymentAndAccountDeployment() (gas: 1351633)\n[PASS] test_feeOnTransferRejected() (gas: 417870)\n[PASS] test_intakeRevertAndMissingPaymentRollBack() (gas: 2245900)\n[PASS] test_invalidIntakeAddressesRejected() (gas: 143007)\n[PASS] test_livePriceChangesWithoutAnETHPriceSetting() (gas: 1831784)\n[PASS] test_missingOrRepeatedUnlockCallbackReverts() (gas: 530304)\n[PASS] test_noDirectETHDeposits() (gas: 38923)\n[PASS] test_onlyOwnerCanPauseAndChangeIntake() (gas: 1312641)\n[PASS] test_ownershipHandoverRequiresAcceptance() (gas: 226289)\n[PASS] test_partialFillAndZeroOutputRevert() (gas: 610658)\n[PASS] test_repeatBuyerReusesAccountAndOtherBuyerHasDifferentPayer() (gas: 2982217)\n[PASS] test_shortTokenDeliveryDetected() (gas: 402618)\n[PASS] test_swapsAllETHWithCanonicalPoolAndNoHookData() (gas: 1199614)\n[PASS] test_tokenAndIntakeCannotReenterPurchase() (gas: 1569448)\n[PASS] test_tokensWithEmptyReturnDataWork() (gas: 1306487)\n[PASS] test_unlockOnlyFromManagerDuringPurchase() (gas: 1224618)\n[PASS] test_unpricedRevertsBeforeAnySwap() (gas: 115420)\n[PASS] test_zeroAndUnrepresentableETHRejected() (gas: 82952)\nSuite result: ok. 26 passed; 0 failed; 0 skipped; finished in 105.40ms (170.39ms CPU time)\n\nRan 1 test for test/RealPoolInvariant.t.sol:RealPoolInvariantTest\n[PASS]\nRealPoolInvariantTest invariants:\n[PASS] invariant_realPoolHubCustodyIsZeroAndManagerSettled\n[PASS] invariant_realPoolValueConserved\n RealPoolInvariantTest invariants (runs: 128, calls: 4096, reverts: 0)\n\n╭-----------------+------------+-------+---------+----------╮\n| Contract        | Selector   | Calls | Reverts | Discards |\n+===========================================================+\n| RealPoolHandler | buy        | 2730  | 0       | 0        |\n|-----------------+------------+-------+---------+----------|\n| RealPoolHandler | moveMarket | 1366  | 0       | 0        |\n╰-----------------+------------+-------+---------+----------╯\n\nLogs:\n  Bound result 10\n  Bound result 11\n  Bound result 6123645191730515706\n  Bound result 387434\n  Bound result 49990000000000000013\n  Bound result 1747\n  Bound result 17189\n  Bound result 49990000000000000170\n  Bound result 10000000000000000000\n  Bound result 244\n  Bound result 10000000000000000000\n  Bound result 2903\n  Bound result 6297\n  Bound result 5\n  Bound result 2089\n  Bound result 2000000000000000000\n  Bound result 1687303715884105727\n  Bound result 33\n  Bound result 9999999999999999998\n  Bound result 190928093539231863\n  Bound result 8743013010627084014\n  Bound result 868758559676677149\n  Bound result 5438\n  Bound result 2307425880333735409\n  Bound result 16603578570129\n  Bound result 118641396035517759\n  Bound result 92\n  Bound result 49152\n  Bound result 49990000000000014357\n  Bound result 49990000000000000227\n  Bound result 990000000000000000\n  Bound result 49990000000000000256\n  Bound result 6123645191730515705\n  Bound result 2000000000000000000\n  Bound result 49990000000000000243\n  Bound result 10000000000000000000\n  Bound result 769277811868007059\n  Bound result 49990000000907403071\n  Bound result 2000000000000000000\n  Bound result 49990000000000006825\n  Bound result 49990000000000000245\n  Bound result 8213995965066510337\n  Bound result 4747\n  Bound result 672\n  Bound result 1000000000000000000\n  Bound result 10000000000000000000\n  Bound result 250000000000000000\n  Bound result 498\n  Bound result 141722473306620683\n  Bound result 49152\n  Bound result 31409265303658670\n  Bound result 5891177212181312674\n  Bound result 543732608005480161\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.90s (1.90s CPU time)\n\nRan 1 test for test/WebsiteBuyerInvariant.t.sol:WebsiteBuyerInvariantTest\n[PASS]\nWebsiteBuyerInvariantTest invariants:\n[PASS] invariant_accountsHoldNothingApproveNothingAndNeverChange\n[PASS] invariant_everyWeiSentReachedThePool\n[PASS] invariant_hubIMDIsOnlyWhatWasDonated\n[PASS] invariant_hubNeverHoldsETH\n[PASS] invariant_imdConservedTreasuryGetsPricesBuyersGetSurplus\n[PASS] invariant_poolManagerSettledAndUnlocked\n[PASS] invariant_recordsMatchPaymentsAndSettleOnce\n WebsiteBuyerInvariantTest invariants (runs: 256, calls: 10240, reverts: 0)\n\n╭---------------------+---------------------+-------+---------+----------╮\n| Contract            | Selector            | Calls | Reverts | Discards |\n+========================================================================+\n| WebsiteBuyerHandler | buy                 | 1623  | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| WebsiteBuyerHandler | buyBadWords         | 822   | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| WebsiteBuyerHandler | buyPartialFill      | 774   | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| WebsiteBuyerHandler | deliverFailure      | 749   | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| WebsiteBuyerHandler | deliverResult       | 787   | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| WebsiteBuyerHandler | donateETH           | 804   | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| WebsiteBuyerHandler | donateIMD           | 781   | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| WebsiteBuyerHandler | migrateIntake       | 803   | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| WebsiteBuyerHandler | outsiderAdmin       | 778   | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| WebsiteBuyerHandler | spoofCallback       | 759   | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| WebsiteBuyerHandler | spoofUnknownRequest | 792   | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| WebsiteBuyerHandler | togglePause         | 768   | 0       | 0        |\n╰---------------------+---------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 6765758757581738751\n  Bound result 2243195347085417639\n  Bound result 2\n  Bound result 2719822761307274440\n  Bound result 2640741808591368035\n  Bound result 79080952715906405\n  Bound result 654\n  Bound result 396\n  Bound result 9500202590539447847\n  Bound result 2\n  Bound result 146222045207550367\n  Bound result 600000000000000000\n  Bound result 8\n  Bound result 5955\n  Bound result 1975633371156194827\n  Bound result 2065592148770782019\n  Bound result 2063383510684775195\n  Bound result 8613448174210464421\n  Bound result 3124842405\n  Bound result 32\n  Bound result 1001\n  Bound result 7153\n  Bound result 26\n  Bound result 3121\n  Bound result 12\n  Bound result 209\n  Bound result 12439\n  Bound result 9\n  Bound result 2220\n  Bound result 2\n  Bound result 7125\n  Bound result 6\n  Bound result 4564728518505578419\n  Bound result 200000\n  Bound result 14190\n  Bound result 4\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.50s (6.49s CPU time)\n\nRan 9 test suites in 6.51s (9.02s CPU time): 73 tests passed, 0 failed, 0 skipped (73 total tests)\n","passed":true},{"durationMs":110,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IntakeAccount.ask(address,bytes32,bytes,(address,bytes4),bytes4,address,uint256)\",\"IntakeAccount.execute(address,uint256,bytes)\",\"IntakeAccount.receive()\",\"WebsiteBuyer.acceptOwnership()\",\"WebsiteBuyer.buyWebsite(string)\",\"WebsiteBuyer.onIntakeFailure(bytes32,uint8,bytes32,uint16,uint16,bytes)\",\"WebsiteBuyer.onIntakeResult(bytes32,bytes32,string)\",\"WebsiteBuyer.renounceOwnership()\",\"WebsiteBuyer.setIntake(address)\",\"WebsiteBuyer.setPaused(bool)\",\"WebsiteBuyer.transferOwnership(address)\",\"WebsiteBuyer.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":211,\"docs/DEPENDENCIES.md\":33,\"docs/VALIDATION.md\":70,\"foundry.toml\":21,\"remappings.txt\":4,\"src/IntakeAccount.sol\":71,\"src/WebsiteBuyer.sol\":226,\"src/interfaces/IIntake.sol\":21,\"test/Deployment.t.sol\":51,\"test/IntakeAccount.t.sol\":142,\"test/RealPoolInvariant.t.sol\":225,\"test/UniswapIntegration.t.sol\":212,\"test/WebsiteBuyer.t.sol\":339,\"test/WebsiteBuyerBase.t.sol\":77,\"test/WebsiteBuyerEdges.t.sol\":169,\"test/WebsiteBuyerInvariant.t.sol\":523,\"test/WebsiteCallbacks.t.sol\":161,\"test/WebsiteWords.t.sol\":85,\"test/mocks/MockIMD.sol\":67,\"test/mocks/MockIntake.sol\":129,\"test/mocks/MockPoolManager.sol\":87},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"394e0213184d98a0cb2ce87e8ce61d02aefe8f0322ab8069ab784cb469ddbb68","verifiedTreeHash":"d80d2311a9bf5a9accae6b45f6b94b8940673974","verifierVersion":"0.1.0+c3fba9eb"},{"checks":[{"durationMs":4959,"exitCode":0,"name":"build","output":"Compiling 98 files with Solc 0.8.26\nSolc 0.8.26 finished in 4.87s\nCompiler run successful!\n","passed":true},{"durationMs":5331,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 8 tests for test/IntakeAccount.t.sol:IntakeAccountTest\n[PASS] test_accountRejectsZeroUser() (gas: 3739)\n[PASS] test_buyerCanUseSamePayerForContinuationOtherBuyerCannot() (gas: 3507721)\n[PASS] test_contractBuyerGetsSurplusWithoutAnETHReceiveFunction() (gas: 1810813)\n[PASS] test_existingBuyerFundsInAccountAreNotSpentByNewPurchase() (gas: 1732019)\n[PASS] test_onlyHubAsksAndOnlyBuyerExecutes() (gas: 1285803)\n[PASS] test_pauseDoesNotTakeAwayBuyerControlOfPayer() (gas: 1458753)\n[PASS] test_payableExecuteAndReturnDataAndRevertBubbling() (gas: 1275166)\n[PASS] test_userCannotReenterOwnAccountExecution() (gas: 1759367)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 33.22ms (8.50ms CPU time)\n\nRan 2 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] test_constructorRejectsZeroOwner() (gas: 4059)\n[PASS] test_factoryConstructorWorksWithNoExternalContractsAndExplicitOwner() (gas: 8461286)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 51.47ms (17.59ms CPU time)\n\nRan 6 tests for test/WebsiteWords.t.sol:WebsiteWordsTest\n[PASS] testFuzz_invalidByteAtAnyPositionIsRefused(uint16,uint16,uint8) (runs: 512, μ: 74714, ~: 47078)\nLogs:\n  Bound result 473\n\n[PASS] testFuzz_validWordsCannotInjectJSON(bytes) (runs: 512, μ: 47468, ~: 40191)\n[PASS] test_UTF8AllowedAndLimitCountsBytes() (gas: 1808316)\n[PASS] test_everyASCIIControlByteIncludingDELIsRefused() (gas: 2401100)\n[PASS] test_oneByteAndExactly1000BytesAreAccepted() (gas: 3455125)\n[PASS] test_rejectsEmptyQuoteBackslashNewlineAndExcessBytes() (gas: 372142)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 50.39ms (78.33ms CPU time)\n\nRan 15 tests for test/WebsiteBuyerEdges.t.sol:WebsiteBuyerEdgesTest\n[PASS] testFuzz_resultURIIsDeliveredStoredUpToCapAndAlwaysEmitted(uint8,bytes32) (runs: 256, μ: 1321359, ~: 1304925)\nLogs:\n  Bound result 1\n\n[PASS] test_badWordsRefusedBeforeReadingAnUnsoldPrice() (gas: 76030)\n[PASS] test_bodyNeverCarriesAnOwnerField() (gas: 54527)\n[PASS] test_failedDeliveryLeavesRequestPendingAndWriterCannotRetry() (gas: 1290824)\n[PASS] test_failureWithPanelFieldsAndLongSignatureIsRecordedWithinStipend() (gas: 1566332)\n[PASS] test_largestRepresentableETHAmountIsAccepted() (gas: 1296572)\n[PASS] test_laterNonzeroFailureStatusIsRecorded() (gas: 1264710)\n[PASS] test_oneWeiPurchaseWorksWhenThePoolPaysThePrice() (gas: 1261357)\n[PASS] test_ownerAndOtherBuyerCannotSpendFromABuyersAccount() (gas: 1777821)\n[PASS] test_pauseTransitionsAreStrictAndPreviewStillWorks() (gas: 186603)\n[PASS] test_planeFallbackURIIsDeliveredWithinTheStipend() (gas: 1385501)\n[PASS] test_prefundedAccountAddressStillOpensAndFundsStayWithBuyer() (gas: 1456596)\n[PASS] test_requestRecordsTheIntakeTakenNotTheCurrentOne() (gas: 2761063)\n[PASS] test_resultURIStorageCapBoundary() (gas: 2576959)\n[PASS] test_veryLongResultURIStillSettles() (gas: 1898057)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 53.56ms (67.14ms CPU time)\n\nRan 26 tests for test/WebsiteBuyer.t.sol:WebsiteBuyerTest\n[PASS] testFuzz_conservationAndLivePrice(uint96,uint96,uint96) (runs: 512, μ: 1397369, ~: 1398737)\nLogs:\n  Bound result 1518\n  Bound result 2866\n  Bound result 2502\n\n[PASS] testFuzz_shortOutputAlwaysReverts(uint128) (runs: 512, μ: 492934, ~: 492660)\nLogs:\n  Bound result 72093272\n\n[PASS] test_belowPriceRevertsEntireSwapEvenWithDonatedIMD() (gas: 481350)\n[PASS] test_buysExactBodyThroughOwnPayerAndReturnsEverySurplusToken() (gas: 1382727)\n[PASS] test_constructorAndExactPoolIdentity() (gas: 57097)\n[PASS] test_duplicateRequestCannotOverwriteAnotherBuyer() (gas: 2116447)\n[PASS] test_exactPriceNoSurplusTransferRequired() (gas: 1327505)\n[PASS] test_failedApprovalAndTreasuryTransferRollBack() (gas: 1903352)\n[PASS] test_failedRefundRollsBackRequestPaymentAndAccountDeployment() (gas: 1354226)\n[PASS] test_feeOnTransferRejected() (gas: 420159)\n[PASS] test_intakeRevertAndMissingPaymentRollBack() (gas: 2251148)\n[PASS] test_invalidIntakeAddressesRejected() (gas: 143008)\n[PASS] test_livePriceChangesWithoutAnETHPriceSetting() (gas: 1836370)\n[PASS] test_missingOrRepeatedUnlockCallbackReverts() (gas: 534918)\n[PASS] test_noDirectETHDeposits() (gas: 38923)\n[PASS] test_onlyOwnerCanPauseAndChangeIntake() (gas: 1315323)\n[PASS] test_ownershipHandoverRequiresAcceptance() (gas: 226249)\n[PASS] test_partialFillAndZeroOutputRevert() (gas: 615272)\n[PASS] test_repeatBuyerReusesAccountAndOtherBuyerHasDifferentPayer() (gas: 2989490)\n[PASS] test_shortTokenDeliveryDetected() (gas: 404907)\n[PASS] test_swapsAllETHWithCanonicalPoolAndNoHookData() (gas: 1202192)\n[PASS] test_tokenAndIntakeCannotReenterPurchase() (gas: 1572137)\n[PASS] test_tokensWithEmptyReturnDataWork() (gas: 1309086)\n[PASS] test_unlockOnlyFromManagerDuringPurchase() (gas: 1226931)\n[PASS] test_unpricedRevertsBeforeAnySwap() (gas: 117887)\n[PASS] test_zeroAndUnrepresentableETHRejected() (gas: 83228)\nSuite result: ok. 26 passed; 0 failed; 0 skipped; finished in 59.30ms (127.21ms CPU time)\n\nRan 10 tests for test/WebsiteRevision.t.sol:WebsiteRevisionTest\n[PASS] testFuzz_everyScalarValueIsAccepted(uint32) (runs: 512, μ: 12218, ~: 12222)\nLogs:\n  Bound result 1114072\n\n[PASS] test_expiredDeadlineReverts() (gas: 59628)\n[PASS] test_malformedUTF8IsRefusedBeforeAnySwap() (gas: 1431964)\n[PASS] test_minimumSurplusMetIsAccepted() (gas: 1311269)\n[PASS] test_minimumVariantRespectsPauseAndBadWords() (gas: 136927)\n[PASS] test_ownerMovesPurchasesToNewActionVersionKeepingAccounts() (gas: 2018408)\n[PASS] test_priceRaisedAfterQuoteIsCaughtByMinimum() (gas: 402803)\n[PASS] test_setActionOnlyOwnerAndNonZero() (gas: 78511)\n[PASS] test_surplusBelowMinimumRevertsBeforeAnyPayment() (gas: 394146)\n[PASS] test_wellFormedUTF8BoundariesAreAccepted() (gas: 108287)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 70.67ms (74.04ms CPU time)\n\nRan 8 tests for test/UniswapIntegration.t.sol:UniswapIntegrationTest\n[PASS] testFuzz_realPoolConservation(uint96) (runs: 512, μ: 1476400, ~: 1476453)\nLogs:\n  Bound result 9400000000000018404\n\n[PASS] test_realManagerStaleERC20SyncDoesNotBlockNativeSettlement() (gas: 1647006)\n[PASS] test_realPoolMultiplePurchasesDoNotAccumulateCustody() (gas: 3181574)\n[PASS] test_realPoolPartialFillAtLiquidityBoundaryReverts() (gas: 631363)\n[PASS] test_realPoolSandwichIsRefusedByBuyerMinimum() (gas: 3305240)\n[PASS] test_realPoolShortOutputRollsBackPoolPriceAndBalances() (gas: 539781)\n[PASS] test_realPoolSwapsPaysAndRefundsWithAllDeltasSettled() (gas: 1484413)\n[PASS] test_realPoolWithoutLiquidityCannotChargeBuyer() (gas: 243115)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 70.73ms (79.32ms CPU time)\n\nRan 13 tests for test/WebsiteCallbacks.t.sol:WebsiteCallbacksTest\n[PASS] testFuzz_anyNonzeroFailureStatusIsRecorded(uint8) (runs: 512, μ: 1271414, ~: 1271383)\nLogs:\n  Bound result 51\n\n[PASS] test_failureCannotBeOverwrittenByEitherCallback() (gas: 1298430)\n[PASS] test_failureRejectsStatusZero() (gas: 1194883)\n[PASS] test_intakeMigrationKeepsOldAndNewCallbacksSeparate() (gas: 3591801)\n[PASS] test_longUriStillSettlesWithHashAndIsEmittedNotStored() (gas: 2040715)\n[PASS] test_missingAndUndeliverableCallbacksDoNotLockFuturePurchases() (gas: 8461330)\n[PASS] test_mockOnlyWriterCanComplete() (gas: 1179313)\n[PASS] test_pausedPurchasesDoNotBlockCallbacks() (gas: 2470104)\n[PASS] test_refusalAndFailureStoredAndEmittedWithin200000Gas() (gas: 2091960)\n[PASS] test_resultCannotBeOverwrittenByEitherCallback() (gas: 1404158)\n[PASS] test_resultStoredAndEmittedWithin200000Gas() (gas: 1486009)\n[PASS] test_uriAtStorageLimitIsStored() (gas: 1445346)\n[PASS] test_wrongSenderAndUnknownRequestsRejectedOnBothCallbacks() (gas: 1279503)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 70.72ms (78.42ms CPU time)\n\nRan 1 test for test/RealPoolInvariant.t.sol:RealPoolInvariantTest\n[PASS]\nRealPoolInvariantTest invariants:\n[PASS] invariant_realPoolHubCustodyIsZeroAndManagerSettled\n[PASS] invariant_realPoolValueConserved\n RealPoolInvariantTest invariants (runs: 128, calls: 4096, reverts: 0)\n\n╭-----------------+------------+-------+---------+----------╮\n| Contract        | Selector   | Calls | Reverts | Discards |\n+===========================================================+\n| RealPoolHandler | buy        | 2730  | 0       | 0        |\n|-----------------+------------+-------+---------+----------|\n| RealPoolHandler | moveMarket | 1366  | 0       | 0        |\n╰-----------------+------------+-------+---------+----------╯\n\nLogs:\n  Bound result 16\n  Bound result 55296\n  Bound result 6123645191730515706\n  Bound result 387434\n  Bound result 49990000000000055297\n  Bound result 652\n  Bound result 17423\n  Bound result 49990000000000000196\n  Bound result 990000000000000000\n  Bound result 3\n  Bound result 10000000000000000000\n  Bound result 1203\n  Bound result 7125\n  Bound result 1001\n  Bound result 2530\n  Bound result 2000000000000000000\n  Bound result 32\n  Bound result 33\n  Bound result 9999999999999999998\n  Bound result 190928093539231863\n  Bound result 8743013010627084014\n  Bound result 868758559676677149\n  Bound result 5438\n  Bound result 2307425880333735409\n  Bound result 16603578570129\n  Bound result 118641396035517759\n  Bound result 223\n  Bound result 160\n  Bound result 49990000000000006143\n  Bound result 49990000002447209286\n  Bound result 600000000000000000\n  Bound result 49990000000000000244\n  Bound result 8971949816594555209\n  Bound result 2000000000000000000\n  Bound result 49990000000000000145\n  Bound result 242\n  Bound result 1055239860\n  Bound result 49990000000000008405\n  Bound result 49990000000000024577\n  Bound result 49990000000000008716\n  Bound result 10000000000000003\n  Bound result 61\n  Bound result 997000021555113576\n  Bound result 674\n  Bound result 255\n  Bound result 10000000000000000000\n  Bound result 9999\n  Bound result 498\n  Bound result 141722473306620683\n  Bound result 237\n  Bound result 1584007913129637742\n  Bound result 5891177212181312674\n  Bound result 543732608005480161\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.32s (1.32s CPU time)\n\nRan 1 test for test/WebsiteBuyerInvariant.t.sol:WebsiteBuyerInvariantTest\n[PASS]\nWebsiteBuyerInvariantTest invariants:\n[PASS] invariant_accountsHoldNothingApproveNothingAndNeverChange\n[PASS] invariant_everyWeiSentReachedThePool\n[PASS] invariant_hubIMDIsOnlyWhatWasDonated\n[PASS] invariant_hubNeverHoldsETH\n[PASS] invariant_imdConservedTreasuryGetsPricesBuyersGetSurplus\n[PASS] invariant_poolManagerSettledAndUnlocked\n[PASS] invariant_recordsMatchPaymentsAndSettleOnce\n WebsiteBuyerInvariantTest invariants (runs: 256, calls: 10240, reverts: 0)\n\n╭---------------------+---------------------+-------+---------+----------╮\n| Contract            | Selector            | Calls | Reverts | Discards |\n+========================================================================+\n| WebsiteBuyerHandler | buy                 | 1395  | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| WebsiteBuyerHandler | buyBadWords         | 719   | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| WebsiteBuyerHandler | buyPartialFill      | 746   | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| WebsiteBuyerHandler | buyWithMinimum      | 741   | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| WebsiteBuyerHandler | deliverFailure      | 712   | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| WebsiteBuyerHandler | deliverResult       | 707   | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| WebsiteBuyerHandler | donateETH           | 718   | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| WebsiteBuyerHandler | donateIMD           | 681   | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| WebsiteBuyerHandler | migrateIntake       | 789   | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| WebsiteBuyerHandler | outsiderAdmin       | 740   | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| WebsiteBuyerHandler | spoofCallback       | 802   | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| WebsiteBuyerHandler | spoofUnknownRequest | 789   | 0       | 0        |\n|---------------------+---------------------+-------+---------+----------|\n| WebsiteBuyerHandler | togglePause         | 701   | 0       | 0        |\n╰---------------------+---------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 654\n  Bound result 2261\n  Bound result 3000000000000000000\n  Bound result 9\n  Bound result 555\n  Bound result 8664138622560336283\n  Bound result 983965353415853542\n  Bound result 483965353415853540\n  Bound result 1\n  Bound result 18720\n  Bound result 4116\n  Bound result 8961\n  Bound result 9109\n  Bound result 1442128738109248869\n  Bound result 653049595\n  Bound result 4076725132\n  Bound result 4500000002447209288\n  Bound result 4000000002447209287\n  Bound result 2\n  Bound result 1687303715884105728\n  Bound result 65536\n  Bound result 244\n  Bound result 194\n  Bound result 4676\n  Bound result 20458\n  Bound result 1551303472419132591\n  Bound result 1051303472419132589\n  Bound result 2\n  Bound result 183\n  Bound result 745129218\n  Bound result 644\n  Bound result 191\n  Bound result 1239446556\n  Bound result 4500000000000055297\n  Bound result 4000000000000055295\n  Bound result 1\n  Bound result 7716\n  Bound result 2011\n  Bound result 8\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 5.23s (5.22s CPU time)\n\nRan 10 test suites in 5.23s (7.01s CPU time): 90 tests passed, 0 failed, 0 skipped (90 total tests)\n","passed":true},{"durationMs":50,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IntakeAccount.ask(address,bytes32,bytes,(address,bytes4),bytes4,address,uint256)\",\"IntakeAccount.execute(address,uint256,bytes)\",\"IntakeAccount.receive()\",\"WebsiteBuyer.acceptOwnership()\",\"WebsiteBuyer.buyWebsite(string)\",\"WebsiteBuyer.buyWebsiteWithMinimum(string,uint256,uint256)\",\"WebsiteBuyer.onIntakeFailure(bytes32,uint8,bytes32,uint16,uint16,bytes)\",\"WebsiteBuyer.onIntakeResult(bytes32,bytes32,string)\",\"WebsiteBuyer.renounceOwnership()\",\"WebsiteBuyer.setAction(bytes32)\",\"WebsiteBuyer.setIntake(address)\",\"WebsiteBuyer.setPaused(bool)\",\"WebsiteBuyer.transferOwnership(address)\",\"WebsiteBuyer.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":235,\"docs/DEPENDENCIES.md\":33,\"docs/VALIDATION.md\":78,\"foundry.toml\":21,\"remappings.txt\":4,\"src/IntakeAccount.sol\":71,\"src/WebsiteBuyer.sol\":325,\"src/interfaces/IIntake.sol\":21,\"test/Deployment.t.sol\":51,\"test/IntakeAccount.t.sol\":142,\"test/RealPoolInvariant.t.sol\":225,\"test/UniswapIntegration.t.sol\":273,\"test/WebsiteBuyer.t.sol\":339,\"test/WebsiteBuyerBase.t.sol\":77,\"test/WebsiteBuyerEdges.t.sol\":206,\"test/WebsiteBuyerInvariant.t.sol\":591,\"test/WebsiteCallbacks.t.sol\":200,\"test/WebsiteRevision.t.sol\":170,\"test/WebsiteWords.t.sol\":85,\"test/mocks/MockIMD.sol\":67,\"test/mocks/MockIntake.sol\":129,\"test/mocks/MockPoolManager.sol\":87},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"6f64eb6b7bdf8c5834eeaf62c969b415367605a2e9f98eacca89d1ddbd7c5db6","verifiedTreeHash":"72e2d4c1aaaac5634a2dd9ab56107d0611f1051e","verifierVersion":"0.1.0+c3fba9eb"},{"checks":[{"durationMs":3550,"exitCode":0,"name":"build","output":"Compiling 94 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.45s\nCompiler run successful!\n","passed":true},{"durationMs":176,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] test_constructorRejectsZeroOwner() (gas: 4059)\n[PASS] test_factoryConstructorWorksWithNoExternalContractsAndExplicitOwner() (gas: 7821008)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 39.70ms (14.92ms CPU time)\n\nRan 8 tests for test/IntakeAccount.t.sol:IntakeAccountTest\n[PASS] test_accountRejectsZeroUser() (gas: 3739)\n[PASS] test_buyerCanUseSamePayerForContinuationOtherBuyerCannot() (gas: 3502347)\n[PASS] test_contractBuyerGetsSurplusWithoutAnETHReceiveFunction() (gas: 1807970)\n[PASS] test_existingBuyerFundsInAccountAreNotSpentByNewPurchase() (gas: 1727433)\n[PASS] test_onlyHubAsksAndOnlyBuyerExecutes() (gas: 1283204)\n[PASS] test_pauseDoesNotTakeAwayBuyerControlOfPayer() (gas: 1456021)\n[PASS] test_payableExecuteAndReturnDataAndRevertBubbling() (gas: 1272567)\n[PASS] test_userCannotReenterOwnAccountExecution() (gas: 1756612)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 40.36ms (5.78ms CPU time)\n\nRan 10 tests for test/WebsiteCallbacks.t.sol:WebsiteCallbacksTest\n[PASS] testFuzz_failureRejectsOtherStatusCodes(uint8) (runs: 512, μ: 1193058, ~: 1193060)\n[PASS] test_failureCannotBeOverwrittenByEitherCallback() (gas: 1296053)\n[PASS] test_intakeMigrationKeepsOldAndNewCallbacksSeparate() (gas: 3587126)\n[PASS] test_missingAndUndeliverableCallbacksDoNotLockFuturePurchases() (gas: 4212754)\n[PASS] test_mockOnlyWriterCanComplete() (gas: 1176780)\n[PASS] test_pausedPurchasesDoNotBlockCallbacks() (gas: 2465040)\n[PASS] test_refusalAndFailureStoredAndEmittedWithin200000Gas() (gas: 2087333)\n[PASS] test_resultCannotBeOverwrittenByEitherCallback() (gas: 1401621)\n[PASS] test_resultStoredAndEmittedWithin200000Gas() (gas: 1483321)\n[PASS] test_wrongSenderAndUnknownRequestsRejectedOnBothCallbacks() (gas: 1276989)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 55.55ms (86.57ms CPU time)\n\nRan 26 tests for test/WebsiteBuyer.t.sol:WebsiteBuyerTest\n[PASS] testFuzz_conservationAndLivePrice(uint96,uint96,uint96) (runs: 512, μ: 1394862, ~: 1396280)\nLogs:\n  Bound result 1226\n  Bound result 23171\n  Bound result 20421\n\n[PASS] testFuzz_shortOutputAlwaysReverts(uint128) (runs: 512, μ: 490556, ~: 490272)\nLogs:\n  Bound result 2447209286\n\n[PASS] test_belowPriceRevertsEntireSwapEvenWithDonatedIMD() (gas: 479050)\n[PASS] test_buysExactBodyThroughOwnPayerAndReturnsEverySurplusToken() (gas: 1380062)\n[PASS] test_constructorAndExactPoolIdentity() (gas: 57026)\n[PASS] test_duplicateRequestCannotOverwriteAnotherBuyer() (gas: 2111290)\n[PASS] test_exactPriceNoSurplusTransferRequired() (gas: 1324888)\n[PASS] test_failedApprovalAndTreasuryTransferRollBack() (gas: 1898192)\n[PASS] test_failedRefundRollsBackRequestPaymentAndAccountDeployment() (gas: 1351633)\n[PASS] test_feeOnTransferRejected() (gas: 417870)\n[PASS] test_intakeRevertAndMissingPaymentRollBack() (gas: 2245900)\n[PASS] test_invalidIntakeAddressesRejected() (gas: 143007)\n[PASS] test_livePriceChangesWithoutAnETHPriceSetting() (gas: 1831784)\n[PASS] test_missingOrRepeatedUnlockCallbackReverts() (gas: 530304)\n[PASS] test_noDirectETHDeposits() (gas: 38923)\n[PASS] test_onlyOwnerCanPauseAndChangeIntake() (gas: 1312641)\n[PASS] test_ownershipHandoverRequiresAcceptance() (gas: 226289)\n[PASS] test_partialFillAndZeroOutputRevert() (gas: 610658)\n[PASS] test_repeatBuyerReusesAccountAndOtherBuyerHasDifferentPayer() (gas: 2982217)\n[PASS] test_shortTokenDeliveryDetected() (gas: 402618)\n[PASS] test_swapsAllETHWithCanonicalPoolAndNoHookData() (gas: 1199614)\n[PASS] test_tokenAndIntakeCannotReenterPurchase() (gas: 1569448)\n[PASS] test_tokensWithEmptyReturnDataWork() (gas: 1306487)\n[PASS] test_unlockOnlyFromManagerDuringPurchase() (gas: 1224618)\n[PASS] test_unpricedRevertsBeforeAnySwap() (gas: 115420)\n[PASS] test_zeroAndUnrepresentableETHRejected() (gas: 82952)\nSuite result: ok. 26 passed; 0 failed; 0 skipped; finished in 57.59ms (82.55ms CPU time)\n\nRan 7 tests for test/UniswapIntegration.t.sol:UniswapIntegrationTest\n[PASS] testFuzz_realPoolConservation(uint96) (runs: 512, μ: 1473674, ~: 1473730)\nLogs:\n  Bound result 9400000000000001355\n\n[PASS] test_realManagerStaleERC20SyncDoesNotBlockNativeSettlement() (gas: 1644257)\n[PASS] test_realPoolMultiplePurchasesDoNotAccumulateCustody() (gas: 3170593)\n[PASS] test_realPoolPartialFillAtLiquidityBoundaryReverts() (gas: 629161)\n[PASS] test_realPoolShortOutputRollsBackPoolPriceAndBalances() (gas: 537365)\n[PASS] test_realPoolSwapsPaysAndRefundsWithAllDeltasSettled() (gas: 1481744)\n[PASS] test_realPoolWithoutLiquidityCannotChargeBuyer() (gas: 240892)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 61.64ms (65.67ms CPU time)\n\nRan 6 tests for test/WebsiteWords.t.sol:WebsiteWordsTest\n[PASS] testFuzz_invalidByteAtAnyPositionIsRefused(uint16,uint16,uint8) (runs: 512, μ: 83610, ~: 49445)\nLogs:\n  Bound result 1000\n\n[PASS] testFuzz_validWordsCannotInjectJSON(bytes) (runs: 512, μ: 47059, ~: 39822)\n[PASS] test_UTF8AllowedAndLimitCountsBytes() (gas: 1728605)\n[PASS] test_everyASCIIControlByteIncludingDELIsRefused() (gas: 2394905)\n[PASS] test_oneByteAndExactly1000BytesAreAccepted() (gas: 3426489)\n[PASS] test_rejectsEmptyQuoteBackslashNewlineAndExcessBytes() (gas: 371736)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 61.64ms (124.63ms CPU time)\n\nRan 6 test suites in 62.56ms (316.48ms CPU time): 59 tests passed, 0 failed, 0 skipped (59 total tests)\n","passed":true},{"durationMs":56,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IntakeAccount.ask(address,bytes32,bytes,(address,bytes4),bytes4,address,uint256)\",\"IntakeAccount.execute(address,uint256,bytes)\",\"IntakeAccount.receive()\",\"WebsiteBuyer.acceptOwnership()\",\"WebsiteBuyer.buyWebsite(string)\",\"WebsiteBuyer.onIntakeFailure(bytes32,uint8,bytes32,uint16,uint16,bytes)\",\"WebsiteBuyer.onIntakeResult(bytes32,bytes32,string)\",\"WebsiteBuyer.renounceOwnership()\",\"WebsiteBuyer.setIntake(address)\",\"WebsiteBuyer.setPaused(bool)\",\"WebsiteBuyer.transferOwnership(address)\",\"WebsiteBuyer.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":211,\"docs/DEPENDENCIES.md\":33,\"docs/VALIDATION.md\":70,\"foundry.toml\":21,\"remappings.txt\":4,\"src/IntakeAccount.sol\":71,\"src/WebsiteBuyer.sol\":226,\"src/interfaces/IIntake.sol\":21,\"test/Deployment.t.sol\":51,\"test/IntakeAccount.t.sol\":142,\"test/UniswapIntegration.t.sol\":212,\"test/WebsiteBuyer.t.sol\":339,\"test/WebsiteBuyerBase.t.sol\":77,\"test/WebsiteCallbacks.t.sol\":161,\"test/WebsiteWords.t.sol\":85,\"test/mocks/MockIMD.sol\":67,\"test/mocks/MockIntake.sol\":129,\"test/mocks/MockPoolManager.sol\":87},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1532,"exitCode":0,"name":"slither","output":"[high/medium] reentrancy-balance at src/IntakeAccount.sol:31: Reentrancy in IntakeAccount.ask(IIntake,bytes32,bytes,IIntake.Callback,bytes4,address,uint256) (src/IntakeAccount.sol#31-49):\n[high/medium] reentrancy-balance at src/WebsiteBuyer.sol:97: Reentrancy in WebsiteBuyer.buyWebsite(string) (src/WebsiteBuyer.sol#97-136):\n[high/medium] reentrancy-balance at src/WebsiteBuyer.sol:97: Reentrancy in WebsiteBuyer.buyWebsite(string) (src/WebsiteBuyer.sol#97-136):\n[low/medium] missing-zero-check at src/IntakeAccount.sol:52: IntakeAccount.execute(address,uint256,bytes).target (src/IntakeAccount.sol#52) lacks a zero-check on :\n[low/medium] reentrancy-benign at src/WebsiteBuyer.sol:97: Reentrancy in WebsiteBuyer.buyWebsite(string) (src/WebsiteBuyer.sol#97-136):\n[low/medium] reentrancy-benign at src/WebsiteBuyer.sol:97: Reentrancy in WebsiteBuyer.buyWebsite(string) (src/WebsiteBuyer.sol#97-136):","passed":true},{"durationMs":440,"exitCode":0,"name":"aderyn","output":"[high] abi-encode-packed-hash-collision at src/WebsiteBuyer.sol:173: `abi.encodePacked()` Hash Collision\n[high] contract-locks-ether at src/WebsiteBuyer.sol:24: Contract locks Ether without a withdraw function\n[high] reentrancy-state-change at src/WebsiteBuyer.sol:102: Reentrancy: State change after external call (5 places)\n[low] centralization-risk at src/WebsiteBuyer.sol:24: Centralization Risk (3 places)\n[low] large-numeric-literal at src/WebsiteBuyer.sol:31: Large Numeric Literal\n[low] local-variable-shadowing at src/WebsiteBuyer.sol:47: Local Variable Shadows State Variable\n[low] non-reentrant-not-first at src/WebsiteBuyer.sol:212: `nonReentrant` is Not the First Modifier (2 places)\n[low] require-revert-in-loop at src/WebsiteBuyer.sol:168: Loop Contains `require`/`revert`\n[low] state-change-without-event at src/IntakeAccount.sol:31: State Change Without Event (3 places)\n[low] uninitialized-local-variable at src/WebsiteBuyer.sol:168: Uninitialized Local Variable","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"75dd260bf615ea48cfa37944b259d33eb430aba750a02660ccb8e091824dfc9f","verifiedTreeHash":"bac17e174a556889c2e898a9bd2e41dbb696ec84","verifierVersion":"0.1.0+c3fba9eb"},{"checks":[{"durationMs":4370,"exitCode":0,"name":"build","output":"Compiling 95 files with Solc 0.8.26\nSolc 0.8.26 finished in 4.25s\nCompiler run successful!\n","passed":true},{"durationMs":181,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 8 tests for test/IntakeAccount.t.sol:IntakeAccountTest\n[PASS] test_accountRejectsZeroUser() (gas: 3739)\n[PASS] test_buyerCanUseSamePayerForContinuationOtherBuyerCannot() (gas: 3507721)\n[PASS] test_contractBuyerGetsSurplusWithoutAnETHReceiveFunction() (gas: 1810813)\n[PASS] test_existingBuyerFundsInAccountAreNotSpentByNewPurchase() (gas: 1732019)\n[PASS] test_onlyHubAsksAndOnlyBuyerExecutes() (gas: 1285803)\n[PASS] test_pauseDoesNotTakeAwayBuyerControlOfPayer() (gas: 1458753)\n[PASS] test_payableExecuteAndReturnDataAndRevertBubbling() (gas: 1275166)\n[PASS] test_userCannotReenterOwnAccountExecution() (gas: 1759367)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 33.48ms (4.11ms CPU time)\n\nRan 2 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] test_constructorRejectsZeroOwner() (gas: 4059)\n[PASS] test_factoryConstructorWorksWithNoExternalContractsAndExplicitOwner() (gas: 8461286)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 36.61ms (36.45ms CPU time)\n\nRan 26 tests for test/WebsiteBuyer.t.sol:WebsiteBuyerTest\n[PASS] testFuzz_conservationAndLivePrice(uint96,uint96,uint96) (runs: 512, μ: 1397369, ~: 1398737)\nLogs:\n  Bound result 632299321811\n  Bound result 556191564087535131\n  Bound result 565857\n\n[PASS] testFuzz_shortOutputAlwaysReverts(uint128) (runs: 512, μ: 492931, ~: 492660)\nLogs:\n  Bound result 18\n\n[PASS] test_belowPriceRevertsEntireSwapEvenWithDonatedIMD() (gas: 481350)\n[PASS] test_buysExactBodyThroughOwnPayerAndReturnsEverySurplusToken() (gas: 1382727)\n[PASS] test_constructorAndExactPoolIdentity() (gas: 57097)\n[PASS] test_duplicateRequestCannotOverwriteAnotherBuyer() (gas: 2116447)\n[PASS] test_exactPriceNoSurplusTransferRequired() (gas: 1327505)\n[PASS] test_failedApprovalAndTreasuryTransferRollBack() (gas: 1903352)\n[PASS] test_failedRefundRollsBackRequestPaymentAndAccountDeployment() (gas: 1354226)\n[PASS] test_feeOnTransferRejected() (gas: 420159)\n[PASS] test_intakeRevertAndMissingPaymentRollBack() (gas: 2251148)\n[PASS] test_invalidIntakeAddressesRejected() (gas: 143008)\n[PASS] test_livePriceChangesWithoutAnETHPriceSetting() (gas: 1836370)\n[PASS] test_missingOrRepeatedUnlockCallbackReverts() (gas: 534918)\n[PASS] test_noDirectETHDeposits() (gas: 38923)\n[PASS] test_onlyOwnerCanPauseAndChangeIntake() (gas: 1315323)\n[PASS] test_ownershipHandoverRequiresAcceptance() (gas: 226249)\n[PASS] test_partialFillAndZeroOutputRevert() (gas: 615272)\n[PASS] test_repeatBuyerReusesAccountAndOtherBuyerHasDifferentPayer() (gas: 2989490)\n[PASS] test_shortTokenDeliveryDetected() (gas: 404907)\n[PASS] test_swapsAllETHWithCanonicalPoolAndNoHookData() (gas: 1202192)\n[PASS] test_tokenAndIntakeCannotReenterPurchase() (gas: 1572137)\n[PASS] test_tokensWithEmptyReturnDataWork() (gas: 1309086)\n[PASS] test_unlockOnlyFromManagerDuringPurchase() (gas: 1226931)\n[PASS] test_unpricedRevertsBeforeAnySwap() (gas: 117887)\n[PASS] test_zeroAndUnrepresentableETHRejected() (gas: 83228)\nSuite result: ok. 26 passed; 0 failed; 0 skipped; finished in 50.29ms (99.89ms CPU time)\n\nRan 10 tests for test/WebsiteRevision.t.sol:WebsiteRevisionTest\n[PASS] testFuzz_everyScalarValueIsAccepted(uint32) (runs: 512, μ: 12181, ~: 11828)\nLogs:\n  Bound result 1114072\n\n[PASS] test_expiredDeadlineReverts() (gas: 59628)\n[PASS] test_malformedUTF8IsRefusedBeforeAnySwap() (gas: 1431964)\n[PASS] test_minimumSurplusMetIsAccepted() (gas: 1311269)\n[PASS] test_minimumVariantRespectsPauseAndBadWords() (gas: 136927)\n[PASS] test_ownerMovesPurchasesToNewActionVersionKeepingAccounts() (gas: 2018408)\n[PASS] test_priceRaisedAfterQuoteIsCaughtByMinimum() (gas: 402803)\n[PASS] test_setActionOnlyOwnerAndNonZero() (gas: 78511)\n[PASS] test_surplusBelowMinimumRevertsBeforeAnyPayment() (gas: 394146)\n[PASS] test_wellFormedUTF8BoundariesAreAccepted() (gas: 108287)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 52.67ms (55.28ms CPU time)\n\nRan 13 tests for test/WebsiteCallbacks.t.sol:WebsiteCallbacksTest\n[PASS] testFuzz_anyNonzeroFailureStatusIsRecorded(uint8) (runs: 512, μ: 1271414, ~: 1271383)\nLogs:\n  Bound result 35\n\n[PASS] test_failureCannotBeOverwrittenByEitherCallback() (gas: 1298430)\n[PASS] test_failureRejectsStatusZero() (gas: 1194883)\n[PASS] test_intakeMigrationKeepsOldAndNewCallbacksSeparate() (gas: 3591801)\n[PASS] test_longUriStillSettlesWithHashAndIsEmittedNotStored() (gas: 2040715)\n[PASS] test_missingAndUndeliverableCallbacksDoNotLockFuturePurchases() (gas: 8461330)\n[PASS] test_mockOnlyWriterCanComplete() (gas: 1179313)\n[PASS] test_pausedPurchasesDoNotBlockCallbacks() (gas: 2470104)\n[PASS] test_refusalAndFailureStoredAndEmittedWithin200000Gas() (gas: 2091960)\n[PASS] test_resultCannotBeOverwrittenByEitherCallback() (gas: 1404158)\n[PASS] test_resultStoredAndEmittedWithin200000Gas() (gas: 1486009)\n[PASS] test_uriAtStorageLimitIsStored() (gas: 1445346)\n[PASS] test_wrongSenderAndUnknownRequestsRejectedOnBothCallbacks() (gas: 1279503)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 52.63ms (82.46ms CPU time)\n\nRan 6 tests for test/WebsiteWords.t.sol:WebsiteWordsTest\n[PASS] testFuzz_invalidByteAtAnyPositionIsRefused(uint16,uint16,uint8) (runs: 512, μ: 74663, ~: 47556)\nLogs:\n  Bound result 48\n\n[PASS] testFuzz_validWordsCannotInjectJSON(bytes) (runs: 512, μ: 47478, ~: 40191)\n[PASS] test_UTF8AllowedAndLimitCountsBytes() (gas: 1808316)\n[PASS] test_everyASCIIControlByteIncludingDELIsRefused() (gas: 2401100)\n[PASS] test_oneByteAndExactly1000BytesAreAccepted() (gas: 3455125)\n[PASS] test_rejectsEmptyQuoteBackslashNewlineAndExcessBytes() (gas: 372142)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 52.65ms (108.21ms CPU time)\n\nRan 8 tests for test/UniswapIntegration.t.sol:UniswapIntegrationTest\n[PASS] testFuzz_realPoolConservation(uint96) (runs: 512, μ: 1476400, ~: 1476453)\nLogs:\n  Bound result 9400000000000000267\n\n[PASS] test_realManagerStaleERC20SyncDoesNotBlockNativeSettlement() (gas: 1647006)\n[PASS] test_realPoolMultiplePurchasesDoNotAccumulateCustody() (gas: 3181574)\n[PASS] test_realPoolPartialFillAtLiquidityBoundaryReverts() (gas: 631363)\n[PASS] test_realPoolSandwichIsRefusedByBuyerMinimum() (gas: 3305240)\n[PASS] test_realPoolShortOutputRollsBackPoolPriceAndBalances() (gas: 539781)\n[PASS] test_realPoolSwapsPaysAndRefundsWithAllDeltasSettled() (gas: 1484413)\n[PASS] test_realPoolWithoutLiquidityCannotChargeBuyer() (gas: 243115)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 71.53ms (77.81ms CPU time)\n\nRan 7 test suites in 72.33ms (349.87ms CPU time): 73 tests passed, 0 failed, 0 skipped (73 total tests)\n","passed":true},{"durationMs":111,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IntakeAccount.ask(address,bytes32,bytes,(address,bytes4),bytes4,address,uint256)\",\"IntakeAccount.execute(address,uint256,bytes)\",\"IntakeAccount.receive()\",\"WebsiteBuyer.acceptOwnership()\",\"WebsiteBuyer.buyWebsite(string)\",\"WebsiteBuyer.buyWebsiteWithMinimum(string,uint256,uint256)\",\"WebsiteBuyer.onIntakeFailure(bytes32,uint8,bytes32,uint16,uint16,bytes)\",\"WebsiteBuyer.onIntakeResult(bytes32,bytes32,string)\",\"WebsiteBuyer.renounceOwnership()\",\"WebsiteBuyer.setAction(bytes32)\",\"WebsiteBuyer.setIntake(address)\",\"WebsiteBuyer.setPaused(bool)\",\"WebsiteBuyer.transferOwnership(address)\",\"WebsiteBuyer.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":235,\"docs/DEPENDENCIES.md\":33,\"docs/VALIDATION.md\":78,\"foundry.toml\":21,\"remappings.txt\":4,\"src/IntakeAccount.sol\":71,\"src/WebsiteBuyer.sol\":325,\"src/interfaces/IIntake.sol\":21,\"test/Deployment.t.sol\":51,\"test/IntakeAccount.t.sol\":142,\"test/UniswapIntegration.t.sol\":273,\"test/WebsiteBuyer.t.sol\":339,\"test/WebsiteBuyerBase.t.sol\":77,\"test/WebsiteCallbacks.t.sol\":200,\"test/WebsiteRevision.t.sol\":170,\"test/WebsiteWords.t.sol\":85,\"test/mocks/MockIMD.sol\":67,\"test/mocks/MockIntake.sol\":129,\"test/mocks/MockPoolManager.sol\":87},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1415,"exitCode":0,"name":"slither","output":"[high/medium] reentrancy-balance at src/IntakeAccount.sol:31: Reentrancy in IntakeAccount.ask(IIntake,bytes32,bytes,IIntake.Callback,bytes4,address,uint256) (src/IntakeAccount.sol#31-49):\n[high/medium] reentrancy-balance at src/WebsiteBuyer.sol:130: Reentrancy in WebsiteBuyer._buy(string,uint256) (src/WebsiteBuyer.sol#130-153):\n[high/medium] reentrancy-balance at src/WebsiteBuyer.sol:130: Reentrancy in WebsiteBuyer._buy(string,uint256) (src/WebsiteBuyer.sol#130-153):\n[medium/medium] uninitialized-local at src/WebsiteBuyer.sol:222: WebsiteBuyer.bodyOf(string).extra (src/WebsiteBuyer.sol#222) is a local variable never initialized\n[low/medium] missing-zero-check at src/IntakeAccount.sol:52: IntakeAccount.execute(address,uint256,bytes).target (src/IntakeAccount.sol#52) lacks a zero-check on :\n[low/medium] reentrancy-benign at src/WebsiteBuyer.sol:164: Reentrancy in WebsiteBuyer._ask(IntakeAccount,IIntake,bytes,uint256) (src/WebsiteBuyer.sol#164-180):\n[low/medium] reentrancy-benign at src/WebsiteBuyer.sol:130: Reentrancy in WebsiteBuyer._buy(string,uint256) (src/WebsiteBuyer.sol#130-153):\n[low/medium] timestamp at src/WebsiteBuyer.sol:119: WebsiteBuyer.buyWebsiteWithMinimum(string,uint256,uint256) (src/WebsiteBuyer.sol#119-128) uses timestamp for comparisons","passed":true},{"durationMs":402,"exitCode":0,"name":"aderyn","output":"[high] abi-encode-packed-hash-collision at src/WebsiteBuyer.sol:233: `abi.encodePacked()` Hash Collision\n[high] contract-locks-ether at src/WebsiteBuyer.sol:24: Contract locks Ether without a withdraw function\n[low] centralization-risk at src/WebsiteBuyer.sol:24: Centralization Risk (4 places)\n[low] large-numeric-literal at src/WebsiteBuyer.sol:31: Large Numeric Literal\n[low] literal-instead-of-constant at src/WebsiteBuyer.sol:217: Literal Instead of Constant (8 places)\n[low] local-variable-shadowing at src/WebsiteBuyer.sol:50: Local Variable Shadows State Variable\n[low] non-reentrant-not-first at src/WebsiteBuyer.sol:302: `nonReentrant` is Not the First Modifier (3 places)\n[low] require-revert-in-loop at src/WebsiteBuyer.sol:215: Loop Contains `require`/`revert` (2 places)\n[low] state-change-without-event at src/IntakeAccount.sol:31: State Change Without Event (3 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"8ae268ce432162d694fa7f332d89eece7746819fe9c2264c6a0510e7e062921c","verifiedTreeHash":"58c07751af7d6481b2ae96fd28a847ea167eb564","verifierVersion":"0.1.0+c3fba9eb"},{"checks":[{"durationMs":3248,"exitCode":0,"name":"build","output":"Compiling 94 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.15s\nCompiler run successful!\n","passed":true},{"durationMs":175,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] test_constructorRejectsZeroOwner() (gas: 4059)\n[PASS] test_factoryConstructorWorksWithNoExternalContractsAndExplicitOwner() (gas: 7821008)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 32.46ms (30.35ms CPU time)\n\nRan 10 tests for test/WebsiteCallbacks.t.sol:WebsiteCallbacksTest\n[PASS] testFuzz_failureRejectsOtherStatusCodes(uint8) (runs: 512, μ: 1193058, ~: 1193060)\n[PASS] test_failureCannotBeOverwrittenByEitherCallback() (gas: 1296053)\n[PASS] test_intakeMigrationKeepsOldAndNewCallbacksSeparate() (gas: 3587126)\n[PASS] test_missingAndUndeliverableCallbacksDoNotLockFuturePurchases() (gas: 4212754)\n[PASS] test_mockOnlyWriterCanComplete() (gas: 1176780)\n[PASS] test_pausedPurchasesDoNotBlockCallbacks() (gas: 2465040)\n[PASS] test_refusalAndFailureStoredAndEmittedWithin200000Gas() (gas: 2087333)\n[PASS] test_resultCannotBeOverwrittenByEitherCallback() (gas: 1401621)\n[PASS] test_resultStoredAndEmittedWithin200000Gas() (gas: 1483321)\n[PASS] test_wrongSenderAndUnknownRequestsRejectedOnBothCallbacks() (gas: 1276989)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 32.56ms (39.30ms CPU time)\n\nRan 8 tests for test/IntakeAccount.t.sol:IntakeAccountTest\n[PASS] test_accountRejectsZeroUser() (gas: 3739)\n[PASS] test_buyerCanUseSamePayerForContinuationOtherBuyerCannot() (gas: 3502347)\n[PASS] test_contractBuyerGetsSurplusWithoutAnETHReceiveFunction() (gas: 1807970)\n[PASS] test_existingBuyerFundsInAccountAreNotSpentByNewPurchase() (gas: 1727433)\n[PASS] test_onlyHubAsksAndOnlyBuyerExecutes() (gas: 1283204)\n[PASS] test_pauseDoesNotTakeAwayBuyerControlOfPayer() (gas: 1456021)\n[PASS] test_payableExecuteAndReturnDataAndRevertBubbling() (gas: 1272567)\n[PASS] test_userCannotReenterOwnAccountExecution() (gas: 1756612)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 37.93ms (5.54ms CPU time)\n\nRan 6 tests for test/WebsiteWords.t.sol:WebsiteWordsTest\n[PASS] testFuzz_invalidByteAtAnyPositionIsRefused(uint16,uint16,uint8) (runs: 512, μ: 83610, ~: 49445)\nLogs:\n  Bound result 733\n\n[PASS] testFuzz_validWordsCannotInjectJSON(bytes) (runs: 512, μ: 47059, ~: 39822)\n[PASS] test_UTF8AllowedAndLimitCountsBytes() (gas: 1728605)\n[PASS] test_everyASCIIControlByteIncludingDELIsRefused() (gas: 2394905)\n[PASS] test_oneByteAndExactly1000BytesAreAccepted() (gas: 3426489)\n[PASS] test_rejectsEmptyQuoteBackslashNewlineAndExcessBytes() (gas: 371736)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 41.95ms (82.32ms CPU time)\n\nRan 26 tests for test/WebsiteBuyer.t.sol:WebsiteBuyerTest\n[PASS] testFuzz_conservationAndLivePrice(uint96,uint96,uint96) (runs: 512, μ: 1394862, ~: 1396280)\nLogs:\n  Bound result 586\n  Bound result 4534\n  Bound result 800\n\n[PASS] testFuzz_shortOutputAlwaysReverts(uint128) (runs: 512, μ: 490556, ~: 490272)\nLogs:\n  Bound result 19784868232527798\n\n[PASS] test_belowPriceRevertsEntireSwapEvenWithDonatedIMD() (gas: 479050)\n[PASS] test_buysExactBodyThroughOwnPayerAndReturnsEverySurplusToken() (gas: 1380062)\n[PASS] test_constructorAndExactPoolIdentity() (gas: 57026)\n[PASS] test_duplicateRequestCannotOverwriteAnotherBuyer() (gas: 2111290)\n[PASS] test_exactPriceNoSurplusTransferRequired() (gas: 1324888)\n[PASS] test_failedApprovalAndTreasuryTransferRollBack() (gas: 1898192)\n[PASS] test_failedRefundRollsBackRequestPaymentAndAccountDeployment() (gas: 1351633)\n[PASS] test_feeOnTransferRejected() (gas: 417870)\n[PASS] test_intakeRevertAndMissingPaymentRollBack() (gas: 2245900)\n[PASS] test_invalidIntakeAddressesRejected() (gas: 143007)\n[PASS] test_livePriceChangesWithoutAnETHPriceSetting() (gas: 1831784)\n[PASS] test_missingOrRepeatedUnlockCallbackReverts() (gas: 530304)\n[PASS] test_noDirectETHDeposits() (gas: 38923)\n[PASS] test_onlyOwnerCanPauseAndChangeIntake() (gas: 1312641)\n[PASS] test_ownershipHandoverRequiresAcceptance() (gas: 226289)\n[PASS] test_partialFillAndZeroOutputRevert() (gas: 610658)\n[PASS] test_repeatBuyerReusesAccountAndOtherBuyerHasDifferentPayer() (gas: 2982217)\n[PASS] test_shortTokenDeliveryDetected() (gas: 402618)\n[PASS] test_swapsAllETHWithCanonicalPoolAndNoHookData() (gas: 1199614)\n[PASS] test_tokenAndIntakeCannotReenterPurchase() (gas: 1569448)\n[PASS] test_tokensWithEmptyReturnDataWork() (gas: 1306487)\n[PASS] test_unlockOnlyFromManagerDuringPurchase() (gas: 1224618)\n[PASS] test_unpricedRevertsBeforeAnySwap() (gas: 115420)\n[PASS] test_zeroAndUnrepresentableETHRejected() (gas: 82952)\nSuite result: ok. 26 passed; 0 failed; 0 skipped; finished in 51.44ms (108.11ms CPU time)\n\nRan 7 tests for test/UniswapIntegration.t.sol:UniswapIntegrationTest\n[PASS] testFuzz_realPoolConservation(uint96) (runs: 512, μ: 1473674, ~: 1473730)\nLogs:\n  Bound result 8192538380565833405\n\n[PASS] test_realManagerStaleERC20SyncDoesNotBlockNativeSettlement() (gas: 1644257)\n[PASS] test_realPoolMultiplePurchasesDoNotAccumulateCustody() (gas: 3170593)\n[PASS] test_realPoolPartialFillAtLiquidityBoundaryReverts() (gas: 629161)\n[PASS] test_realPoolShortOutputRollsBackPoolPriceAndBalances() (gas: 537365)\n[PASS] test_realPoolSwapsPaysAndRefundsWithAllDeltasSettled() (gas: 1481744)\n[PASS] test_realPoolWithoutLiquidityCannotChargeBuyer() (gas: 240892)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 51.91ms (56.07ms CPU time)\n\nRan 6 test suites in 52.88ms (248.26ms CPU time): 59 tests passed, 0 failed, 0 skipped (59 total tests)\n","passed":true},{"durationMs":62,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IntakeAccount.ask(address,bytes32,bytes,(address,bytes4),bytes4,address,uint256)\",\"IntakeAccount.execute(address,uint256,bytes)\",\"IntakeAccount.receive()\",\"WebsiteBuyer.acceptOwnership()\",\"WebsiteBuyer.buyWebsite(string)\",\"WebsiteBuyer.onIntakeFailure(bytes32,uint8,bytes32,uint16,uint16,bytes)\",\"WebsiteBuyer.onIntakeResult(bytes32,bytes32,string)\",\"WebsiteBuyer.renounceOwnership()\",\"WebsiteBuyer.setIntake(address)\",\"WebsiteBuyer.setPaused(bool)\",\"WebsiteBuyer.transferOwnership(address)\",\"WebsiteBuyer.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":211,\"docs/DEPENDENCIES.md\":33,\"docs/VALIDATION.md\":70,\"foundry.toml\":21,\"launch.json\":12,\"remappings.txt\":4,\"src/IntakeAccount.sol\":71,\"src/WebsiteBuyer.sol\":226,\"src/interfaces/IIntake.sol\":21,\"test/Deployment.t.sol\":51,\"test/IntakeAccount.t.sol\":142,\"test/UniswapIntegration.t.sol\":212,\"test/WebsiteBuyer.t.sol\":339,\"test/WebsiteBuyerBase.t.sol\":77,\"test/WebsiteCallbacks.t.sol\":161,\"test/WebsiteWords.t.sol\":85,\"test/mocks/MockIMD.sol\":67,\"test/mocks/MockIntake.sol\":129,\"test/mocks/MockPoolManager.sol\":87},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"b3b8e0b13a4b399399904bde2207e65c129a5e110dcc70e651042683a8296d91","verifiedTreeHash":"9e3d1e5fb06b1083cf4ac06ba33627b4a884728a","verifierVersion":"0.1.0+c3fba9eb"}]}