{"assessments":[],"deployments":[{"attestationHash":"003d895a22f7a7ff98f37465fd08a934c25835aad43f6fd82dc3964190405fbd","chainId":1,"contracts":[{"address":"0x9325a3bfbdfae6e8d71b6850f7348bbc979fec5f","blockNumber":26186454,"name":"ArtifactsEscrow","role":"other","txHash":"0x079472cb919e8783f21dae2debc7c9c642bb0024997777de1cac7f10aef64f6e"},{"address":"0xdb36a049f351f4afdcdbdcf3932b444fe1d198c9","blockNumber":26186454,"name":"ArtHouse","role":"other","txHash":"0x079472cb919e8783f21dae2debc7c9c642bb0024997777de1cac7f10aef64f6e"},{"address":"0x7475f29a26a7b92c35eb6dd71341464c3de0f8d4","blockNumber":26186454,"name":"ArtStorage","role":"other","txHash":"0x079472cb919e8783f21dae2debc7c9c642bb0024997777de1cac7f10aef64f6e"},{"address":"0x1fffb44159b5b563dc241412f48ae92f57e6570d","blockNumber":26186454,"name":"ArtifactsAuction","role":"other","txHash":"0x079472cb919e8783f21dae2debc7c9c642bb0024997777de1cac7f10aef64f6e"},{"address":"0x5051f8b21c55182ad4cf5fbf8236f5f29d74727f","blockNumber":26186454,"name":"ArtifactsFactory","role":"other","txHash":"0x079472cb919e8783f21dae2debc7c9c642bb0024997777de1cac7f10aef64f6e"},{"address":"0x80ff349d4cd0db77dade6e1197d1d60efbea7291","blockNumber":26186454,"name":"ArtifactsSale","role":"other","txHash":"0x079472cb919e8783f21dae2debc7c9c642bb0024997777de1cac7f10aef64f6e"},{"address":"0xc2517f76766b955260bf94c50da1b3366c9ae57b","blockNumber":26186454,"name":"ArtifactsPiece","role":"other","txHash":"0x079472cb919e8783f21dae2debc7c9c642bb0024997777de1cac7f10aef64f6e"}],"id":"059dec50-12cb-443f-b726-2c35b58c8538","manifestHash":"e6cd47da1a25e77a02fc4b76fd4e80673aceb8ba54bea2091fc9ef1a5cc683c9","status":"live"}],"fuzz":[],"identity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"interpretation":"Records acceptance and evidence. Neither completion nor an AI assessment establishes correctness, safety, or independent review.","jobId":"dee6f3a6-d148-4289-99e9-10995635c9df","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"cdad8f2195cd77a24db95b1e3e07d7b9d3fff8525a914d8e9b254a927a1ee465","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":"29612bc24c4459d5375ce3dd15af508e19bfe494d2c5629c953c4bf8386881dc","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":"21d7b68ce0ab4bc40684152f68e78364b58151ce266a9c65881064e9654fadea","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":"482945d42e2a375d4f3e4a6c66d15fea70b313f1b0eff6dd2f5700b85eda12e3","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":"3bd9c0e08aa3498aed84378e95c548c102adfd99fc841e1604f710e9824b90e0","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":"154e224836e515bc818a7dcbf309fb61c208db1db5878ebbd38f607d4f5c6df2","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"6b8d2c2ebd3301cff2be8edb1e1cc6ba65c858c02fe7247be81722b969842265","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"6b8d2c2ebd3301cff2be8edb1e1cc6ba65c858c02fe7247be81722b969842265","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"ecc263691e21d092548b339e9687fb1966bda78a1d49637db5241bac17c30a09","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"2ba7209f7417c971f0162b3b3dcbe8c3eb2f88a7a9af0a8e4b669081effabd13","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":"Build the contracts for Proof House, a curator-run house for fully on-chain art that people and AI agents buy, with AI agents as brokers, and its first collection ARTIFACTS: 256 encrypted generative pieces, 255 editions and THE SOURCE. Deploy the contracts on Sepolia.\n\nToken facts. No fungible token is created and there is no pool; IMD is an existing ERC-20 that buyers may pay with. The only tokens are ERC-721 NFTs, minted only when a piece is bought. Token name: \"ARTIFACTS\" (each piece contract is named \"ARTIFACTS #NNN\"; THE SOURCE is \"ARTIFACTS · THE SOURCE\"). Token symbol: \"ARTIFACTS\". Total supply: 256 (exactly 1 per piece contract), fixed forever. Later Proof House collections take their token name and symbol from the curator at creation, and their supply is the number of registered pieces. Reference material, including the art rules and a renderer test, is at https://github.com/mylesdaughtry/proof-house (artifacts/RULES.md, artifacts/PROTOCOL.md, artifacts/reference/).\n\nDeployment: constructors take only static values, $owner and earlier contracts, and nothing is called after deployment. Deploy ArtStorage($owner), then ArtHouse($owner, $contract:ArtStorage, the existing IMD ERC-20 at 0x44a1cd38474fb1748400e7deb5f8d786cce3f89a), then ArtifactsPiece() as a locked EIP-1167 implementation, then ArtifactsFactory($owner, $contract:ArtHouse, $contract:ArtifactsPiece). Split any contract that would exceed the size limit, up to 8 in total. The owner is the curator: two-step ownership, no renounceOwnership, ETH sent by call, no upgradeability, no mint that bypasses payment.\n\nArtHouse is the house ledger. The curator approves and revokes artists (display name, and a payout address the artist can change) and brokers. Every sale is split per collection in basis points (default broker 15%, house 15%, artist 70%; the house takes any rounding). Every piece, ARTIFACTS and later collections alike, pays an ERC-2981 royalty of 7.5% (750 basis points) to its artist's payout address. A broker earns only if approved and not the buyer; otherwise its share goes to the house. Balances are kept per currency (ETH and IMD) and each party withdraws its own; the house balance is paid to the current owner. The curator can approve sale modules, such as ArtifactsFactory, that record ETH sales through this ledger. Mints, purchases and withdrawals are nonReentrant, with state and credits written before any token or ETH moves.\n\nArtHouse also runs plain SVG collections for later artists. ArtStorage keeps pieces in SSTORE2 chunks and accepts writes only from ArtHouse after the owner's one-time setHouse. registerPiece(collection, pieceId, contentHash, title, description) needs pieceId to be the next number. uploadChunk(collection, pieceId, index, data), by the curator or the collection's artist, needs index to be the next chunk. Until a piece is finalized the curator can clear it; finalizePiece(collection, pieceId) checks keccak256 of the stored bytes against contentHash; seal(collection) needs every piece finalized; pieces over 100 KB are rejected. Each collection is an ERC-721 with ERC-2981, an EIP-1167 clone of an ArtCollection implementation that ArtHouse's constructor deploys, with an on-chain base64 JSON tokenURI. mint(collection, pieceId, broker) takes exactly the ETH price; mintWithIMD(collection, pieceId, broker, price) reverts unless price equals the current IMD price, then pulls exactly that. The curator sets prices and opens or closes sales. A submission registry takes submit(contentHash, previewURI) for the curator to accept or reject.\n\nARTIFACTS:\n- Before the sale, the curator uploads to ArtifactsFactory, and then locks forever, every piece's public traits (title, palette, colour count, direction), its ciphertext (the AES-256-GCM encrypted seed, stored with SSTORE2) and keccak256 of its content key.\n- Editions sell at one ETH price that the curator sets. The edition sale starts closed; the curator sets the price (above zero) and opens the sale, and can change the price only while the sale is closed. Purchases pay exactly that price through the ArtHouse ledger, with brokers. Each purchase requests Chainlink VRF v2.5 (coordinator, key hash and subscription set once by the owner after deployment), and the callback assigns a random edition not yet assigned. No edition is assigned twice or skipped.\n- Claim: the buyer registers an X25519 viewing public key, the curator posts the edition's content key wrapped to it, and the buyer's claim deploys the piece's own contract: an EIP-1167 clone of ArtifactsPiece, a one-token ERC-721 with ERC-2981 holding its ciphertext pointer, traits, commitment and the current owner's wrapped key.\n- Every edition's tokenURI is fully on-chain JSON whose image is a seal: the framed window from RULES.md R8 with the piece's public traits and no dots.\n- Resale goes through the factory's escrow. The seller lists; the buyer pays and registers a viewing key; the seller posts the re-wrapped key; payment releases on the buyer's confirmation or after 72 hours; the buyer may cancel until the key is posted. Plain ERC-721 transfers stay allowed, and the previous owner may then post a re-wrapped key for the new owner.\n- THE SOURCE is piece 256, with its own contract, sold by a single-lot ETH auction: the curator sets the reserve and end time; each bid must beat the current high bid by at least 5%; a bid in the final 10 minutes moves the end to 10 minutes after that bid; outbid bids are refunded by pull; proce","parentJobId":null,"planHash":"91cdc995c253fb09cd74c0b95a888ac08325098bfc491017450e03f2b39fe37a","previousHash":"e94a1ad8f1e8354223060978863180ea983e8bd35c6252ae2366c695af20d841","projectId":"dee6f3a6-d148-4289-99e9-10995635c9df","publication":{"commit":"5b0d36486994b24de88438d6def7d120c27efd58","deliveredAt":"2026-10-13T22:03:58.079Z","repoUrl":"https://github.com/identity-md-launches/launch-1631-build-contracts-proof-house"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[],"site":null,"snapshotHash":"764fc6747cc5383f81cfd51d1756dc620de9b89a71808f5cee91774e79d37c64","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":"12c1397fd247763692cb284e8a87533967af9ee47564161c5e425cd7aa75eef6","device":"78d8eb9b16352a18","findings":[],"hash":"154e224836e515bc818a7dcbf309fb61c208db1db5878ebbd38f607d4f5c6df2","nodeId":"8d837aeb-17db-4b7e-bf3e-cdbcf1cf3470","outcome":"completed","summary":"All checks are green. Recap of this revision:\n\n**Outcome.** The reviewer's proof for the quadratic read now passes, every one of the 14 findings reproduced and was fixed, and the suite grew from 46 to 62 tests. Build, tests with the pinned fuzz seed, and the format check all pass. Answers to each finding are in `.imd-responses.json`.\n\n**The two blocking findings**\n\n- **Quadratic storage read.** ArtStorage now allocates the piece once and copies each SSTORE2 chunk at its offset. A 100 KB piece in 100 chunks finalizes well inside 30M gas and renders inside a 50M gas call.\n- **Unrepairable content hash.** The curator gained two repairs for unfinalized pieces in unsealed collections: rewrite the hash and text in place, or drop the last registered piece so the count shrinks and the seal is unblocked.\n\n**Advisory findings, all fixed**\n\n- Revoked artists lose write access to their collections until re-approved.\n- Cancelling a resale restores the seller's wrapped key, which the escrow saves at purchase.\n- A previous owner can post a key only while the new owner has none, so it can never overwrite one.\n- The edition sale and the auction refuse to open or configure unless the factory names them.\n- Revoking the escrow or auction module no longer strands anyone: the piece still moves, and proceeds are held for pull or forwarded later.\n- Re-approving an approved artist keeps the payout the artist chose.\n- SSTORE2 rejects payloads above one contract's size instead of truncating them, with a clear chunk-size error upstream.\n- Escrow listings are bound to the piece's transfer count, so any transfer kills a listing.\n- Plain collection titles and descriptions are JSON-escaped in the token metadata.\n- The auction increment rounds up, so a bid never merely equals the high bid.\n- Module revocation keeps the collection on record, so ERC-2981 royalty lookups keep resolving.\n- Splits of plain collections change only while their sale is closed. The remaining curator powers over the ARTIFACTS split, the VRF coordinator choice, and key posting are documented as trust assumptions.\n\n**Not run here.** The two protected check files in the read list need a helper source and environment variables that only the verifier provides, so they do not compile on this machine. Constructors, opcodes and runtime sizes are unchanged in kind, and every contract stays under 13 KB.\n\nThe README documents the new repair functions, the chunk-size cap, the escrow and auction fallbacks, and the added trust assumptions. Nothing was committed, and no launch manifest or blocked file was written.","treeHash":"4152af20d9997a7e4f421edbc90f3ad30a657dab","usage":{"cachedInputTokens":2109398,"inputTokens":546,"model":"claude-fable-5-1","outputTokens":51243,"runtime":"claude","turns":67,"wallClockMs":1396466}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"4dd67dae195771b6","findings":[{"citation":"resolved","description":"Reported by write_foundry_tests; reproduced in test/scratch/Settle.t.sol::test_payoutAfterRevokeReapprove. The fix to the earlier finding keeps the artist's payout only while the artist is currently approved. After revokeArtist the flag is false, so approveArtist(artist, name, P3) writes P3 over the address the artist set with setPayout. Every later artist share credited by _split and every ERC-2981 royaltyInfo answer (ArtCollection.royaltyInfo and ArtifactsPiece.royaltyInfo resolve through royaltyReceiver) then names P3 until the artist calls setPayout again. The revocation and re-approval are visible events and the artist can correct the payout immediately once re-approved, so this is a curator power within the documented trust model rather than a bypass; README documents the keep-on-rename rule but not this reset. Fix, if wanted: keep a.payout whenever it is non-zero (the artist has been approved at least once), or document the reset. No fund already credited moves.","line":141,"path":"src/ArtHouse.sol","reproduction":"State: approveArtist(ARTIST,'Myles',ARTIST_PAYOUT); createModuleCollection(ARTIST) -> collection 1. ARTIST setPayout(0xBEEF). OWNER approveArtist(ARTIST,'renamed',OWNER) -> artist(ARTIST).payout still 0xBEEF (earlier fix holds). OWNER revokeArtist(ARTIST); OWNER approveArtist(ARTIST,'renamed',OWNER). Expected per brief ('a payout address the artist can change'): payout stays 0xBEEF. Actual: artist(ARTIST).payout == OWNER and royaltyReceiver(1) == OWNER.","severity":"info","snippet":"        if (a.approved) payout = a.payout;","title":"Residual of the payout finding: revoke-then-reapprove still lets the curator replace the payout the artist chose"}],"hash":"21d7b68ce0ab4bc40684152f68e78364b58151ce266a9c65881064e9654fadea","nodeId":"c15651d1-db5f-4f71-894d-6d38ac6748fd","outcome":"completed","summary":"The review is complete and `.imd-findings.json` holds the result.\n\n**Outcome: every finding from the previous round is fixed.** Both medium findings and all twelve advisory findings were re-run against the revised code and hold up.\n\n- **Quadratic read** (medium): both attached proof files now pass. A 100 KB piece in 100 one-kilobyte chunks finalizes and renders within the stated gas limits.\n- **Unrepairable contentHash** (medium): the new curator-only `redescribePiece` and `dropLastPiece` paths repair or remove an unfinalized piece, after which seal and openSale succeed.\n- **Advisory items**: revoked artists lose write access, escrow cancel restores the seller's key, the previous owner cannot overwrite a posted key, sale and auction refuse to open unless the factory names them, module revocation holds proceeds for pull instead of stranding the buyer or winner, approved artists keep their payout, SSTORE2 rejects oversize chunks, listings die on any transfer, tokenURI escapes JSON, the auction increment rounds up, royalty lookups survive factory revocation, and setSplit is closed-only for SVG collections.\n\n**Verification done.** The full project suite passes with 105 tests, all eight runtime sizes are under the EIP-170 limit, and the manifest has valid keys, seven uniquely named contracts in dependency order, and one argument per constructor parameter. I wrote an eleven-test scratch file reproducing each earlier scenario on the fixed code, then deleted it, so the tree is unchanged.\n\n**One residual, reported as info.** The payout fix only protects a currently approved artist. Revoking and re-approving still lets the curator write a new payout over the one the artist chose, redirecting future artist shares and ERC-2981 royalties until the artist calls setPayout again. This is a visible curator power inside the documented trust model, not a bypass, so it does not block the launch. The suggested fix is to keep the stored payout whenever it is non-zero.\n\nThe coverage record answers all 90 entry points, with every row marked holds except approveArtist, which points to the info finding.","treeHash":null,"usage":{"cachedInputTokens":1467948,"inputTokens":356,"model":"claude-fable-5-1","outputTokens":23308,"runtime":"claude","turns":24,"wallClockMs":686261}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"1a7606be97f168d4","findings":[{"citation":"resolved","description":"Reported by audit_math; reproduced with its proof. _read allocates a fresh buffer holding the whole prefix on every iteration, so memory (and memory-expansion gas) grows with chunks^2. uploadChunk bounds only the total (MAX_PIECE_BYTES = 102400) and accepts any chunk of 1 byte or more, so the curator or the collection's artist chooses the chunk count. _read is used by finalizePiece (a transaction), pieceBytes and through it ArtCollection.tokenURI (an eth_call). A 100 KB piece uploaded as 100 x 1 KB chunks needs about 55M gas to finalize (out of gas at 30M) and more than 50M gas for tokenURI (over geth's default RPC gas cap). finalizePiece is irreversible (clearPiece reverts once finalized), so a piece finalized on a high gas limit chain and then sold has a tokenURI standard RPC endpoints cannot serve; one that cannot be finalized blocks seal until the curator clears it and re-uploads, wasting roughly 20M gas of SSTORE2 writes. Fix: allocate new bytes(p.size) once and extcodecopy each chunk at its offset.","line":163,"path":"src/ArtStorage.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {ArtStorage} from \"src/ArtStorage.sol\";\nimport {ArtHouse} from \"src/ArtHouse.sol\";\n\ncontract ScratchIMD is ERC20 {\n    constructor() ERC20(\"IMD\", \"IMD\") {}\n}\n\n/// A 100 KB piece (the allowed maximum) uploaded as 100 chunks of 1 KB must be finalizable within a 30M-gas\n/// transaction and its tokenURI readable within a 50M-gas eth_call (geth's default RPC gas cap).\n/// ArtStorage._read re-concatenates the whole prefix for every chunk, so memory and gas grow with chunks^2.\ncontract ReadQuadraticTest is Test {\n    address owner = address(0xA11CE);\n    address artist = address(0xA27);\n    address buyer = address(0xB0B);\n    ArtStorage st;\n    ArtHouse house;\n\n    function setUp() public {\n        st = new ArtStorage(owner);\n        house = new ArtHouse(owner, address(st), address(new ScratchIMD()));\n        vm.startPrank(owner);\n        st.setHouse(address(house));\n        house.approveArtist(artist, \"artist\", artist);\n        house.createCollection(artist, \"Coll\", \"COLL\");\n        vm.stopPrank();\n    }\n\n    function test_hundredKBPieceIn1KBChunksIsFinalizableAndRenderable() public {\n        uint256 chunkSize = 1024;\n        uint256 n = 100; // 100 * 1024 = 102400 = MAX_PIECE_BYTES\n        bytes memory chunk = new bytes(chunkSize);\n        for (uint256 i; i < chunkSize; ++i) chunk[i] = bytes1(uint8(65 + i % 26));\n        bytes memory all;\n        for (uint256 i; i < n; ++i) all = bytes.concat(all, chunk);\n        assertEq(all.length, st.MAX_PIECE_BYTES());\n\n        vm.startPrank(artist);\n        house.registerPiece(1, 1, keccak256(all), \"t\", \"d\");\n        for (uint256 i; i < n; ++i) house.uploadChunk(1, 1, i, chunk);\n        (bool finalized,) = address(house).call{gas: 30_000_000}(abi.encodeCall(ArtHouse.finalizePiece, (1, 1)));\n        vm.stopPrank();\n        assertTrue(finalized, \"finalizePiece of a 100 KB piece runs out of 30M gas\");\n\n        vm.startPrank(owner);\n        house.seal(1);\n        house.setPrices(1, 1 ether, 0);\n        house.openSale(1);\n        vm.stopPrank();\n        vm.deal(buyer, 1 ether);\n        vm.prank(buyer);\n        house.mint{value: 1 ether}(1, 1, address(0));\n\n        address token = house.collection(1).token;\n        (bool rendered,) = token.staticcall{gas: 50_000_000}(abi.encodeWithSignature(\"tokenURI(uint256)\", 1));\n        assertTrue(rendered, \"tokenURI needs more than a 50M-gas eth_call\");\n    }\n}","reproduction":"Owner: setHouse(house); approveArtist(A,'a',A); createCollection(A,'N','S') -> collection 1. A: registerPiece(1,1,keccak256(X),'t','d') with X of 102400 bytes; uploadChunk(1,1,i,X[1024*i:1024*(i+1)]) for i=0..99 (all succeed). A: finalizePiece(1,1) with a 30M gas limit. Expected: a piece inside the 100 KB cap finalizes at a cost independent of chunking. Actual: out of gas (needs ~55M); after a finalize with unlimited gas, seal, open and mint, ArtCollection.tokenURI(1) needs more than 50M gas. The attached test fails on this tree ('finalizePiece of a 100 KB piece runs out of 30M gas', gas: 115,141,408) and passes with a linear _read.","severity":"medium","snippet":"            data = bytes.concat(data, SSTORE2.read(p.chunks[i]));","title":"ArtStorage._read re-concatenates the whole prefix per chunk, so a 100 KB piece in small chunks cannot be finalized within a block or rendered by tokenURI"},{"citation":"resolved","description":"Reported by audit_flow and audit_permissions (merged); reproduced. registerPiece writes contentHash, title and description once and only accepts pieceId == pieceCount + 1, so a slot cannot be re-registered. clearPiece, the only repair tool the brief names, resets chunks and size but leaves contentHash. finalizePiece requires keccak256(stored bytes) == contentHash, seal requires every piece finalized, and ArtHouse.openSale requires seal, so a single registerPiece with a hash that has no known preimage (a typo, bytes32(0), the hash of a draft) leaves the collection unsealable and unsellable forever. The ArtCollection clone, its name and symbol are wasted and the only recourse is a new collection. Because registerPiece is also open to the collection's artist (even after revocation, see the next finding), an artist can impose this on the curator at will. Fix: let clearPiece (curator-only, unfinalized piece, unsealed collection) also take a new contentHash/title/description, or let the curator drop the last unfinalized piece so pieceCount shrinks.","line":103,"path":"src/ArtStorage.sol","reproduction":"State: OWNER createCollection(ARTIST,'Plain','PLN') -> col. OWNER: registerPiece(col,1,keccak256('typo'),'T','D'); uploadChunk(col,1,0,'<svg/>'); finalizePiece(col,1) -> reverts ContentHashMismatch; clearPiece(col,1) -> ok, pieceInfo(col,1).contentHash still keccak256('typo'); registerPiece(col,1,keccak256('<svg/>'),'T','D') -> reverts PieceIdNotNext(2); seal(col) -> reverts PieceNotFinalized(1); openSale(col) -> reverts NotSealed. Expected: the curator can repair or remove an unfinalized piece so the collection can still be sealed. Actual: collection col can never be sold. Reproduced in test/scratch/Review.t.sol::test_contentHashCannotBeRepaired.","severity":"medium","snippet":"        delete p.chunks;\n        p.size = 0;","title":"A registered piece's contentHash can never be corrected or the piece removed, so one bad hash permanently blocks seal and sale of the whole SVG collection"},{"citation":"resolved","description":"Reported by audit_flow and audit_permissions (merged); reproduced. createCollection and setPayout require _artists[x].approved, but registerPiece, uploadChunk and finalizePiece only compare msg.sender to c.artist, so a revoked artist keeps registering pieces (growing maxSupply), uploading chunks (SSTORE2 contract creation) and finalizing content into every unsealed collection it was given. Combined with the previous finding, a revoked artist can register an unfinalizable piece at any moment before seal and the curator can never seal, or front-run the curator's clearPiece with finalizePiece to lock content in. Fix: in _requireCuratorOrArtist also require _artists[c.artist].approved when the caller is the artist.","line":433,"path":"src/ArtHouse.sol","reproduction":"State: approveArtist(ARTIST,..); createCollection(ARTIST,'Plain','PLN') -> col; OWNER revokeArtist(ARTIST). ARTIST: registerPiece(col,1,keccak256('x'),'T','D') -> succeeds (expected NotApprovedArtist or NotCuratorOrArtist); uploadChunk(col,1,0,'x') -> succeeds; pieceInfo(col,1).chunkCount == 1. OWNER seal(col) now reverts PieceNotFinalized(1) with no preimage of keccak256('x') available. Reproduced in test/scratch/Review.t.sol::test_revokedArtistCanStillRegister.","severity":"low","snippet":"        if (msg.sender != owner() && msg.sender != c.artist) revert NotCuratorOrArtist();","title":"revokeArtist does not remove the artist's write access to its collections: the guard checks identity, not approval"},{"citation":"resolved","description":"Reported by write_foundry_tests; reproduced. _update clears wrappedKey on every transfer to a new holder. In the escrow the piece moves seller -> escrow on buy() (key deleted, previousOwner = seller) and escrow -> seller on cancel() (previousOwner = escrow). Nothing re-posts the seller's key, so any buyer can pay, cancel before the key is posted and leave the seller with an empty wrappedKey at the cost of gas only (the refund is complete). The wrapped bytes are not in any event either. The seller can repost as current owner only if they hold the plaintext content key off-chain; the curator cannot help because postContentKey reverts AlreadyClaimed after the claim. Fix: in cancel() restore the seller's key (store it in the listing at buy time and call setWrappedKey after the return transfer), or skip the clear when the piece returns from the escrow to the listed seller.","line":75,"path":"src/ArtifactsPiece.sol","reproduction":"Claim edition e for ALICE (wrappedKey = K, non-empty). ALICE escrow.list(e, 1 ether); BOB escrow.buy{value: 1 ether}(e, vk); BOB escrow.cancel(e). Expected: ownerOf(e) == ALICE and wrappedKey() == K. Actual: ownerOf(e) == ALICE, wrappedKey() is empty and previousOwner() == escrow. Reproduced in test/scratch/Review.t.sol::test_cancelWipesSellerKey.","severity":"low","snippet":"        if (from != address(0) && to != keyHolder) {\n            delete wrappedKey;\n            previousOwner = from;","title":"A buyer cancelling a resale wipes the seller's wrapped content key and nothing restores it"},{"citation":"resolved","description":"Reported by write_foundry_tests, audit_flow, audit_economics and audit_permissions (merged); reproduced. previousOwner exists so the seller of a plain ERC-721 transfer can post the re-wrapped key for the new owner. It is never cleared after use and only replaced by the next transfer, so after A -> B the address A can call setWrappedKey at any later time, including after B posted a correct key, replacing wrappedKey with arbitrary bytes while keyHolder still names B. Off-chain readers see a valid-looking but useless key; B can repair it only if B kept the plaintext content key, and A can repeat every block. Fix: allow previousOwner only while wrappedKey is empty, or clear previousOwner once a key is posted for the current holder.","line":63,"path":"src/ArtifactsPiece.sol","reproduction":"ALICE owns claimed piece e. ALICE piece.transferFrom(ALICE, BOB, e) (wrappedKey cleared, previousOwner = ALICE). BOB piece.setWrappedKey('bob-good') -> wrappedKey = 'bob-good', keyHolder = BOB. One year later ALICE piece.setWrappedKey('garbage'). Expected: revert NotKeyPoster once the holder has a key. Actual: succeeds, wrappedKey() == 'garbage', keyHolder() == BOB. Reproduced in test/scratch/Review.t.sol::test_previousOwnerOverwrites.","severity":"low","snippet":"            msg.sender != owner_ && msg.sender != previousOwner && msg.sender != factory","title":"previousOwner keeps setWrappedKey authority indefinitely and can overwrite the key the current owner posted for itself"},{"citation":"resolved","description":"Reported by audit_flow, audit_economics and audit_permissions (merged); reproduced. openSale checks price, VRF config and factory.locked() but not factory.sale() == address(this). buy() then credits the full price to artist, house and broker in the ArtHouse ledger (immediately withdrawable) and requests VRF; the callback's factory.assignEdition reverts ModulesNotSet or NotSale, the coordinator records the request as fulfilled with success = false, and the sale has no refund path. If setModules was never called the purchases wait for setModules plus a retryRequest after one day; if setModules was called with another sale address (one-time, ModulesAlreadySet) every retry fails forever and up to 255 buyers have paid for nothing. ArtifactsAuction.configure/settle has the same shape: settle reverts in factory.assignSource when factory.auction() != address(this), freezing the winning bid (refunds[] only holds outbid amounts). The harm needs a curator wiring error; the defect is the missing state check. Fix: require factory.sale() == address(this) in openSale and factory.auction() == address(this) in configure.","line":114,"path":"src/ArtifactsSale.sol","reproduction":"Deploy f2 = ArtifactsFactory(OWNER, house, pieceImpl) and s2 = ArtifactsSale(OWNER, house, f2); OWNER approveModule(s2, artifactsCollection); setPiece x256 and lock() on f2; f2.setModules(0xdead, 0xdead, 0xdead); s2.configureVRF(vrf, keyHash, 7, false); s2.setPrice(1 ether); s2.openSale() -> succeeds (expected: revert since f2.sale() != s2). ALICE s2.buy{value: 1 ether}(address(0)) -> succeeds; house.balances(ARTIST_PAYOUT, ETH) == 0.7 ether. Coordinator fulfils the request -> rawFulfillRandomWords reverts ArtifactsFactory.NotSale. ALICE has paid 1 ETH, purchase.edition stays 0 and setModules cannot be redone. Reproduced in test/scratch/Review.t.sol::test_openSaleWithoutWiring.","severity":"low","snippet":"        if (!factory.locked()) revert FactoryNotLocked();","title":"ArtifactsSale.openSale and ArtifactsAuction.configure do not check that the factory names them, so buyers can pay for pieces that can never be assigned or refunded"},{"citation":"resolved","description":"Reported by audit_flow and audit_permissions (merged); reproduced. ArtifactsEscrow._release ends with house.recordResale{value}(seller) and ArtifactsAuction.settle with house.recordSale{value}(highBidder, 0); both revert NotModule once the curator calls revokeModule on that module. In the escrow the buyer's cancel path closes when the key is posted (KeyAlreadyPosted), so after revocation confirm, release and cancel all revert and the buyer's ETH and the piece sit in the escrow until the curator re-approves. In the auction the winning bid is frozen the same way. README states no owner power can move a buyer's token or take a party's ledger balance; this power can lock both indefinitely. Curator power, recoverable by re-approval, hence low. Fix: let _release/settle fall back to crediting the seller directly or escrow proceeds locally for pull, or document that revoking a module freezes its in-flight operations.","line":176,"path":"src/ArtHouse.sol","reproduction":"ALICE owns claimed edition e. ALICE escrow.list(e, 2 ether); BOB escrow.buy{value: 2 ether}(e, key); ALICE escrow.postKey(e, 'rewrapped'); OWNER house.revokeModule(escrow). BOB escrow.confirm(e) -> reverts ArtHouse.NotModule; BOB escrow.cancel(e) -> reverts KeyAlreadyPosted; after 73 h escrow.release(e) -> reverts NotModule; piece.ownerOf(e) == escrow and escrow balance == 2 ether. Expected: the buyer always has either the piece or a refund path. Reproduced in test/scratch/Review.t.sol::test_revokeModuleStrandsEscrow.","severity":"low","snippet":"        delete moduleCollection[module];","title":"revokeModule on the escrow or auction freezes in-flight buyer ETH and the escrowed piece with no exit for the buyer"},{"citation":"resolved","description":"Reported by audit_permissions; reproduced. The brief gives the artist a payout address it controls through setPayout, and every sale credit and royaltyReceiver() reads it. approveArtist has no already-approved guard and writes the whole struct, so any re-approval (the only way to change a display name) replaces the payout the artist chose; the curator can point the artist's 70% and the ERC-2981 receiver of every collection of that artist (ArtCollection.royaltyInfo and ArtifactsPiece.royaltyInfo both resolve through royaltyReceiver) at any address without the artist's consent. Fix: keep the existing payout when the artist is already approved, or add a separate rename.","line":135,"path":"src/ArtHouse.sol","reproduction":"State: approveArtist(ARTIST, 'Myles', PAYOUT); createModuleCollection(ARTIST) -> collection 1. ARTIST setPayout(0xBEEF): artist(ARTIST).payout == 0xBEEF. OWNER approveArtist(ARTIST, 'Myles renamed', OWNER). Expected: payout stays 0xBEEF or the call reverts. Actual: artist(ARTIST).payout == OWNER and royaltyReceiver(1) == OWNER. Reproduced in test/scratch/Review.t.sol::test_reapproveOverwritesPayout.","severity":"low","snippet":"        _artists[artist_] = Artist({name: name, payout: payout, approved: true});","title":"approveArtist on an already-approved artist overwrites the payout address the artist chose, redirecting future credits and ERC-2981 royalties"},{"citation":"resolved","description":"Reported by audit_flow; reproduced. The init code encodes the runtime length as uint16(data.length + 1), a narrowing cast that truncates. For data.length == 65535 the PUSH2 length is 0 and CREATE returns a contract with empty code (pointer != 0, so WriteFailed is not raised); for 65536 <= data.length <= 90111 the runtime is data.length + 1 - 65536 bytes, the first bytes of the data. ArtStorage.uploadChunk allows one chunk up to 102400 bytes, so both cases are reachable by the curator or the artist; with an empty pointer pieceBytes, finalizePiece and later tokenURI revert ReadOutOfBounds until the piece is cleared, and with a truncated one pieceInfo reports the full size while pieceBytes returns the truncated bytes and finalizePiece fails for a reason the uploader cannot see. Recoverable by clearPiece, hence low. Fix: revert when data.length + 1 > 24576 (EIP-170) before building the init code.","line":15,"path":"src/libraries/SSTORE2.sol","reproduction":"OWNER: registerPiece(col,1,keccak256(d65535),'T','D'); uploadChunk(col,1,0,d65535) where d65535 is 65535 bytes of 0x41 -> succeeds; chunkPointer(col,1,0).code.length == 0; artStorage.pieceBytes(col,1) reverts ReadOutOfBounds. registerPiece(col,2,..); uploadChunk(col,2,0,d70000) -> succeeds; pieceBytes(col,2).length == 4464 instead of 70000. Expected: the full bytes stored or an explicit revert in uploadChunk. Reproduced in test/scratch/Review.t.sol::test_sstore2Truncation.","severity":"low","snippet":"        bytes memory code = abi.encodePacked(hex\"61\", uint16(data.length + 1), hex\"80600a3d393df300\", data);","title":"SSTORE2.write truncates the payload length to 16 bits: a 65535-byte chunk stores an empty pointer and 65536 to 90111 bytes are stored silently truncated"},{"citation":"resolved","description":"Reported by audit_economics; reproduced. A listing is keyed by pieceId and seller only; ArtifactsPiece._update does not notify the escrow and buy() only checks that the current owner equals l.seller. If the seller moves the piece away by a plain transfer (a cold wallet, an OTC buyer who sells it back, a display contract) and later owns it again, the old listing becomes executable again at the old price and any watcher can take the piece for it. Fix: bind the listing to a transfer epoch on the piece checked in buy(), or clear the listing when the piece leaves the listed seller.","line":93,"path":"src/ArtifactsEscrow.sol","reproduction":"ALICE owns claimed edition e. ALICE escrow.list(e, 1 ether); ALICE piece.transferFrom(ALICE, BOB, e); BOB piece.transferFrom(BOB, ALICE, e); CAROL escrow.buy{value: 1 ether}(e, key). Expected: revert because the listing died with the transfer. Actual: buy succeeds, ownerOf(e) == escrow and listing(e).buyer == CAROL at the stale 1 ETH price. Reproduced in test/scratch/Review.t.sol::test_staleListingRevives.","severity":"low","snippet":"        if (piece.ownerOf(pieceId) != l.seller) revert NotPieceOwner();","title":"An escrow listing survives a plain transfer and revives at the stale price when the piece returns to the seller"},{"citation":"resolved","description":"Reported by write_foundry_tests and audit_flow (merged); reproduced. Title and description come from registerPiece, which the collection's artist may call, and are concatenated raw into the JSON. A double quote, backslash or control character yields invalid JSON (wallets and marketplaces fail to render every token of the piece) or lets extra fields such as a second image be injected, and the text is fixed forever once registered (see the contentHash finding). Fix: escape quotes, backslashes and control characters when building the JSON, or reject them in registerPiece.","line":76,"path":"src/ArtCollection.sol","reproduction":"OWNER or ARTIST: registerPiece(col,1,keccak256('<svg/>'),'A\"B','D'); uploadChunk; finalizePiece; seal; setPrices(col,1 ether,0); openSale; ALICE mint{value:1 ether}(col,1,0). Base64-decoding token.tokenURI(1) gives {\"name\":\"A\"B\",\"description\":\"D\",\"image\":\"data:image/svg+xml;base64,PHN2Zy8+\"}, which is not valid JSON. Expected: a valid JSON document for every registered piece. Reproduced in test/scratch/Review.t.sol::test_jsonNotEscaped.","severity":"low","snippet":"            '{\"name\":\"',\n            title,","title":"ArtCollection.tokenURI embeds artist-supplied title and description without JSON escaping"},{"citation":"resolved","description":"Reported by write_foundry_tests, audit_flow and audit_economics (merged); reproduced. highBid * 500 / 10_000 truncates to 0 for highBid < 20 wei, so minimumBid() == highBid and bid() accepts msg.value == highBid, contradicting 'each bid must beat the current high bid by at least 5%'. Only reachable with a dust reserve, so no practical economic impact. Fix: round the increment up or additionally require msg.value > highBid.","line":65,"path":"src/ArtifactsAuction.sol","reproduction":"OWNER auction.configure(1, now + 1 day); ALICE bid{value: 19}(); BOB bid{value: 19}(). Expected: revert BelowIncrement. Actual: succeeds and highBidder() == BOB, ALICE refunded 19 wei. Reproduced in test/scratch/Review.t.sol::test_auctionIncrement.","severity":"info","snippet":"        return highBid + highBid * MIN_INCREMENT_BPS / 10_000;","title":"Auction minimum increment rounds down: below 20 wei an equal bid displaces the leader, and at any size the threshold can sit 1 wei under 5%"},{"citation":"resolved","description":"Own finding. royaltyInfo resolves through ArtifactsFactory.royaltyReceiver -> ArtHouse.moduleRoyaltyReceiver(factory), which reverts NotModule when moduleCollection[factory] == 0. The factory never records sales, so its module approval only serves this lookup; the launch notes do list approving it, but a later revokeModule(factory) (or never approving it) silently turns every ARTIFACTS piece's royaltyInfo into a revert, which marketplaces reading ERC-2981 treat as no royalty or a broken token. Curator wiring, no fund loss, hence info. Fix: resolve the receiver through the collection id directly (store it in the factory) rather than through a revocable module mapping.","line":113,"path":"src/ArtifactsPiece.sol","reproduction":"Full ARTIFACTS setup with factory approved as module; ALICE claims edition e. piece.royaltyInfo(e, 1 ether) returns (ARTIST_PAYOUT, 0.075 ether). OWNER house.revokeModule(factory). piece.royaltyInfo(e, 1 ether) now reverts ArtHouse.NotModule. Expected: the 7.5% royalty to the artist's payout is always reported. Reproduced in test/scratch/Review.t.sol::test_royaltyInfoRevertsIfFactoryModuleRevoked.","severity":"info","snippet":"        receiver = IArtifactsFactory(factory).royaltyReceiver();","title":"ArtifactsPiece.royaltyInfo depends on ArtifactsFactory staying an approved ArtHouse module; revoking it makes every piece's ERC-2981 call revert"},{"citation":"resolved","description":"Reported by audit_permissions; reproduced. Trust assumptions, not bypasses. (a) setSplit has no sale-closed guard, unlike setPrices and ArtifactsSale.setPrice, so the curator can move the artist's 70% to the house while a sale is open, including right before a specific purchase; the buyer pays the same exact price. (b) configureVRF is one-time and owner-chosen; a coordinator under the curator's control may call rawFulfillRandomWords with chosen words, so edition assignment is only as random as the configured address, which buyers must verify is Chainlink's Sepolia coordinator. (c) The curator alone posts content keys and may refuse. None can mint without payment or take ledger balances.","line":214,"path":"src/ArtHouse.sol","reproduction":"(a) Full ARTIFACTS setup, sale open at 1 ETH. OWNER setSplit(artifactsCollection, 0, 10000, 0); ALICE sale.buy{value: 1 ether}(0). Actual: balances(ARTIST_PAYOUT, ETH) == 0 and balances(house, ETH) == 1 ether. Expected per brief: split fixed while the sale is open, or at least closed-only like setPrices. Reproduced in test/scratch/Review.t.sol::test_setSplitWhileOpen. (b) OWNER configureVRF(attackerCoordinator, ..): openSale passes; the coordinator returns request ids and later calls rawFulfillRandomWords(id, [chosen]) as msg.sender == vrfCoordinator, selecting the edition.","severity":"info","snippet":"    function setSplit(uint256 collection_, uint16 brokerBps, uint16 houseBps, uint16 artistBps) external onlyOwner {","title":"Curator powers the brief implies but the code does not bound: setSplit while a sale is open, and the owner-chosen VRF coordinator decides the random edition"}],"hash":"224ee6cbf1ba74411b9d0bd7a018d8d91352dd376dac62da560042b2f363aa38","nodeId":"c15651d1-db5f-4f71-894d-6d38ac6748fd","outcome":"completed","summary":"The review is complete and `.imd-findings.json` holds 14 findings and 87 coverage rows covering all 85 entry points.\n\n**What I verified**\n\n- Every specialist claim I kept was reproduced with a Foundry test in `test/scratch/Review.t.sol`, and all 14 of those tests pass on this tree, confirming the behaviour. The existing suite still passes.\n- The audit_math proof for the quadratic storage read fails on this code as stated, so it stays attached as the one proof.\n- Duplicates across the four specialists and the test writer were merged: the previous-owner key overwrite (four reports), the sale wiring check (three), the auction rounding (three), the JSON escaping (two), the content-hash repair gap (two), the revoked-artist guard (two) and the module revocation freeze (two).\n\n**Findings kept, by severity**\n\n- Medium: `ArtStorage._read` is quadratic in chunk count, so a 100 KB piece in 1 KB chunks cannot be finalized in 30M gas or rendered in a 50M eth_call.\n- Medium: a registered piece's content hash can never be corrected or removed, so one bad hash permanently blocks seal and sale of the whole SVG collection.\n- Low: revoked artists keep write access; buyer cancel wipes the seller's wrapped key; previous owner can overwrite the key forever; sale and auction do not check the factory wiring; revoking a module strands escrow and auction funds; re-approving an artist overwrites their payout; SSTORE2 truncates lengths above 65534 bytes; stale escrow listings revive; tokenURI JSON is unescaped.\n- Info: auction increment rounds down below 20 wei; ERC-2981 on pieces reverts if the factory's module approval is revoked (my own finding); setSplit while open and the owner-chosen VRF coordinator are documented trust assumptions.\n\n**Leads dropped**\n\n- The slither reentrancy line on `ArtifactsSale.buy` and the aderyn locked-ether line do not reproduce. The function is guarded, calls only trusted contracts and forwards all value.\n- The VRF callback measures about 103k gas, well within its 250k limit, so that path holds.\n- The manifest lists seven contracts in dependency order with matching argument counts, and every runtime is under the size limits.","treeHash":null,"usage":{"cachedInputTokens":1210194,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":45466,"runtime":"claude","turns":28,"wallClockMs":746259}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"7e64d4917fa814e6","findings":[{"citation":"resolved","description":"First Principles / Execution Trace (wrong-state execution). ArtStorage.registerPiece writes contentHash once and only accepts pieceId == pieceCount+1, so an existing piece cannot be re-registered. clearPiece (the only curator repair tool the brief names: 'until a piece is finalized the curator can clear it') only resets chunks and size and leaves contentHash untouched. finalizePiece requires keccak256(stored bytes) == contentHash, seal requires every registered piece finalized, and ArtHouse.openSale requires seal. Hence a single registerPiece with a hash whose preimage the uploader does not have (a typo, a hash of a draft that was later edited, or a hash computed over the wrong encoding) leaves that piece unfinalizable forever, which leaves the collection unsealable forever, which leaves mint/mintWithIMD permanently reverting with SaleNotOpen. There is no delete/replace path. The brief's 'curator can clear it' repair guarantee is therefore broken: clearing cannot recover from the most common upload mistake. Minimal fix that preserves the design: let clearPiece (curator-only, unfinalized pieces, unsealed collection) also accept a new contentHash/title/description, or add a curator-only reRegisterPiece for unfinalized pieces, or let clearPiece pop the last unfinalized piece so pieceCount shrinks.","line":103,"path":"src/ArtStorage.sol","reproduction":"Confirmed with a Foundry test on this tree. State: OWNER created collection col for ARTIST. Calls (as OWNER): registerPiece(col, 1, keccak256(\"typo\"), \"T\", \"D\"); uploadChunk(col, 1, 0, \"<svg/>\"); finalizePiece(col, 1) -> reverts ContentHashMismatch; clearPiece(col, 1) -> ok but contentHash still keccak256(\"typo\"); registerPiece(col, 1, keccak256(\"<svg/>\"), \"T\", \"D\") -> reverts PieceIdNotNext(2); seal(col) -> reverts PieceNotFinalized(1); openSale(col) -> reverts NotSealed. Expected: the curator can repair or remove an unfinalized piece so the collection can still be sealed. Actual: collection col can never be sold; the only recourse is a brand-new collection with a new token address.","severity":"medium","snippet":"        delete p.chunks;\n        p.size = 0;","title":"A registered piece's contentHash can never be corrected, so one typo permanently blocks seal and sale of the whole SVG collection"},{"citation":"resolved","description":"Periphery (assembly byte-width / truncation). The init code encodes the runtime length as uint16(data.length + 1). Solidity narrowing casts truncate, so for data.length == 65535 the PUSH2 length is 0 and CREATE returns a contract with empty code (pointer != 0, so WriteFailed is not raised); for 65536 <= data.length <= 90111 the stored runtime is data.length+1-65536 bytes, i.e. the first (data.length-65536) data bytes. ArtStorage.uploadChunk allows a single chunk up to MAX_PIECE_BYTES = 102400 bytes (the only per-chunk bound is the 100 KB total), so both cases are reachable by the curator or the collection's artist. Consequences: (a) with a 65535-byte chunk, p.size and p.chunks are updated but SSTORE2.read reverts ReadOutOfBounds, so pieceBytes, finalizePiece and later ArtCollection.tokenURI revert until the curator clears the piece; (b) with a 70000-byte chunk, pieceInfo reports size 70000 while pieceBytes returns 4464 bytes, and finalizePiece fails with ContentHashMismatch for a reason the uploader cannot see. Both are recoverable through clearPiece, so impact is limited to bricking/confusion, but the library is also used for the ARTIFACTS ciphertext in ArtifactsFactory.setPiece. Fix: revert when data.length + 1 > 24576 (EIP-170) or at least > type(uint16).max, before building the init code.","line":15,"path":"src/libraries/SSTORE2.sol","reproduction":"Confirmed with a Foundry test on this tree. (a) OWNER: registerPiece(col, 1, keccak256(data65535), \"T\", \"D\"); uploadChunk(col, 1, 0, data65535) where data65535 is 65535 bytes of 0x41 -> succeeds; pieceInfo size = 65535, chunkCount = 1; chunkPointer(col,1,0).code.length == 0; artStorage.pieceBytes(col, 1) reverts ReadOutOfBounds; finalizePiece(col, 1) reverts ReadOutOfBounds. Expected: either the full 65535 bytes stored or an explicit revert in uploadChunk. (b) same with 70000 bytes of 0x42 -> uploadChunk succeeds, pieceInfo size = 70000, pieceBytes(col,1).length == 4464.","severity":"low","snippet":"        bytes memory code = abi.encodePacked(hex\"61\", uint16(data.length + 1), hex\"80600a3d393df300\", data);","title":"SSTORE2.write truncates the payload length to 16 bits: a chunk of 65535 bytes stores an empty pointer and chunks of 65536-90111 bytes are stored silently truncated"},{"citation":"resolved","description":"Execution Trace (wrong-state execution across transactions). _requireCuratorOrArtist checks c.artist only, never _artists[c.artist].approved, so registerPiece, uploadChunk and finalizePiece stay open to an artist the curator has revoked. Combined with finding 1 (a registered piece can never be removed) the artist of a collection can at any moment before seal call registerPiece(collection, next, randomHash, ..) and the curator can never seal: seal iterates every piece and reverts PieceNotFinalized for the junk piece, clearPiece cannot remove it, and revokeArtist does nothing to stop a repeat. The brief says the curator 'approves and revokes artists'; after revocation the artist should lose the ability to write. Impact: the collection can never open; house and broker revenue from it is lost; the artist can also keep front-running the curator's seal transaction with a new registerPiece. Fix: require _artists[c.artist].approved in _requireCuratorOrArtist (or let only the curator register pieces), and give the curator a way to drop an unfinalized trailing piece.","line":433,"path":"src/ArtHouse.sol","reproduction":"Confirmed with a Foundry test on this tree. State: OWNER approveArtist(ARTIST,..), createCollection(ARTIST, \"Plain\", \"PLN\") -> col. OWNER revokeArtist(ARTIST). ARTIST calls house.registerPiece(col, 1, keccak256(\"x\"), \"T\", \"D\") -> succeeds (expected: NotCuratorOrArtist / NotApprovedArtist). pieceInfo(col,1).finalized == false and no preimage of keccak256(\"x\") is known, so OWNER seal(col) reverts PieceNotFinalized(1) forever; openSale(col) reverts NotSealed.","severity":"low","snippet":"        if (msg.sender != owner() && msg.sender != c.artist) revert NotCuratorOrArtist();","title":"revokeArtist does not remove the artist's write access to its collection, so a revoked artist can block seal indefinitely by registering unfinalizable pieces"},{"citation":"resolved","description":"Execution Trace (wrong-state execution / dependency mismatch). openSale checks price, VRF config and factory.locked() but not factory.sale() == address(this). buy() then succeeds: msg.value is split and credited to artist, house and broker in ArtHouse.recordSale, and a VRF request is made. When the callback arrives, factory.assignEdition reverts NotSale (or ModulesNotSet), the whole callback reverts, and the VRF coordinator marks the request fulfilled with success=false. Because ArtifactsFactory.setModules is one-time (ModulesAlreadySet), if it was ever called with a different sale address the purchase can never be assigned and retryRequest only burns LINK every day. The buyer's ETH has already been distributed in the ledger and there is no refund path. The same shape applies to ArtifactsAuction.configure/settle: settle reverts in factory.assignSource when factory.auction() != address(this), stranding the high bid in the auction with no refund path (refunds[] only holds outbid amounts). Fix: in openSale require factory.sale() == address(this) and in configure require factory.auction() == address(this) (both are view calls on the already-trusted factory); optionally also check house.moduleCollection(address(this)) != 0 so buy cannot open on an unapproved module.","line":114,"path":"src/ArtifactsSale.sol","reproduction":"Confirmed with a Foundry test on this tree. Deploy f2 = ArtifactsFactory(OWNER, house, pieceImpl); s2 = ArtifactsSale(OWNER, house, f2); OWNER approveModule(s2, artifactsCollection); setPiece x256 and lock() on f2; f2.setModules(0xdead, 0xdead, 0xdead); s2.configureVRF(vrf, keyHash, 7, false); s2.setPrice(1 ether); s2.openSale() -> succeeds (expected: revert because f2.sale() != s2). ALICE: s2.buy{value: 1 ether}(address(0)) -> succeeds; house.balances(ARTIST_PAYOUT, ETH) == 0.7 ether. Coordinator fulfils requestId -> rawFulfillRandomWords reverts ArtifactsFactory.NotSale. ALICE has paid 1 ETH and can never receive an edition; setModules cannot be redone.","severity":"low","snippet":"        if (!factory.locked()) revert FactoryNotLocked();","title":"ArtifactsSale.openSale and ArtifactsAuction.configure do not check that the factory's module wiring points at them, so buyers can pay for editions that can never be assigned"},{"citation":"resolved","description":"Execution Trace (mid-operation config mutation). ArtifactsEscrow._release ends with house.recordResale{value}(seller), and ArtifactsAuction.settle ends with house.recordSale{value}(highBidder, 0). Both revert NotModule as soon as the curator calls revokeModule(escrow) / revokeModule(auction). In the escrow, once the seller has posted the key the buyer's cancel path is closed (KeyAlreadyPosted), confirm and release revert, so the buyer's ETH sits in the escrow and the piece sits in the escrow until the curator re-approves the module. In the auction, after endTime nobody can bid and settle reverts, so the winning bid is frozen. README states that no owner power can 'move a buyer's token, or take another party's ledger balance'; revocation cannot take funds but it can freeze them indefinitely, which the trust section does not disclose. This is a curator power, so it is reported as a trust-assumption gap with a concrete mechanism rather than a bypass. Fix options: make _release/settle tolerate NotModule by crediting the seller directly (or escrowing proceeds locally with a pull), or document that revoking a module freezes its in-flight operations.","line":176,"path":"src/ArtHouse.sol","reproduction":"Confirmed with a Foundry test on this tree. Full ARTIFACTS setup; ALICE owns claimed edition ed. ALICE escrow.list(ed, 2 ether); BOB escrow.buy{value: 2 ether}(ed, 0x63); ALICE escrow.postKey(ed, \"rewrapped\"); OWNER house.revokeModule(address(escrow)). BOB escrow.confirm(ed) -> reverts ArtHouse.NotModule; BOB escrow.cancel(ed) -> reverts KeyAlreadyPosted; after 72h anyone release(ed) -> reverts NotModule; piece.ownerOf(ed) == escrow and BOB's 2 ETH stays in the escrow. Expected: the buyer always has either the piece or a refund path.","severity":"low","snippet":"        delete moduleCollection[module];","title":"revokeModule on the escrow or auction strands in-flight buyer funds and the escrowed token with no exit for the buyer"},{"citation":"resolved","description":"First Principles (stale authority). previousOwner is meant to let the seller of a plain ERC-721 transfer post the re-wrapped key for the new owner once. It is never cleared after use and is only replaced by the next ownership change, so the previous owner can call setWrappedKey at any later time, including after the new owner has posted its own wrapped key, replacing wrappedKey with arbitrary bytes while keyHolder still names the new owner. Off-chain consumers that read wrappedKey/keyHolder from the piece contract will see a valid-looking but useless key. The current owner can re-set it, so there is no permanent loss, but the on-chain record is corruptible by a party that no longer owns the token. Fix: clear previousOwner inside setWrappedKey once a key has been posted for the current keyHolder (or restrict previousOwner to posting only while wrappedKey is empty).","line":63,"path":"src/ArtifactsPiece.sol","reproduction":"Confirmed with a Foundry test on this tree. ALICE owns claimed piece ed; ALICE piece.transferFrom(ALICE, BOB, ed) (wrappedKey cleared, previousOwner = ALICE). BOB piece.setWrappedKey(\"good-for-bob\") -> wrappedKey = good-for-bob, keyHolder = BOB. ALICE piece.setWrappedKey(\"garbage\") -> succeeds; piece.wrappedKey() == \"garbage\" with keyHolder == BOB. Expected: once BOB holds a key for himself, ALICE has no authority over it.","severity":"low","snippet":"            msg.sender != owner_ && msg.sender != previousOwner && msg.sender != factory","title":"previousOwner keeps setWrappedKey authority indefinitely and can overwrite the key the current owner set for itself"},{"citation":"resolved","description":"Boundary case in minimumBid: highBid * 500 / 10_000 is 0 for highBid < 20 wei, so minimumBid() == highBid and bid() accepts msg.value == highBid, violating 'each bid must beat the current high bid by at least 5%'. Only reachable if the curator sets a reserve below 20 wei, so it has no practical economic impact; reported because the increment rule is a stated requirement. Fix: require msg.value > highBid in addition to the increment, or round the increment up.","line":65,"path":"src/ArtifactsAuction.sol","reproduction":"Confirmed with a Foundry test on this tree. OWNER auction.configure(10, now + 1 day); ALICE bid{value: 10}(); BOB bid{value: 10}() -> succeeds and highBidder == BOB. Expected: BelowIncrement revert for a bid that does not exceed the high bid.","severity":"info","snippet":"        return highBid + highBid * MIN_INCREMENT_BPS / 10_000;","title":"Auction minimum increment rounds to zero below 20 wei, so an equal bid displaces the high bidder"},{"citation":"resolved","description":"Periphery (encoder). The README documents that ARTIFACTS trait strings must not contain quotes, but ArtCollection's title/description come from registerPiece, which the collection's artist may call (not only the curator). A title containing a double quote or backslash produces invalid JSON in tokenURI, so marketplaces and wallets fail to render every token of that piece; the title is fixed at registration and cannot be edited (see finding 1). Fix: escape \" and \\\\ (and control characters) when building the JSON, or validate titles/descriptions in registerPiece.","line":75,"path":"src/ArtCollection.sol","reproduction":"ARTIST: house.registerPiece(col, 1, keccak256(svg), 'A\"B', 'D'); upload, finalize, seal, open, mint. token.tokenURI(1) decodes to {\"name\":\"A\"B\",\"description\":\"D\",...} which is not valid JSON. Expected: a valid JSON document for every registered piece.","severity":"info","snippet":"        bytes memory json = abi.encodePacked(","title":"ArtCollection.tokenURI embeds artist-supplied title and description without JSON escaping"}],"hash":"29612bc24c4459d5375ce3dd15af508e19bfe494d2c5629c953c4bf8386881dc","nodeId":"edb419ac-86ca-4310-9ce8-a44ca0fc84a2","outcome":"completed","summary":"Review complete. The findings file is written and validated: every snippet matches the tree, and all 85 listed entry points have a coverage row.\n\n**What I did**\n\nI read the Senior Auditor's Mindset plus the Execution Trace, Periphery and First Principles guides, then traced every state-changing entry point across the seven contracts and two libraries. The build only fits the 1.5 GB memory cgroup when the large existing test files are skipped, so I confirmed each claim with a scratch Foundry test compiled against the source alone. All seven probes passed, meaning each defect reproduces on this tree.\n\n**Findings, most severe first**\n\n1. **Medium, ArtStorage.clearPiece:** a registered piece's content hash can never be corrected. Clear only resets chunks, re-registration needs the next id, so one typo leaves the collection unsealable and unsellable forever.\n2. **Low, SSTORE2.write:** the length is cast to 16 bits. A 65535-byte chunk deploys an empty pointer and reads revert; 65536 to 90111 bytes store silently truncated data. Both pass the 100 KB check and are reachable from uploadChunk.\n3. **Low, ArtHouse revocation:** a revoked artist keeps write access to its collection and can block seal indefinitely by registering pieces nobody can finalize.\n4. **Low, ArtifactsSale.openSale and ArtifactsAuction.configure:** neither checks the factory's module wiring. Buyers can pay into a sale whose VRF callback reverts forever if setModules was consumed with another address.\n5. **Low, ArtHouse.revokeModule:** revoking the escrow or auction strands the buyer's ETH and the escrowed token with no refund path once the key is posted.\n6. **Low, ArtifactsPiece.setWrappedKey:** the previous owner keeps key-posting authority indefinitely and can overwrite the key the current owner set.\n7. **Info:** the auction increment rounds to zero below 20 wei, so an equal bid wins.\n8. **Info:** ArtCollection.tokenURI does not escape artist-supplied title and description.\n\n**Coverage**\n\nThe ledger, VRF assignment, Fisher-Yates uniqueness, escrow and auction flows, withdrawals and clone initialization all hold under my area's checks. ETH conservation holds in ArtHouse, the escrow and the auction. No critical or high defect was found, so no proof test file is attached. The scratch probes remain under test/scratch for the judge's reference and are not part of the submission.","treeHash":null,"usage":{"cachedInputTokens":1391743,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":57090,"runtime":"claude","turns":36,"wallClockMs":936219}},{"artifacts":[],"attempt":1,"bundleHash":"fb1a5fe3b06a75376fbbd6dec49bbfda8ff5b2fcdef03a74a6c5783894a98d8f","device":"e382bd4d2b3e471f","findings":[{"description":"approveArtist keeps the artist's own payout only when the artist is currently approved (`if (a.approved) payout = a.payout;`). After revokeArtist the flag is false, so re-approving with a different payout argument replaces the address the artist chose with setPayout. Every later ERC-2981 royaltyInfo answer for that artist's collections (ArtCollection and every ARTIFACTS piece contract) and every later artist share in the ledger then goes to the curator-supplied address. The brief says the payout address is one 'the artist can change'; this two-call sequence lets the curator change it instead. The README documents that re-approving an approved artist keeps the payout, but not that a revoked artist's payout is reset. This is a curator power, consistent with the documented trust model, so it is reported as a trust note rather than a defect: the fix, if wanted, is to keep a.payout whenever it is non-zero (the artist has been approved at least once) regardless of the approved flag, or to document the reset explicitly.","line":141,"path":"src/ArtHouse.sol","reproduction":"1) owner: approveArtist(A, \"Name\", P1). 2) A: setPayout(P2). 3) owner: revokeArtist(A). 4) owner: approveArtist(A, \"Name\", P3). Expected (per the brief's 'payout the artist can change'): artist(A).payout == P2 and royaltyReceiver(collection) == P2. Actual: artist(A).payout == P3 and royaltyReceiver(collection) == P3, so future ERC-2981 royalties and ledger artist shares are redirected to P3 without the artist's action. Credits already written stay with P2.","severity":"low","title":"Revoke-then-reapprove lets the curator overwrite an artist's chosen payout address"}],"hash":"2ba7209f7417c971f0162b3b3dcbe8c3eb2f88a7a9af0a8e4b669081effabd13","nodeId":"35435f62-f8e4-46e6-a9da-f54c04ebe0b7","outcome":"completed","summary":"The scratch reproduction confirms the finding. The work is complete.\n\n**Outcome.** The implementer's revision changed two behaviours my previous tests asserted, so those two tests failed. I updated them to the new design and extended coverage of the new code. `forge test` with the verifier's seed passes all 105 tests, and only `test/FailurePaths.t.sol` and `test/Invariants.t.sol` changed.\n\n**What changed in my tests**\n\n- Auction settle while revoked no longer reverts. The test now checks that the winning bid is held, the winner can still claim THE SOURCE, the outbid refund is unaffected, forwarding fails until re-approval, anyone can forward once, and a second revocation cannot recreate a hold.\n- Revoked module royalty lookup no longer reverts. The test now checks it still resolves, strangers still get the revert, and re-approval restores recording.\n- New failure-path tests: escrow release under a revoked module via timed release holds the full price for the seller with no royalty and no double payment on re-approval; cancel restores the seller's key only if one existed; a self-transfer keeps the key but kills the listing; an old owner's stale listing cannot sell the new owner's piece; split changes are frozen only for an open SVG sale; re-approving keeps an artist's payout and revocation removes write access; repair functions reject strangers, module collections, finalized pieces and sealed collections, and a dropped id can be re-registered and sealed; chunk and piece limits at the exact boundary; a 100-chunk piece finalizes under block gas.\n- JSON escaping, which the implementer's suite did not test as a property: a fuzz round-trip through an independent unescaper that rejects raw quotes and control characters, pinned edge cases, and a full decoded tokenURI check.\n- A fuzz over tiny bids confirms the minimum bid is strictly above the high bid and at least 5% over.\n\n**Invariant handlers.** The escrow and auction handlers now toggle module approval mid-flow and track held proceeds, so conservation is checked across revocation. I also found and fixed a bug in my previous handlers: a prank placed before a view call was consumed by it, so every broker toggle had silently reverted. All handler actions now run with zero reverts.\n\n**Finding reported.** One low-severity trust note in `.imd-findings.json`: revoking an artist and re-approving them lets the curator overwrite the payout address the artist chose, redirecting future royalties. I verified the reproduction in a scratch test. No critical or high defects were found in the revised code.","treeHash":"10f244f5f2e45ed8410bca3c17427d18f11bd069","usage":{"cachedInputTokens":2380955,"inputTokens":642,"model":"claude-fable-5-1","outputTokens":39767,"runtime":"claude","turns":53,"wallClockMs":1610882}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"3c9b660ee1938912","findings":[{"citation":"resolved","description":"Access x asymmetry (Trust Gap seam 3). The brief gives the artist a payout address it controls through setPayout, and every sale credit and royaltyReceiver() read that address. approveArtist has no 'already approved' guard and writes the whole struct, so any re-approval (the only way to change a display name) replaces the payout the artist chose. The curator can therefore point future artist shares and the ERC-2981 receiver of every collection of that artist (ArtCollection.royaltyInfo and ArtifactsPiece.royaltyInfo both resolve through royaltyReceiver) at any address, including its own, without the artist's consent. setPayout (line 145-150) is guarded by the artist's identity; its curator-side twin is not guarded against overwriting. Fix: in approveArtist, if the artist is already approved keep the existing payout (or add a separate renameArtist), so only the artist moves its payout.","line":135,"path":"src/ArtHouse.sol","reproduction":"State: approveArtist(ARTIST, 'Myles', PAYOUT); createModuleCollection(ARTIST) -> collection 1. 1) ARTIST calls setPayout(0xBEEF); artist(ARTIST).payout == 0xBEEF. 2) OWNER calls approveArtist(ARTIST, 'Myles (renamed)', OWNER). Actual: artist(ARTIST).payout == OWNER and royaltyReceiver(1) == OWNER; the next recordSale/mint credits 70% to OWNER and every piece's royaltyInfo names OWNER. Expected: the payout chosen by the artist is kept (or the call reverts for an approved artist). Verified in test/scratch/Perms.t.sol::test_reapproveOverwritesPayout.","severity":"low","snippet":"        _artists[artist_] = Artist({name: name, payout: payout, approved: true});","title":"approveArtist on an already-approved artist silently overwrites the artist-controlled payout address (curator can redirect future credits and ERC-2981 royalties)"},{"citation":"resolved","description":"Lifecycle asymmetry: registerPiece writes contentHash/title/description and increments pieceCount with no reverse operation. The curator's 'clear' (brief: 'Until a piece is finalized the curator can clear it') resets only chunks and size, and registerPiece requires pieceId == pieceCount+1 so the slot cannot be re-registered. finalizePiece needs keccak256(bytes) == contentHash and seal() needs every piece finalized, so a piece registered with an unmatchable hash (a typo, or bytes32(0) which has no preimage) permanently blocks seal(collection) and with it openSale/mint for the whole collection. The curator or the collection's artist can trigger it (registerPiece is open to both, including a revoked artist, see finding 3). The ArtCollection clone and its name/symbol are wasted; the only remedy is a new collection. Fix: let clearPiece (or a curator-only resetPiece) also reset contentHash/title/description, or allow re-registering the last unfinalized piece.","line":103,"path":"src/ArtStorage.sol","reproduction":"State: createCollection(ARTIST,'SVG','SVG') -> id 2. 1) ARTIST (or OWNER) calls registerPiece(2, 1, bytes32(0), 't', 'd'); uploadChunk(2,1,0,'x'). 2) OWNER calls clearPiece(2,1): chunks gone, contentHash still bytes32(0). 3) OWNER calls registerPiece(2, 1, keccak256('real'), 't','d') -> reverts PieceIdNotNext(2). 4) OWNER calls seal(2) -> reverts PieceNotFinalized(1), and finalizePiece(2,1) can never succeed because no bytes hash to 0. Expected: the curator can repair or drop an unfinalized piece so the collection can still be sealed. Verified in test/scratch/Perms.t.sol::test_revokedArtistBlocksSealForever.","severity":"low","snippet":"        delete p.chunks;\n        p.size = 0;","title":"A registered piece can never be unregistered or have its contentHash corrected; clearPiece only drops chunks, so one bad hash makes seal() and openSale impossible forever"},{"citation":"resolved","description":"Inconsistent guards on the same role: createCollection (line 188) and setPayout (line 146) require _artists[x].approved, but the write path used after creation compares msg.sender to c.artist only. revokeArtist therefore does not remove the artist's ability to register new pieces (growing maxSupply), upload chunks (SSTORE2 contract creation on the house's storage), or finalize content into an unsealed collection; combined with finding 2 a revoked artist can register an unfinalizable piece and permanently block the collection, or front-run the curator's clearPiece with finalizePiece to lock its content in. Fix: _requireCuratorOrArtist should also require _artists[c.artist].approved when the caller is the artist.","line":433,"path":"src/ArtHouse.sol","reproduction":"State: approveArtist(ARTIST,...); createCollection(ARTIST,'SVG','SVG') -> id 2; OWNER calls revokeArtist(ARTIST). 1) ARTIST calls registerPiece(2, 1, bytes32(0), 't', 'd') -> succeeds (expected: NotApprovedArtist). 2) ARTIST calls uploadChunk(2,1,0,'x') -> succeeds. 3) OWNER can no longer seal(2) (PieceNotFinalized(1)). Verified in test/scratch/Perms.t.sol::test_revokedArtistBlocksSealForever.","severity":"low","snippet":"        if (msg.sender != owner() && msg.sender != c.artist) revert NotCuratorOrArtist();","title":"Revoked artists keep registerPiece/uploadChunk/finalizePiece rights on their collections (guard checks identity, not approval)"},{"citation":"resolved","description":"The brief lets the previous owner post a re-wrapped key for the new owner after a plain transfer. The implementation grants that right with no end: previousOwner is only replaced by the next transfer, and setWrappedKey has no one-shot or 'key empty' condition. After the new owner has set its own usable key, the previous owner can at any later time overwrite wrappedKey with garbage (keyHolder stays the owner, so readers see a 'valid' key for the holder). The holder can re-set, but a hostile previous owner can repeat this every block, and any off-chain client that reads wrappedKey at a given moment may get the garbage. Fix: allow previousOwner only while wrappedKey is empty (the post-transfer gap it exists for), or clear previousOwner once the owner sets its own key.","line":63,"path":"src/ArtifactsPiece.sol","reproduction":"State: ALICE claimed edition e (keyHolder ALICE). 1) ALICE transferFrom(ALICE,BOB,e): wrappedKey cleared, previousOwner=ALICE. 2) BOB setWrappedKey('bob-good'). 3) One year later ALICE calls setWrappedKey('garbage') -> succeeds; wrappedKey=='garbage', keyHolder==BOB. Expected: once the holder has a key (or after a grace window), the previous owner can no longer write it. Verified in test/scratch/Perms.t.sol::test_previousOwnerOverwritesKeyForever.","severity":"low","snippet":"            msg.sender != owner_ && msg.sender != previousOwner && msg.sender != factory","title":"previousOwner retains unlimited setWrappedKey rights for the whole holding period of the new owner and can keep overwriting the holder's key"},{"citation":"resolved","description":"Access x asymmetry (Trust Gap seam 3): settlement of in-flight user value depends on a curator-mutable mapping. _release (confirm and release) ends with house.recordResale, which reverts NotModule when moduleCollection[escrow]==0, and cancel() is blocked once the key is posted. So after ArtHouse.revokeModule(escrow) (line 175-178) a funded listing has no exit: the buyer's ETH sits in the escrow and the piece is owned by the escrow until the curator re-approves; approveModule(escrow, otherCollection) instead sends the 7.5% royalty of pending resales to another collection's artist. ArtifactsAuction.settle() has the same dependency (recordSale after a bid) and would strand the winning bid. README's trust section says no owner power can 'move a buyer's token, or take another party's ledger balance'; this one can lock both indefinitely. Documented as a privileged-power risk: report severity low because the curator is trusted and the state is recoverable by re-approval. Fix options: have the escrow/auction fall back to crediting the seller directly (or let recordResale accept previously approved modules for listings opened before revocation), or make the escrow's module approval irrevocable once set.","line":157,"path":"src/ArtifactsEscrow.sol","reproduction":"State: ALICE owns claimed edition e. 1) ALICE escrow.list(e, 2 ether); BOB escrow.buy{value:2 ether}(e, key); ALICE escrow.postKey(e,'rewrapped'). 2) OWNER house.revokeModule(escrow). 3) BOB escrow.confirm(e) -> reverts ArtHouse.NotModule; BOB escrow.cancel(e) -> reverts KeyAlreadyPosted; after 73h escrow.release(e) -> reverts NotModule. piece.ownerOf(e)==escrow and escrow balance == 2 ether. Expected: payment and piece release do not depend on a revocable approval once the buyer has paid. Verified in test/scratch/Perms.t.sol::test_revokeEscrowModuleFreezesFundedEscrow.","severity":"low","snippet":"        house.recordResale{value: l.price}(l.seller);","title":"Curator's revokeModule/approveModule on the escrow (or auction) after a buyer has paid freezes the buyer's ETH and the piece in the escrow, contradicting the stated owner-power limits"},{"citation":"resolved","description":"Asymmetry between the sale's preconditions and the callback's requirements: openSale checks price, VRF config and factory.locked(), but the assignment path (rawFulfillRandomWords -> factory.assignEdition) additionally requires factory.sale()==address(this). buy() splits and credits the buyer's ETH to artist/house/broker before the callback ever runs. If setModules has not been called yet, every callback reverts ModulesNotSet (Chainlink marks the request fulfilled-with-failure) and purchases wait on the curator calling setModules and retryRequest after 1 day. If setModules was called with a different sale address, the wiring is one-time and every retry also reverts NotSale, so up to 255 buyers have paid and can never be assigned or refunded. Fix: in openSale (and ArtifactsAuction.configure for factory.auction()) require factory.sale()==address(this); optionally also check it in buy().","line":114,"path":"src/ArtifactsSale.sol","reproduction":"State: factory locked, all 256 pieces set. 1) OWNER: configureVRF(coord, keyHash, 7, false); setPrice(1 ether); openSale() -> succeeds although factory.sale()==address(0). 2) OWNER: setModules(0xDEAD, auction, escrow) (one-time). 3) ALICE: buy{value:1 ether}(0) -> succeeds; house.balances(PAYOUT, ETH)==0.7 ether. 4) coordinator fulfils request -> reverts ArtifactsFactory.NotSale. 5) +1 day OWNER retryRequest(pid); fulfil again -> reverts NotSale; purchase(pid).edition==0 forever, no refund. Expected: openSale reverts until the factory names this sale. Verified in test/scratch/Perms.t.sol::test_openSaleWithoutModuleWiringStrandsPurchases.","severity":"low","snippet":"        if (!factory.locked()) revert FactoryNotLocked();","title":"openSale does not verify the factory's module wiring; purchases are accepted and paid out while every VRF callback reverts, with no refund path if setModules named another sale"},{"citation":"resolved","description":"Not defects against the agreed design; recorded so the author and judge see the privileged powers in this area. (a) setSplit has no 'sale closed' guard, unlike setPrices (line 276) and ArtifactsSale.setPrice (line 105): the curator can change the broker/house/artist split while a sale is open, including front-running a specific mint or auction settle to move the artist's 70% to the house. The buyer pays the same exact price, so only artist/broker are affected. (b) ArtifactsSale.configureVRF (line 90-101) is owner-chosen once; a coordinator contract under the curator's control returns request ids and may call rawFulfillRandomWords with chosen words, so edition assignment is only as random as the configured coordinator; buyers must verify the configured address is Chainlink's on Sepolia. (c) The curator alone posts content keys (postContentKey) and can refuse or re-post; documented in README. None of these can mint without payment or take ledger balances.","line":214,"path":"src/ArtHouse.sol","reproduction":"(a) OWNER setSplit(col, 0, 10000, 0) in the block before ALICE's mint(col, 1, 0){value: priceEth}: ALICE pays priceEth, artist is credited 0 and the house 100%. Expected per brief: split fixed per collection when the sale opens (or at least closed-only like setPrices). (b) OWNER configureVRF(attackerCoordinator, ...): openSale passes; attackerCoordinator.requestRandomWords returns ids and later calls rawFulfillRandomWords(id, [chosen]) as msg.sender == vrfCoordinator, selecting the edition.","severity":"info","snippet":"    function setSplit(uint256 collection_, uint16 brokerBps, uint16 houseBps, uint16 artistBps) external onlyOwner {","title":"Trust assumptions on the curator that the brief implies but the code does not bound (setSplit while a sale is open; owner-chosen VRF coordinator decides the 'random' edition)"}],"hash":"3bd9c0e08aa3498aed84378e95c548c102adfd99fc841e1604f710e9824b90e0","nodeId":"52c87505-840d-445e-9420-80c7f2ac3a6f","outcome":"completed","summary":"The review is complete. The findings file is written and validated.\n\n**Outcome.** The permission model holds on its core guarantees: no mint bypasses payment, the escrow's blanket operator right is only ever exercised from the listed seller, initializers are locked on both implementations, and every ledger movement is pull-based with credits written before transfers. I found no critical or high defects in the Access Control, Trust Gap, or Asymmetry areas, so no finding carries a proof file. Six low findings and one info note are recorded in `.imd-findings.json`, each with a concrete call sequence I confirmed in a scratch Foundry test.\n\n**Findings reported (all low unless noted):**\n- **Re-approving an artist overwrites the artist-chosen payout.** The curator can redirect future artist credits and ERC-2981 royalties by calling `approveArtist` again, which contradicts the brief's \"payout address the artist can change\".\n- **A registered piece can never be unregistered or have its hash fixed.** `clearPiece` only drops chunks, so one wrong contentHash permanently blocks `seal` and the collection can never open.\n- **Revoked artists keep write rights on their collections.** The curator-or-artist guard checks identity, not approval, so revocation does not stop `registerPiece`, `uploadChunk`, or `finalizePiece`.\n- **The previous owner's re-wrap right never expires.** After a plain transfer the old owner can overwrite the holder's wrapped key with garbage at any later time.\n- **Revoking the escrow or auction module mid-flight strands buyer funds.** With a key posted, `confirm` and `release` revert and `cancel` is blocked, so the buyer's ETH and the piece sit in the escrow until the curator re-approves. Documented as a privileged-power risk.\n- **`openSale` does not check the factory names this sale.** Buyers are charged and the split is credited while every VRF callback reverts. If `setModules` named another address, the one-time wiring makes the purchases unrecoverable.\n- **Info:** trust assumptions the code does not bound. `setSplit` works while a sale is open, and the owner-chosen VRF coordinator fully controls edition assignment.\n\n**Coverage.** All 85 listed entry points have a row, plus three invariant rows. None are marked unreached.\n\n**Verification notes.** The project's own suite could not run in this sandbox because the via-IR compile of all 67 files exceeds the 1.5 GB memory cap, and a non-via-IR build fails with stack-too-deep. I worked around this by compiling only the sources plus one self-contained scratch test under `test/scratch/`, which passed all five reproduction checks. That directory is gitignored and nothing else in the tree changed.","treeHash":null,"usage":{"cachedInputTokens":1884670,"inputTokens":514,"model":"claude-fable-5-1","outputTokens":52803,"runtime":"claude","turns":33,"wallClockMs":872305}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"1c29c7c6e4cf57eb","findings":[{"citation":"resolved","description":"Seam boundary x invariant (Numerical Gap). The 100 KB cap (MAX_PIECE_BYTES = 102400) is the only bound on piece cost, but _read builds the result with `data = bytes.concat(data, chunk)` once per chunk. Each iteration allocates a fresh buffer holding the whole prefix and the old one is never freed, so memory used is about size*n/2 for n chunks and memory gas is quadratic in that. Chunk size is not bounded below (any data.length >= 1 is accepted by uploadChunk), and the curator or the collection's artist chooses it. _read is used by finalizePiece (a transaction), by pieceBytes, and through pieceBytes by ArtCollection.tokenURI (an eth_call). Measured on this tree for a 100 KB piece: 4 KB chunks need 4.4M gas to finalize; 2 KB chunks 15.0M to finalize and 38.3M for tokenURI; 1 KB chunks 54.8M to finalize (out of gas at 30M) and more than 50M for tokenURI (fails under geth's default 50M RPC gas cap); smaller chunks are worse. Finalizing is irreversible: clearPiece reverts once a piece is finalized. So a piece finalized on a high-gas-limit chain, then sealed and sold, ends up with a tokenURI that standard RPC endpoints cannot serve. A piece that cannot be finalized blocks seal(collection) until the curator clears it and someone re-uploads it, which wastes the roughly 20M gas already spent writing 100 KB of SSTORE2. Fix: allocate `new bytes(p.size)` once and extcodecopy each chunk at its offset (O(size)). With that change the proof passes, and all 47 existing tests still pass.","line":161,"path":"src/ArtStorage.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {ArtStorage} from \"src/ArtStorage.sol\";\nimport {ArtHouse} from \"src/ArtHouse.sol\";\n\ncontract ScratchIMD is ERC20 {\n    constructor() ERC20(\"IMD\", \"IMD\") {}\n}\n\n/// A 100 KB piece (the allowed maximum) uploaded as 100 chunks of 1 KB must be finalizable within a 30M-gas\n/// transaction and its tokenURI readable within a 50M-gas eth_call (geth's default RPC gas cap).\n/// ArtStorage._read re-concatenates the whole prefix for every chunk, so memory and gas grow with chunks^2.\ncontract ReadQuadraticTest is Test {\n    address owner = address(0xA11CE);\n    address artist = address(0xA27);\n    address buyer = address(0xB0B);\n    ArtStorage st;\n    ArtHouse house;\n\n    function setUp() public {\n        st = new ArtStorage(owner);\n        house = new ArtHouse(owner, address(st), address(new ScratchIMD()));\n        vm.startPrank(owner);\n        st.setHouse(address(house));\n        house.approveArtist(artist, \"artist\", artist);\n        house.createCollection(artist, \"Coll\", \"COLL\");\n        vm.stopPrank();\n    }\n\n    function test_hundredKBPieceIn1KBChunksIsFinalizableAndRenderable() public {\n        uint256 chunkSize = 1024;\n        uint256 n = 100; // 100 * 1024 = 102400 = MAX_PIECE_BYTES\n        bytes memory chunk = new bytes(chunkSize);\n        for (uint256 i; i < chunkSize; ++i) chunk[i] = bytes1(uint8(65 + i % 26));\n        bytes memory all;\n        for (uint256 i; i < n; ++i) all = bytes.concat(all, chunk);\n        assertEq(all.length, st.MAX_PIECE_BYTES());\n\n        vm.startPrank(artist);\n        house.registerPiece(1, 1, keccak256(all), \"t\", \"d\");\n        for (uint256 i; i < n; ++i) house.uploadChunk(1, 1, i, chunk);\n        (bool finalized,) = address(house).call{gas: 30_000_000}(abi.encodeCall(ArtHouse.finalizePiece, (1, 1)));\n        vm.stopPrank();\n        assertTrue(finalized, \"finalizePiece of a 100 KB piece runs out of 30M gas\");\n\n        vm.startPrank(owner);\n        house.seal(1);\n        house.setPrices(1, 1 ether, 0);\n        house.openSale(1);\n        vm.stopPrank();\n        vm.deal(buyer, 1 ether);\n        vm.prank(buyer);\n        house.mint{value: 1 ether}(1, 1, address(0));\n\n        address token = house.collection(1).token;\n        (bool rendered,) = token.staticcall{gas: 50_000_000}(abi.encodeWithSignature(\"tokenURI(uint256)\", 1));\n        assertTrue(rendered, \"tokenURI needs more than a 50M-gas eth_call\");\n    }\n}","reproduction":"1) owner: ArtStorage.setHouse(house); ArtHouse.approveArtist(A,\"a\",A); createCollection(A,\"N\",\"S\") -> id 1. 2) A: registerPiece(1,1,keccak256(X),\"t\",\"d\") where X is 102400 bytes; uploadChunk(1,1,i,X[1024*i:1024*(i+1)]) for i=0..99 (every call succeeds, size == MAX_PIECE_BYTES). 3) A: finalizePiece(1,1) with a 30M gas limit. Expected: a piece within the 100 KB cap finalizes at a cost independent of how it was chunked (a linear read costs a few million gas). Actual: out of gas. It needs 54.8M gas, and after finalize/seal/mint, ArtCollection.tokenURI(1) needs more than 50M gas. The proof test asserts finalize within 30M and tokenURI within 50M. It fails now and passes with a linear _read.","severity":"medium","snippet":"        uint256 n = p.chunks.length;\n        for (uint256 i; i < n; ++i) {\n            data = bytes.concat(data, SSTORE2.read(p.chunks[i]));\n        }","title":"ArtStorage._read re-concatenates the whole prefix per chunk: gas grows with chunks^2, so a 100 KB piece in small chunks cannot be finalized or its tokenURI read"}],"hash":"482945d42e2a375d4f3e4a6c66d15fea70b313f1b0eff6dd2f5700b85eda12e3","nodeId":"61931987-2dd1-4c0e-b6cb-ec716a17c713","outcome":"completed","summary":"I found one defect in the math area and wrote it to `.imd-findings.json`, with a coverage row for all 85 entry points plus 3 invariant rows. No source files changed, and the scratch test has been removed.\n\n**Finding 1 (medium): reading a stored piece costs gas that grows with the square of its chunk count.** At `src/ArtStorage.sol:161`, `_read` rebuilds the whole piece with `bytes.concat` once per chunk, and each pass copies everything read so far. The 100 KB size cap doesn't limit the cost, because the curator or the artist can upload a piece in chunks of any size. `_read` is used by `finalizePiece` and, through `pieceBytes`, by `ArtCollection.tokenURI`. Gas measured on this tree for a 100 KB piece:\n\n| Chunk size | `finalizePiece` | `tokenURI` |\n|---|---|---|\n| 4 KB | 4.4M | not measured |\n| 2 KB | 15.0M | 38.3M |\n| 1 KB | 54.8M (out of gas at a 30M limit) | over 50M (fails under the default 50M call limit of standard Ethereum nodes) |\n\n- **Impact:** a finalized piece can't be cleared, so once it is sealed and sold its `tokenURI` may be unreadable through standard nodes. A piece that can't be finalized blocks `seal` until the curator clears it and someone re-uploads it, wasting roughly 20M gas of storage writes.\n- **Fix:** allocate the buffer once at the piece's recorded size and copy each chunk into it at its offset, so the cost is linear in the piece's size.\n- **Proof:** the attached Foundry test fails on the current code. I applied that fix to a copy of the repo outside the working tree; there the test passed and all 47 existing tests still passed.\n\n**Checked and holding:**\n- **Sale split:** the shares always add up to the amount paid, and the house keeps any rounding (`setSplit` adds the parts at full width before comparing to 10,000).\n- **Resale royalty:** 7.5%, rounded down; it is zero only for prices under 14 wei.\n- **Edition assignment:** each of the 255 editions is assigned exactly once, and stale or retried randomness callbacks are ignored.\n- **Auction:** the 5% increment rounds down, so a bid exactly equal to the high bid can win only when the high bid is under 20 wei. The 10-minute extension and end-time boundaries are consistent, and the ETH it holds always equals what it owes.\n- **Escrow:** the exact-price checks, the 72-hour release boundary and refund accounting hold.\n- **Size casts:** the narrowing casts are safe. The 16-bit length in the storage library can't overflow because Ethereum caps contract creation code at 49,152 bytes.\n- **Static analysis leads:** the reentrancy and timestamp warnings involve only trusted contracts or safe comparisons.\n\n**Outside my area, noted but not reported:**\n- A previous owner keeps the right to overwrite a piece's wrapped key indefinitely.\n- When a buyer cancels an escrow sale, the seller's on-chain wrapped key is lost.\n- An escrow listing is not cleared when the piece is transferred away, so it becomes buyable again if the seller gets the piece back.\n- Titles and descriptions supplied by artists go into the token's JSON metadata without escaping.\n\nAll three guides (Math Precision, Boundary, Numerical Gap) were covered and nothing was left unreached.","treeHash":null,"usage":{"cachedInputTokens":1385038,"inputTokens":32,"model":"claude-opus-5-5","outputTokens":31628,"runtime":"claude","turns":24,"wallClockMs":469256}},{"artifacts":[],"attempt":1,"bundleHash":"2638cd195bf58976c102669f42ba67ee189b68496cd86d743a1833564e6bc678","device":"a18a0c6087e1362f","findings":[],"hash":"62b1cf2c2ec7abfe2a312b28f0bdaeb5a63f45dfa4846f65b4c2209af4f42e39","nodeId":"8d837aeb-17db-4b7e-bf3e-cdbcf1cf3470","outcome":"completed","summary":"The work is complete and committed. Build, tests and formatting all pass under the pinned compiler.\n\n**What was built** (7 launch contracts, all under 13 KB runtime, no DELEGATECALL/CALLCODE/SELFDESTRUCT in any launch contract, verified with a scratch replica of the protected opcode scan):\n\n- `ArtStorage($owner)`: SSTORE2 chunk storage for plain SVG pieces; writes only from ArtHouse after the one-time `setHouse`. Next-id and next-index checks, clear until finalized, keccak256 check on finalize, seal needs every piece finalized, 100 KB cap.\n- `ArtHouse($owner, ArtStorage, IMD)`: the ledger. Artists with changeable payout, brokers, sale modules, per-collection basis-point splits (default 15/15/70, house keeps rounding, broker share falls to the house if unapproved or the buyer), per-currency balances with pull withdrawals, house balance paid to the current owner. Also plain SVG collections as EIP-1167 clones of an `ArtCollection` implementation the constructor deploys, with on-chain base64 JSON tokenURI, 7.5% ERC-2981 royalty, exact-ETH `mint` and exact-IMD `mintWithIMD` that rejects fee-on-transfer, plus the submission registry.\n- `ArtifactsPiece()`: locked one-token ERC-721 + ERC-2981 implementation holding ciphertext pointer, traits, commitment and the current owner's wrapped key. Transfers clear the key and let the previous owner re-wrap.\n- `ArtifactsFactory($owner, ArtHouse, ArtifactsPiece)`: upload and lock of all 256 pieces, assignments, X25519 viewing keys, curator-posted wrapped keys, the buyer's claim that clones the piece, and the R8 seal tokenURI reproduced byte for byte from the reference framed pieces.\n- `ArtifactsSale`, `ArtifactsAuction`, `ArtifactsEscrow`: the factory was split so each flow stays small and reviewable. The sale runs the one-price edition sale with Chainlink VRF v2.5 and a Fisher-Yates pool (tested over all 255 editions). The auction has the 5% increment, 10-minute extension, pull refunds and settlement through the ledger. The escrow runs the keyed resale with 72-hour release and buyer cancel until the key is posted.\n\n**Checks run**: `forge build`, `forge test` with the verifier's fuzz seed (46 tests, all passing, including success and failure paths, not-configured states, reentrancy, conservation of funds, VRF stale-request handling), and `forge fmt --check`. Dependencies are vendored as plain files under `lib/` and committed. The tree is clean.\n\n**Assumptions to note** (also in the README): the brief's auction text was truncated after \"proce\", so auction proceeds go through the ledger split. The ARTIFACTS artist, module approvals, the one-time `setModules` wiring and the VRF coordinator, key hash and subscription are owner actions after launch, listed in order under \"After launch\". THE SOURCE's on-chain R9 render and `revealed` flag from PROTOCOL.md are outside the brief I was given and are not implemented. Tests use mocks for IMD and the VRF coordinator and are not a security audit.","treeHash":"8478a53585da22c2188b7b4ffb6bca9befb59f83","usage":{"cachedInputTokens":3908879,"inputTokens":1122,"model":"claude-fable-5-1","outputTokens":105050,"runtime":"claude","turns":59,"wallClockMs":1642418}},{"artifacts":[],"attempt":1,"bundleHash":"2f0fa4aa6041523294d3ee4ccf92d0a7b065fea8413ebf03cc655e55b3737399","device":"d0653dc91b6e2259","findings":[],"hash":"911eb76bdd6fe487d1372c901c6063fe3f07040991de91dac82ec51b70059917","nodeId":"4614f6ea-e97f-4f6b-8ca3-3b5423877633","outcome":"completed","summary":"The manifest is written and verified. Only launch.json was added to the tree.\n\n**What I delivered**\n\nlaunch.json describes the seven accepted application contracts in constructor dependency order, matching the README's launch table and the Deploy script that the accepted tests exercise:\n\n1. ArtStorage takes the owner.\n2. ArtHouse takes the owner, ArtStorage and the existing IMD ERC-20 address from the brief.\n3. ArtifactsPiece takes no arguments and is the locked EIP-1167 implementation.\n4. ArtifactsFactory takes the owner, ArtHouse and ArtifactsPiece.\n5. ArtifactsSale takes the owner, ArtHouse and ArtifactsFactory.\n6. ArtifactsAuction takes the owner, ArtHouse and ArtifactsFactory.\n7. ArtifactsEscrow takes ArtHouse and ArtifactsFactory. It has no owner because it has no administrative power.\n\nEvery privileged owner argument is `$owner`, every contract reference points backward and fills an address parameter, and the IMD address is the static one the brief states. The notes explain the four-way split of the factory and the post-launch owner wiring.\n\n**Checks run**\n\n- Clean build at solc 0.8.26 with via-IR. Every runtime is under the EIP-170 limit, the largest being ArtifactsPiece.\n- Full test suite passed with the verifier's fuzz seed: 46 tests, 0 failures, including the deploy wiring test and the VRF assignment fuzz test.\n- A script compared the manifest against the compiled constructor ABIs: the manifest has exactly the three schema keys, every name is a unique identifier under 32 characters, every argument count matches its constructor, every reference resolves to an earlier contract, and the notes are under the 4,000-character limit.\n- Git status shows launch.json as the only new or changed file.\n\nOne note for the reviewer: the task text says Sepolia while the pinned launch policy says Ethereum mainnet. The chain is not a manifest field, so the manifest is correct either way and the pinned policy decides the chain.","treeHash":"0d2ac6fb98a501ce83a3e20d5174179af5118fe0","usage":{"cachedInputTokens":288030,"inputTokens":194,"model":"claude-fable-5-1","outputTokens":4831,"runtime":"claude","turns":7,"wallClockMs":309993}},{"artifacts":[],"attempt":1,"bundleHash":"2f16ac70e6f45ebe6a701429a2af75dd538bc6083188e828c1d325ff61b06678","device":"bc1e515ca5c78ac4","findings":[{"description":"ArtifactsPiece._update deletes wrappedKey on every transfer to a new holder. In the escrow the piece moves seller -> escrow on buy() (key deleted) and escrow -> seller on cancel(). Nothing restores the key, so a buyer who pays and then cancels before the key is posted leaves the seller with an empty wrappedKey and previousOwner = escrow. The wrapped key is not emitted in any event either. Any buyer can do this to any listed seller and only loses gas, since the refund is complete. A seller who never kept the plaintext content key off-chain can no longer open their piece, and the curator cannot re-post because postContentKey reverts AlreadyClaimed after the claim.","line":80,"path":"src/ArtifactsPiece.sol","reproduction":"Claim edition e for ALICE (wrappedKey = K). ALICE escrow.list(e, 1 ether); BOB escrow.buy{value:1 ether}(e, vk); BOB escrow.cancel(e). Expected: ownerOf(e)==ALICE and wrappedKey()==K. Actual: ownerOf(e)==ALICE and wrappedKey()==0x (empty).","severity":"medium","title":"Buyer cancelling a resale permanently wipes the seller's wrapped content key"},{"description":"setWrappedKey lets previousOwner post at any time, including after a valid key was already posted for the current owner. Only a transfer resets previousOwner. After a plain transfer A -> B, A can post the correct key and later replace it with garbage, as often as A likes, while B holds the piece. B can only repair it if B kept the plaintext content key.","line":61,"path":"src/ArtifactsPiece.sol","reproduction":"ALICE owns piece e and calls transferFrom(ALICE, BOB, e). ALICE setWrappedKey('good-for-bob'), then ALICE setWrappedKey('garbage'). Expected: the second call reverts once the current owner has a key. Actual: it succeeds and wrappedKey()=='garbage'.","severity":"low","title":"Previous owner can overwrite the current owner's wrapped key at any time"},{"description":"minimumBid() = highBid + highBid*500/10000, rounded down. When highBid < 20 wei the increment is 0, so a bid equal to the high bid replaces it. This breaks the rule that each bid must beat the current high bid by at least 5%. At any size the threshold is up to 1 wei below an exact 5%. configure() accepts any reserve above 0. Rounding up (ceil) or requiring an increment of at least 1 wei fixes it.","line":69,"path":"src/ArtifactsAuction.sol","reproduction":"configure(1 wei, now+1 day); ALICE bid{value:19}; BOB bid{value:19}. Expected: revert BelowIncrement. Actual: succeeds and BOB becomes highBidder, with ALICE refunded 19.","severity":"low","title":"Minimum bid increment rounds down; with tiny bids an equal bid can outbid"},{"description":"The title and description given to registerPiece by the artist or curator are concatenated raw into the JSON. A quote, backslash or control character produces invalid JSON or lets extra fields be injected, such as an alternative image. The text is fixed forever once the piece is sold. Either escape it or reject such characters in registerPiece.","line":72,"path":"src/ArtCollection.sol","reproduction":"registerPiece(col, 1, h, 'A\",\"image\":\"data:evil', 'd'), then upload, finalize, seal and mint. Decoding tokenURI(1) gives JSON with a duplicate 'image' key, or JSON that fails to parse.","severity":"low","title":"ArtCollection tokenURI JSON is not escaped"}],"hash":"c9c52389921f55874052a096a9441e7a8e116e0e433297534785e16960ae2c0c","nodeId":"35435f62-f8e4-46e6-a9da-f54c04ebe0b7","outcome":"completed","summary":"I added 30 new tests and 4 invariant suites in two new files. The full suite passes with the verifier's fuzz seed: 76 tests, 0 failing. I found four defects in the contracts and wrote them to `.imd-findings.json` instead of writing tests that treat them as correct.\n\n**`test/Invariants.t.sol`** has a random-call-sequence suite for each contract that holds value:\n- **ArtHouse:** the handler mints in ETH and IMD, records module sales and resales, withdraws, and changes splits, brokers, the artist's payout and prices. After every sequence, the contract's ETH and IMD balances must equal the sum of everyone's credits, and must equal what came in minus what went out. Withdrawals pay exactly what is owed, and there is one token per paid mint, never more than the registered pieces. Each recorded sale is also checked for the right broker, artist and house shares, with the house taking the rounding.\n- **Auction (THE SOURCE):** the auction always holds exactly the pull refunds plus the open high bid. The end time never moves earlier, a bid always leaves at least 10 minutes, a bid below the minimum changes nothing, and settling assigns THE SOURCE to the winner and pays the ledger.\n- **Resale escrow:** it holds exactly the refunds plus the funded listings. A funded piece is always held by the escrow and an unfunded one never is. A release hands the buyer the piece and the re-wrapped key. The ledger's balance stays matched to what it owes.\n- **Edition sale:** no edition is assigned twice, the assigned buyer is correct, and a repeated VRF callback changes nothing. The sale contract keeps no ETH; everything goes to the ledger.\n\n**`test/FailurePaths.t.sol`** covers the failure paths:\n- **ArtHouse:** wrong price, closed sale, unknown or already-sold piece, IMD with the wrong price or not enough allowance, and module-only collections. It also covers who may upload, finalize, clear and seal; splits that don't sum to 100%; revoked modules; and the house balance following a change of owner.\n- **Edition sale:** VRF set only once, and the price changeable only while the sale is closed. The 256th purchase is refused as sold out, and retries follow their rules. The VRF callback fits within its 250,000 gas limit.\n- **Claim and piece contract:** the order of the claim steps, and re-initialising a piece or the implementation is refused. Only the right people can post keys, and the 7.5% royalty goes to the current payout address.\n- **Auction:** a bidder who rejects ETH blocks only their own refund, and a bid at the exact end time is refused. Settling while the auction is not an approved module is refused.\n- **Escrow:** steps out of order are refused, and the refund can't be re-entered.\n\n**Defects reported in `.imd-findings.json`:**\n1. **Medium:** a buyer who cancels a resale permanently erases the seller's wrapped key on their own piece. The curator can't re-post it once the piece is claimed, and any buyer can do this to any listed seller for just gas.\n2. **Low:** after a plain transfer, the previous owner can keep overwriting the new owner's key with garbage.\n3. **Low:** the minimum bid rounds down, so with a reserve of a few wei an equal bid can take the lead (two bids of 19 wei).\n4. **Low:** the tokenURI JSON for later collections doesn't escape titles or descriptions, so a quote in a title breaks the JSON or injects fields.\n\nI reproduced the first three in throwaway tests, which fail on the current code as expected. The fourth comes from reading the code and I did not run it. None of the four carry a proof file, since none is critical or high.\n\nThe VRF and IMD token are local stand-ins. Nothing has been run against the live Sepolia coordinator or the real IMD token, so those checks are still owed.","treeHash":"82f3c9f1b88abdcd95bdfafc74301cdbe21a948f","usage":{"cachedInputTokens":1175419,"inputTokens":30,"model":"claude-opus-5-5","outputTokens":43079,"runtime":"claude","turns":19,"wallClockMs":836159}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"d5e40d018a0986f3","findings":[{"citation":"resolved","description":"A listing is keyed only by pieceId and seller; nothing ties it to the ownership epoch in which it was made. ArtifactsPiece._update does not notify the escrow, and buy() only checks that the current owner equals l.seller. If the seller moves the piece away by a plain ERC-721 transfer (to a cold wallet, an OTC buyer who later sells it back, a lending or display contract) and later owns it again, the old listing silently becomes executable again at the old price. Any watcher can then take the piece for the stale price, which may be far below what the owner would now accept. Flow gap (execution x first principles): every step is individually valid, but the end state lets a third party buy a piece its owner no longer offers. Fix: bind the listing to an ownership epoch (for example a transfer counter on the piece, checked in buy()), or clear the listing whenever the piece is transferred away from the listed seller.","line":71,"path":"src/ArtifactsEscrow.sol","reproduction":"Alice owns claimed edition e. 1) Alice: escrow.list(e, 1 ether). 2) Alice: piece.transferFrom(Alice, Bob, e) (plain transfer, e.g. to her other wallet). 3) Bob: piece.transferFrom(Bob, Alice, e). 4) Carol: escrow.buy{value: 1 ether}(e, key). Expected: step 4 reverts because the listing died with the transfer in step 2. Actual: buy() succeeds, the piece moves into escrow with buyer Carol at the stale 1 ETH price; Alice can only avoid it by remembering to cancelListing before step 4. Reproduced in test/scratch/Review.t.sol::test_staleListingRevivesWhenPieceReturnsToSeller (passes on current code, demonstrating the behaviour).","severity":"low","snippet":"        l.seller = msg.sender;\n        l.price = price;","title":"Escrow listing is not invalidated by a plain transfer and revives at the stale price when the piece returns to the seller"},{"citation":"resolved","description":"buy() splits and credits the payment in the ArtHouse ledger immediately (withdrawable by artist, house and broker) while delivery happens only in the VRF callback via factory.assignEdition, which reverts unless factory.sale() == address(this). openSale checks price, VRF config and the lock but not the factory wiring. ArtifactsFactory.setModules is one-shot, so if the sale opens before setModules, or setModules names any other sale address, every purchase is paid for, credited to the house parties, and never assigned; the sale has no refund path and retryRequest cannot help. Wrong-state execution across ArtifactsSale, ArtifactsFactory and ArtHouse; the harm needs a curator misconfiguration, the defect is the missing state check. Fix: in openSale (and/or buy) require factory.sale() == address(this); optionally give buyers a refund path for purchases that cannot be assigned.","line":111,"path":"src/ArtifactsSale.sol","reproduction":"Deploy, approve modules in ArtHouse, upload and lock all 256 pieces, configureVRF, setPrice(1 ether), openSale() WITHOUT calling factory.setModules (or after setModules(0xDEAD, auction, escrow)). Alice: sale.buy{value: 1 ether}(address(0)) succeeds; house.balances(artistPayout, ETH) == 0.7 ether and house.balances(house, ETH) == 0.3 ether are immediately withdrawable. Coordinator fulfils the request: rawFulfillRandomWords reverts inside factory.assignEdition (ModulesNotSet / NotSale); the real coordinator swallows the failed callback and marks the request fulfilled. purchase(1).edition stays 0 and assignedTo(1) stays zero. After setModules(0xDEAD, ...) and 1 day, retryRequest(1) requests again and the new callback fails the same way, permanently. Expected: openSale (or buy) reverts until factory.sale() == address(this). Reproduced in test/scratch/Review.t.sol::test_openSaleAndBuyWithoutFactoryWiringStrandsPurchase.","severity":"low","snippet":"    function openSale() external onlyOwner {\n        if (price == 0) revert NoPrice();\n        if (vrfCoordinator == address(0)) revert VRFNotConfigured();\n        if (!factory.locked()) revert FactoryNotLocked();","title":"ArtifactsSale.openSale does not check that the factory's sale module is this contract, so paid purchases can be accepted whose editions can never be assigned"},{"citation":"resolved","description":"The brief lets the previous owner post a re-wrapped key for the new owner after a plain transfer. The implementation grants previousOwner that right for as long as no further transfer happens, with no one-shot or expiry and no way for the current owner to revoke it. A previous owner can therefore replace a correct key with garbage at any later time, wiping the only on-chain record the new owner (or a future buyer reading the contract) relies on. The current owner can restore it only if they already extracted and saved the content key off-chain. Fix: clear previousOwner once a key has been posted for the current holder (set previousOwner = address(0) in setWrappedKey), or let the owner revoke it.","line":62,"path":"src/ArtifactsPiece.sol","reproduction":"Alice owns claimed edition e. 1) Alice: piece.transferFrom(Alice, Bob, e). 2) Bob: piece.setWrappedKey(\"bob-good\") (or Alice posts a correct re-wrapped key). 3) One year later, Alice: piece.setWrappedKey(\"garbage\"). Expected: step 3 reverts with NotKeyPoster since the current holder already has a posted key. Actual: wrappedKey() == \"garbage\", keyHolder stays Bob, ownerOf(e) == Bob. Reproduced in test/scratch/Review.t.sol::test_previousOwnerCanOverwriteKeyAfterNewOwnerSetTheirOwn.","severity":"low","snippet":"        if (\n            msg.sender != owner_ && msg.sender != previousOwner && msg.sender != factory\n                && msg.sender != IArtifactsFactory(factory).escrow()\n        ) revert NotKeyPoster();","title":"previousOwner keeps the power to overwrite the piece's wrapped key indefinitely, after the new owner has set their own"},{"citation":"resolved","description":"highBid * 500 / 10_000 truncates to 0 when highBid < 20 wei, so minimumBid() == highBid and a bid equal to the current high bid is accepted and replaces the leader, contradicting 'each bid must beat the current high bid by at least 5%'. Only reachable with a dust reserve, so no practical loss; noted for completeness. Fix: also require msg.value > highBid, or round the increment up.","line":65,"path":"src/ArtifactsAuction.sol","reproduction":"Owner: auction.configure(1, now + 1 hours). Alice: bid{value: 10}(). Bob: bid{value: 10}(). Expected: Bob's bid reverts BelowIncrement. Actual: highBidder() == Bob. Reproduced in test/scratch/Review.t.sol::test_auctionIncrementRoundsToZeroOnDust.","severity":"info","snippet":"        return highBid + highBid * MIN_INCREMENT_BPS / 10_000;","title":"Auction 5% increment rounds to zero for a high bid below 20 wei, so an equal bid displaces the leader"}],"hash":"cdad8f2195cd77a24db95b1e3e07d7b9d3fff8525a914d8e9b254a927a1ee465","nodeId":"e2b5c68c-8b8e-4da1-badb-bbe9d565c8a3","outcome":"completed","summary":"The review is complete and the findings file is written and validated. No source files were changed.\n\n**Outcome:** the economics, invariants and flow gaps hold up well. I found no critical, high or medium defects, so no proof files were attached. Four lower-severity issues are recorded in `.imd-findings.json`, each reproduced by a scratch Foundry test that passes on the current code to demonstrate the behaviour.\n\nFindings:\n- **Low, escrow stale listing.** A listing is not invalidated by a plain transfer. If the seller moves the piece away and later gets it back, the old listing becomes executable again at the old price, and any watcher can buy at that stale price.\n- **Low, sale opens without factory wiring check.** `ArtifactsSale.openSale` does not require the factory's sale module to be this contract. Purchases are paid and credited in the ledger immediately, but the VRF callback can never assign an edition, and there is no refund path. Harm needs a curator misconfiguration. The missing state check is the defect.\n- **Low, previous owner's key power never expires.** After a plain transfer the previous owner can overwrite the wrapped key at any later time, even after the new owner posted their own.\n- **Info, auction increment rounds to zero below 20 wei.** An equal bid then displaces the leader. Dust only.\n\nCoverage: all 85 listed entry points have a row, plus five invariant rows. Verified as holding: full-value conservation in the ArtHouse ledger, escrow and auction balances, the Fisher-Yates edition assignment with no repeats or skips, the 15/15/70 split with house rounding, exact-price enforcement in ETH and IMD, the fee-on-transfer rejection, pull-based withdrawals and refunds, and the reentrancy ordering. Trust assumptions noted but not reported as defects: one-time VRF and module configuration, the curator posting content keys, and the unverifiable wrapped key on resale.\n\nNot reached: the seal SVG rendering and JSON escaping of trait strings, which are outside my area.","treeHash":null,"usage":{"cachedInputTokens":1735199,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":51057,"runtime":"claude","turns":26,"wallClockMs":740323}},{"artifacts":[],"attempt":1,"bundleHash":"6e17db7889f6bd78855366b44eb2a54880816c5a05b49918949e39af07b56a5b","device":"feba2a869621cab2","findings":[],"hash":"ecc263691e21d092548b339e9687fb1966bda78a1d49637db5241bac17c30a09","nodeId":"4614f6ea-e97f-4f6b-8ca3-3b5423877633","outcome":"completed","summary":"The manifest is verified against the revised sources and only one stale figure in the notes needed correcting.\n\n**What I checked**\n\n- All seven manifest entries match the constructors in the accepted tree: ArtStorage($owner), ArtHouse($owner, ArtStorage, IMD), ArtifactsPiece(), ArtifactsFactory($owner, ArtHouse, ArtifactsPiece), ArtifactsSale and ArtifactsAuction ($owner, ArtHouse, ArtifactsFactory), and ArtifactsEscrow(ArtHouse, ArtifactsFactory). Order is dependency order and every reference points backward.\n- The IMD address in the manifest is the correct EIP-55 checksum of the address the brief gives.\n- The file has exactly the keys kind, contracts and notes. Contract names are unique and under 32 characters. Notes are well under the limit.\n- Every post-launch function the notes name still exists in the revised sources, including setHouse, approveModule, setModules, configureVRF, setPiece, lock, setPrice, openSale and configure.\n- All eight runtimes are under the EIP-170 limit. The largest is ArtifactsPiece.\n- The build succeeds and the full suite passes with the verifier's fuzz seed.\n\n| Check | Result |\n|---|---|\n| Tests | 61 passed, 0 failed |\n| Largest runtime | ArtifactsPiece, 12,395 bytes |\n\n**What changed**\n\nThe other contributor's later revision grew ArtifactsPiece, so the notes' claim of a 12.2 KB largest runtime was stale. I replaced it with the measured figure. Nothing else in the manifest changed, and git status shows launch.json as the only modified file.","treeHash":"1cf634c147ee577cadf9983de0e55b108e137181","usage":{"cachedInputTokens":267921,"inputTokens":194,"model":"claude-fable-5-1","outputTokens":4389,"runtime":"claude","turns":12,"wallClockMs":407963}}],"verification":[{"checks":[{"durationMs":88658,"exitCode":0,"name":"build","output":"Compiling 68 files with Solc 0.8.26\nSolc 0.8.26 finished in 88.56s\nCompiler run successful!\n","passed":true},{"durationMs":177,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/Deploy.t.sol:DeployTest\n[PASS] test_constructorsRejectZeroReferences() (gas: 4160)\n[PASS] test_deployWiresOwnersAndReferencesWithoutPostCalls() (gas: 27295585)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.87ms (1.46ms CPU time)\n\nRan 7 tests for test/Artifacts.t.sol:ArtifactsAuctionTest\n[PASS] test_bidsMustBeatReserveAndFivePercentIncrement() (gas: 532721)\n[PASS] test_configureNeedsLockedFactory() (gas: 45450)\n[PASS] test_configureRules() (gas: 303622)\n[PASS] test_dustBidsStillNeedAStrictIncrement() (gas: 287290)\n[PASS] test_lateBidExtendsEndByTenMinutes() (gas: 297834)\n[PASS] test_settleAssignsSourceAndPaysLedger() (gas: 1208777)\n[PASS] test_settleWhileRevokedHoldsProceedsUntilForwarded() (gas: 621868)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 15.82ms (2.71ms CPU time)\n\nRan 2 tests for test/Artifacts.t.sol:ArtifactsWiringTest\n[PASS] test_correctWiringOpens() (gas: 271001)\n[PASS] test_openSaleAndConfigureNeedTheFactoryToNameThem() (gas: 267518)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 21.26ms (1.08ms CPU time)\n\nRan 10 tests for test/Artifacts.t.sol:ArtifactsEscrowTest\n[PASS] test_buyTakesExactPriceAndHoldsPiece() (gas: 800600)\n[PASS] test_buyerCancelsUntilKeyPostedAndIsRefundedByPull() (gas: 717813)\n[PASS] test_cancelRestoresSellerWrappedKey() (gas: 981218)\n[PASS] test_confirmReleasesPaymentThroughLedgerWithRoyalty() (gas: 885058)\n[PASS] test_escrowOperatorRightsAreLimitedToTheEscrow() (gas: 89516)\n[PASS] test_listingRules() (gas: 385028)\n[PASS] test_previousOwnerCannotOverwriteAPostedKey() (gas: 422773)\n[PASS] test_releaseAfter72Hours() (gas: 667555)\n[PASS] test_releaseWhenModuleRevokedHoldsPriceForSellerAndStillDelivers() (gas: 707626)\n[PASS] test_royaltyInfoSurvivesFactoryModuleRevocation() (gas: 111578)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 33.83ms (3.81ms CPU time)\n\nRan 3 tests for test/Artifacts.t.sol:ArtifactsClaimTest\n[PASS] test_claimFlowDeploysOneTokenPieceContract() (gas: 2000951)\n[PASS] test_plainTransferClearsKeyAndPreviousOwnerRewraps() (gas: 1434031)\n[PASS] test_sealTokenURIMatchesReferenceFrame() (gas: 4590716)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 39.18ms (8.87ms CPU time)\n\nRan 27 tests for test/ArtHouse.t.sol:ArtHouseTest\n[PASS] testFuzz_splitConservesEveryWei(uint96,uint16,uint16) (runs: 256, μ: 812122, ~: 816448)\nLogs:\n  Bound result 14558981172\n  Bound result 5821\n  Bound result 6\n\n[PASS] test_artistChangesPayoutOnlyWhenApproved() (gas: 164347)\n[PASS] test_brokerShareGoesToHouseWhenUnapprovedOrBuyer() (gas: 1680985)\n[PASS] test_chunksAboveOneSstore2ContractAreRejected() (gas: 5959378)\n[PASS] test_collectionImplementationAndCloneCannotBeReinitializedOrMintedDirectly() (gas: 99597)\n[PASS] test_curatorRepairsOrDropsAnUnfinalizedPiece() (gas: 1028357)\n[PASS] test_customSplitAndRoundingToHouse() (gas: 835865)\n[PASS] test_finalizeChecksContentHashAndClearResets() (gas: 927828)\n[PASS] test_mintSplitsAndEveryPartyWithdraws() (gas: 1467417)\n[PASS] test_mintWithIMDPullsExactPrice() (gas: 1526427)\n[PASS] test_mintWithIMDRejectsFeeOnTransfer() (gas: 1764432)\n[PASS] test_onlyCuratorManagesRoles() (gas: 88212)\n[PASS] test_openSaleNeedsSealAndPrice() (gas: 729120)\n[PASS] test_ownershipIsTwoStepAndRenounceDisabled() (gas: 133497)\n[PASS] test_pieceIdsAndChunkIndexesMustBeNext() (gas: 485383)\n[PASS] test_piecesOver100KBRejected() (gas: 21814734)\n[PASS] test_readCostIsLinearIn100KBOfSmallChunks() (gas: 117093665)\n[PASS] test_reapprovingAnArtistKeepsItsPayout() (gas: 200039)\n[PASS] test_recordSaleOnlyFromApprovedModules() (gas: 151713)\n[PASS] test_revokedArtistLosesWriteAccessUntilReapproved() (gas: 575134)\n[PASS] test_sealNeedsEveryPieceFinalized() (gas: 566500)\n[PASS] test_setSplitOnlyWhileSvgSaleClosed() (gas: 661384)\n[PASS] test_storageWritesOnlyFromHouseAndSetHouseOnce() (gas: 90745)\n[PASS] test_submissionsAcceptedOrRejectedOnce() (gas: 471049)\n[PASS] test_tokenURIEscapesTitleAndDescription() (gas: 1014541)\n[PASS] test_tokenURIIsOnChainBase64JSONWithSVG() (gas: 1090510)\n[PASS] test_withdrawIsNonReentrantAndRevertsForRejectingReceivers() (gas: 2157953)\nSuite result: ok. 27 passed; 0 failed; 0 skipped; finished in 68.59ms (136.91ms CPU time)\n\nRan 3 tests for test/Artifacts.t.sol:ArtifactsRegistryTest\n[PASS] test_assignmentOnlyByItsModuleAfterLock() (gas: 58986981)\n[PASS] test_modulesSetOnceAndNotConfiguredStateReverts() (gas: 265903)\n[PASS] test_setPieceValidationAndLockNeedsAll256() (gas: 59210106)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 76.67ms (32.36ms CPU time)\n\nRan 7 tests for test/Artifacts.t.sol:ArtifactsSaleTest\n[PASS] testFuzz_randomWordAlwaysLandsOnUnassignedEdition(uint256[8]) (runs: 256, μ: 3124439, ~: 3127076)\n[PASS] test_allEditionsAssignedExactlyOnceThenSoldOut() (gas: 92388470)\n[PASS] test_buyPaysExactPriceThroughLedgerAndRequestsVRF() (gas: 733103)\n[PASS] test_fulfilmentAssignsAndIgnoresStaleOrForeignRequests() (gas: 806188)\n[PASS] test_openSaleNeedsLockedFactory() (gas: 192369)\n[PASS] test_retryAfterMissingCallbackMakesOldRequestStale() (gas: 935730)\n[PASS] test_saleStartsClosedAndNeedsPriceVrfAndLock() (gas: 533089)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 91.54ms (116.26ms CPU time)\n\nRan 8 test suites in 92.61ms (348.77ms CPU time): 61 tests passed, 0 failed, 0 skipped (61 total tests)\n","passed":true},{"durationMs":74,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"ArtCollection.approve(address,uint256)\",\"ArtCollection.initialize(address,address,uint256,string,string)\",\"ArtCollection.mint(address,uint256)\",\"ArtCollection.safeTransferFrom(address,address,uint256)\",\"ArtCollection.safeTransferFrom(address,address,uint256,bytes)\",\"ArtCollection.setApprovalForAll(address,bool)\",\"ArtCollection.transferFrom(address,address,uint256)\",\"ArtHouse.acceptOwnership()\",\"ArtHouse.acceptSubmission(uint256)\",\"ArtHouse.approveArtist(address,string,address)\",\"ArtHouse.approveBroker(address)\",\"ArtHouse.approveModule(address,uint256)\",\"ArtHouse.clearPiece(uint256,uint256)\",\"ArtHouse.closeSale(uint256)\",\"ArtHouse.createCollection(address,string,string)\",\"ArtHouse.createModuleCollection(address)\",\"ArtHouse.dropLastPiece(uint256)\",\"ArtHouse.finalizePiece(uint256,uint256)\",\"ArtHouse.mint(uint256,uint256,address)\",\"ArtHouse.mintWithIMD(uint256,uint256,address,uint256)\",\"ArtHouse.openSale(uint256)\",\"ArtHouse.recordResale(address)\",\"ArtHouse.recordSale(address,address)\",\"ArtHouse.redescribePiece(uint256,uint256,bytes32,string,string)\",\"ArtHouse.registerPiece(uint256,uint256,bytes32,string,string)\",\"ArtHouse.rejectSubmission(uint256)\",\"ArtHouse.revokeArtist(address)\",\"ArtHouse.revokeBroker(address)\",\"ArtHouse.revokeModule(address)\",\"ArtHouse.seal(uint256)\",\"ArtHouse.setPayout(address)\",\"ArtHouse.setPrices(uint256,uint128,uint128)\",\"ArtHouse.setSplit(uint256,uint16,uint16,uint16)\",\"ArtHouse.submit(bytes32,string)\",\"ArtHouse.transferOwnership(address)\",\"ArtHouse.uploadChunk(uint256,uint256,uint256,bytes)\",\"ArtHouse.withdraw(address)\",\"ArtHouse.withdrawHouse(address)\",\"ArtStorage.acceptOwnership()\",\"ArtStorage.clearPiece(uint256,uint256)\",\"ArtStorage.dropLastPiece(uint256)\",\"ArtStorage.finalizePiece(uint256,uint256)\",\"ArtStorage.redescribePiece(uint256,uint256,bytes32,string,string)\",\"ArtStorage.registerPiece(uint256,uint256,bytes32,string,string)\",\"ArtStorage.seal(uint256)\",\"ArtStorage.setHouse(address)\",\"ArtStorage.transferOwnership(address)\",\"ArtStorage.uploadChunk(uint256,uint256,uint256,bytes)\",\"ArtifactsAuction.acceptOwnership()\",\"ArtifactsAuction.bid()\",\"ArtifactsAuction.configure(uint256,uint64)\",\"ArtifactsAuction.forwardProceeds()\",\"ArtifactsAuction.settle()\",\"ArtifactsAuction.transferOwnership(address)\",\"ArtifactsAuction.withdrawRefund()\",\"ArtifactsEscrow.buy(uint16,bytes32)\",\"ArtifactsEscrow.cancel(uint16)\",\"ArtifactsEscrow.cancelListing(uint16)\",\"ArtifactsEscrow.confirm(uint16)\",\"ArtifactsEscrow.list(uint16,uint256)\",\"ArtifactsEscrow.postKey(uint16,bytes)\",\"ArtifactsEscrow.release(uint16)\",\"ArtifactsEscrow.withdrawRefund()\",\"ArtifactsFactory.acceptOwnership()\",\"ArtifactsFactory.assignEdition(uint16,address)\",\"ArtifactsFactory.assignSource(address)\",\"ArtifactsFactory.claim(uint16)\",\"ArtifactsFactory.lock()\",\"ArtifactsFactory.postContentKey(uint16,bytes)\",\"ArtifactsFactory.registerViewingKey(bytes32)\",\"ArtifactsFactory.registerViewingKeyFor(address,bytes32)\",\"ArtifactsFactory.setModules(address,address,address)\",\"ArtifactsFactory.setPiece(uint16,(string,string,uint8,string),bytes,bytes32)\",\"ArtifactsFactory.transferOwnership(address)\",\"ArtifactsPiece.approve(address,uint256)\",\"ArtifactsPiece.initialize(uint16,address,bytes)\",\"ArtifactsPiece.safeTransferFrom(address,address,uint256)\",\"ArtifactsPiece.safeTransferFrom(address,address,uint256,bytes)\",\"ArtifactsPiece.setApprovalForAll(address,bool)\",\"ArtifactsPiece.setWrappedKey(bytes)\",\"ArtifactsPiece.transferFrom(address,address,uint256)\",\"ArtifactsSale.acceptOwnership()\",\"ArtifactsSale.buy(address)\",\"ArtifactsSale.closeSale()\",\"ArtifactsSale.configureVRF(address,bytes32,uint256,bool)\",\"ArtifactsSale.openSale()\",\"ArtifactsSale.rawFulfillRandomWords(uint256,uint256[])\",\"ArtifactsSale.retryRequest(uint256)\",\"ArtifactsSale.setPrice(uint256)\",\"ArtifactsSale.transferOwnership(address)\"],\"files\":{\".gitignore\":4,\"README.md\":202,\"foundry.toml\":18,\"remappings.txt\":2,\"script/Deploy.s.sol\":50,\"src/ArtCollection.sol\":96,\"src/ArtHouse.sol\":479,\"src/ArtStorage.sol\":208,\"src/ArtifactsAuction.sol\":126,\"src/ArtifactsEscrow.sol\":186,\"src/ArtifactsFactory.sol\":219,\"src/ArtifactsPiece.sol\":128,\"src/ArtifactsSale.sol\":199,\"src/HouseOwnable.sol\":18,\"src/interfaces/IArtifactsFactory.sol\":37,\"src/libraries/ArtifactsSeal.sol\":105,\"src/libraries/JsonEscape.sol\":40,\"src/libraries/SSTORE2.sol\":55,\"test/ArtHouse.t.sol\":637,\"test/Artifacts.t.sol\":860,\"test/Deploy.t.sol\":52,\"test/Fixture.sol\":109,\"test/mocks/B64.sol\":46,\"test/mocks/Mocks.sol\":73},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":8310,"exitCode":0,"name":"slither","output":"[high/medium] reentrancy-eth at src/ArtifactsSale.sol:131: Reentrancy in ArtifactsSale.buy(address) (src/ArtifactsSale.sol#131-142):\n[medium/medium] uninitialized-local at src/libraries/JsonEscape.sol:20: JsonEscape.escape(string).o (src/libraries/JsonEscape.sol#20) is a local variable never initialized\n[medium/medium] uninitialized-local at src/ArtStorage.sol:202: ArtStorage._read(ArtStorage.Piece).offset (src/ArtStorage.sol#202) is a local variable never initialized\n[medium/medium] uninitialized-local at src/libraries/JsonEscape.sol:12: JsonEscape.escape(string).extra (src/libraries/JsonEscape.sol#12) is a local variable never initialized\n[medium/medium] unused-return at src/ArtHouse.sol:475: ArtHouse._requireFinalized(uint256,uint256) (src/ArtHouse.sol#475-478) ignores return value by (None,None,None,None,None,finalized) = artStorage.pieceInfo(id,pieceId) (src/ArtHouse.sol#476)\n[medium/medium] unused-return at src/ArtCollection.sol:72: ArtCollection.tokenURI(uint256) (src/ArtCollection.sol#72-86) ignores return value by (None,title,description,None,None,None) = artStorage.pieceInfo(collectionId,tokenId) (src/ArtCollection.sol#74)\n[low/medium] missing-zero-check at src/ArtCollection.sol:38: ArtCollection.initialize(address,address,uint256,string,string).house_ (src/ArtCollection.sol#38) lacks a zero-check on :\n[low/medium] missing-zero-check at src/ArtifactsPiece.sol:44: ArtifactsPiece.initialize(uint16,address,bytes).owner_ (src/ArtifactsPiece.sol#44) lacks a zero-check on :\n[low/medium] missing-zero-check at src/ArtifactsPiece.sol:49: ArtifactsPiece.initialize(uint16,address,bytes).ciphertext_ (src/ArtifactsPiece.sol#49) lacks a zero-check on :\n[low/medium] reentrancy-benign at src/ArtifactsSale.sol:131: Reentrancy in ArtifactsSale.buy(address) (src/ArtifactsSale.sol#131-142):\n[low/medium] reentrancy-benign at src/ArtifactsSale.sol:176: Reentrancy in ArtifactsSale._requestRandomness(uint256) (src/ArtifactsSale.sol#176-193):\n[low/medium] reentrancy-events at src/ArtifactsSale.sol:176: Reentrancy in ArtifactsSale._requestRandomness(uint256) (src/ArtifactsSale.sol#176-193):\n[low/medium] reentrancy-events at src/ArtHouse.sol:195: Reentrancy in ArtHouse.createCollection(address,string,string) (src/ArtHouse.sol#195-207):\n[low/medium] timestamp at src/ArtifactsAuction.sol:60: ArtifactsAuction.configure(uint256,uint64) (src/ArtifactsAuction.sol#60-69) uses timestamp for comparisons\n[low/medium] timestamp at src/ArtifactsAuction.sol:102: ArtifactsAuction.settle() (src/ArtifactsAuction.sol#102-116) uses timestamp for comparisons\n[low/medium] timestamp at src/ArtifactsSale.sol:163: ArtifactsSale.retryRequest(uint256) (src/ArtifactsSale.sol#163-170) uses timestamp for comparisons\n[low/medium] timestamp at src/ArtifactsAuction.sol:77: ArtifactsAuction.bid() (src/ArtifactsAuction.sol#77-88) uses timestamp for comparisons\n[low/medium] timestamp at src/ArtifactsEscrow.sol:147: ArtifactsEscrow.release(uint16) (src/ArtifactsEscrow.sol#147-153) uses timestamp for comparisons","passed":true},{"durationMs":628,"exitCode":0,"name":"aderyn","output":"[high] abi-encode-packed-hash-collision at src/ArtCollection.sol:76: `abi.encodePacked()` Hash Collision (3 places)\n[high] contract-locks-ether at src/ArtifactsSale.sol:26: Contract locks Ether without a withdraw function\n[high] eth-send-unchecked-address at src/ArtifactsAuction.sol:90: ETH transferred without address checks (2 places)\n[high] reentrancy-state-change at src/ArtHouse.sol:317: Reentrancy: State change after external call (11 places)\n[low] centralization-risk at src/ArtHouse.sol:137: Centralization Risk (32 places)\n[low] internal-function-used-once at src/libraries/ArtifactsSeal.sol:22: Internal Function Used Only Once (2 places)\n[low] large-numeric-literal at src/ArtCollection.sol:90: Large Numeric Literal (5 places)\n[low] literal-instead-of-constant at src/libraries/ArtifactsSeal.sol:29: Literal Instead of Constant (13 places)\n[low] non-reentrant-not-first at src/ArtHouse.sol:379: `nonReentrant` is Not the First Modifier\n[low] require-revert-in-loop at src/ArtStorage.sol:159: Loop Contains `require`/`revert` (2 places)\n[low] state-change-without-event at src/ArtCollection.sol:37: State Change Without Event\n[low] state-no-address-check at src/ArtCollection.sol:46: Address State Variable Set Without Checks (3 places)\n[low] unchecked-return at src/ArtCollection.sol:73: Unchecked Return (8 places)\n[low] uninitialized-local-variable at src/ArtStorage.sol:203: Uninitialized Local Variable (3 places)\n[low] unsafe-oz-erc721-mint at src/ArtifactsPiece.sol:55: Unsafe `ERC721::_mint()`","passed":true},{"durationMs":14156,"exitCode":0,"name":"proof c5ac78abd788","output":"Compiling 66 files with Solc 0.8.26\nSolc 0.8.26 finished in 13.53s\nCompiler run successful!\n\nRan 1 test for test/imd-proof-d58af981/Proof_c5ac78abd788.t.sol:ReadQuadraticTest\n[PASS] test_hundredKBPieceIn1KBChunksIsFinalizableAndRenderable() (gas: 111615630)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 40.01ms (39.41ms CPU time)\n\nRan 1 test suite in 40.78ms (40.01ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"154e224836e515bc818a7dcbf309fb61c208db1db5878ebbd38f607d4f5c6df2","verifiedTreeHash":"4152af20d9997a7e4f421edbc90f3ad30a657dab","verifierVersion":"0.1.0+7129bf9d"},{"checks":[{"durationMs":166022,"exitCode":0,"name":"build","output":"Compiling 70 files with Solc 0.8.26\nSolc 0.8.26 finished in 165.92s\nCompiler run successful with warnings:\nWarning (2519): This declaration shadows an existing declaration.\n   --> test/Invariants.t.sol:475:9:\n    |\n475 |         address owner = _piece(id).ownerOf(id);\n    |         ^^^^^^^^^^^^^\nNote: The shadowed declaration is here:\n   --> test/Invariants.t.sol:426:5:\n    |\n426 |     address internal owner;\n    |     ^^^^^^^^^^^^^^^^^^^^^^\n\nWarning (2519): This declaration shadows an existing declaration.\n   --> test/Invariants.t.sol:565:9:\n    |\n565 |         address owner = p.ownerOf(id);\n    |         ^^^^^^^^^^^^^\nNote: The shadowed declaration is here:\n   --> test/Invariants.t.sol:426:5:\n    |\n426 |     address internal owner;\n    |     ^^^^^^^^^^^^^^^^^^^^^^\n\n","passed":true},{"durationMs":1064,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/Deploy.t.sol:DeployTest\n[PASS] test_constructorsRejectZeroReferences() (gas: 4160)\n[PASS] test_deployWiresOwnersAndReferencesWithoutPostCalls() (gas: 27295585)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 3.25ms (2.74ms CPU time)\n\nRan 2 tests for test/Artifacts.t.sol:ArtifactsWiringTest\n[PASS] test_correctWiringOpens() (gas: 271001)\n[PASS] test_openSaleAndConfigureNeedTheFactoryToNameThem() (gas: 267518)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 18.72ms (220.54µs CPU time)\n\nRan 10 tests for test/Artifacts.t.sol:ArtifactsEscrowTest\n[PASS] test_buyTakesExactPriceAndHoldsPiece() (gas: 800600)\n[PASS] test_buyerCancelsUntilKeyPostedAndIsRefundedByPull() (gas: 717813)\n[PASS] test_cancelRestoresSellerWrappedKey() (gas: 981218)\n[PASS] test_confirmReleasesPaymentThroughLedgerWithRoyalty() (gas: 885058)\n[PASS] test_escrowOperatorRightsAreLimitedToTheEscrow() (gas: 89516)\n[PASS] test_listingRules() (gas: 385028)\n[PASS] test_previousOwnerCannotOverwriteAPostedKey() (gas: 422773)\n[PASS] test_releaseAfter72Hours() (gas: 667555)\n[PASS] test_releaseWhenModuleRevokedHoldsPriceForSellerAndStillDelivers() (gas: 707626)\n[PASS] test_royaltyInfoSurvivesFactoryModuleRevocation() (gas: 111578)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 32.92ms (2.72ms CPU time)\n\nRan 3 tests for test/Artifacts.t.sol:ArtifactsClaimTest\n[PASS] test_claimFlowDeploysOneTokenPieceContract() (gas: 2000951)\n[PASS] test_plainTransferClearsKeyAndPreviousOwnerRewraps() (gas: 1434031)\n[PASS] test_sealTokenURIMatchesReferenceFrame() (gas: 4590716)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 34.10ms (10.04ms CPU time)\n\nRan 7 tests for test/Artifacts.t.sol:ArtifactsAuctionTest\n[PASS] test_bidsMustBeatReserveAndFivePercentIncrement() (gas: 532721)\n[PASS] test_configureNeedsLockedFactory() (gas: 45450)\n[PASS] test_configureRules() (gas: 303622)\n[PASS] test_dustBidsStillNeedAStrictIncrement() (gas: 287290)\n[PASS] test_lateBidExtendsEndByTenMinutes() (gas: 297834)\n[PASS] test_settleAssignsSourceAndPaysLedger() (gas: 1208777)\n[PASS] test_settleWhileRevokedHoldsProceedsUntilForwarded() (gas: 621868)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 37.61ms (1.81ms CPU time)\n\nRan 3 tests for test/Artifacts.t.sol:ArtifactsRegistryTest\n[PASS] test_assignmentOnlyByItsModuleAfterLock() (gas: 58986981)\n[PASS] test_modulesSetOnceAndNotConfiguredStateReverts() (gas: 265903)\n[PASS] test_setPieceValidationAndLockNeedsAll256() (gas: 59210106)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 47.35ms (52.81ms CPU time)\n\nRan 20 tests for test/FailurePaths.t.sol:ArtifactsFailurePathsTest\n[PASS] testFuzz_bidBelowFivePercentStepReverts(uint256,uint256) (runs: 512, μ: 227866, ~: 227930)\nLogs:\n  Bound result 1000000000000000003\n  Bound result 47427047993252799\n\n[PASS] testFuzz_minimumBidIsStrictlyAboveAndAtLeastFivePercentOver(uint256) (runs: 1000, μ: 301499, ~: 301313)\nLogs:\n  Bound result 422446693308363483266506914474\n\n[PASS] test_256thPurchaseIsSoldOut() (gas: 50458391)\n[PASS] test_auctionBidAtExactEndRevertsAndEarlySettleFails() (gas: 401458)\n[PASS] test_auctionRefundOfRejectingBidderBlocksOnlyItself() (gas: 729981)\n[PASS] test_auctionSettleWhileRevokedHoldsProceedsAndNeverStrandsTheWinner() (gas: 1382472)\n[PASS] test_auctionWithApprovedModuleHoldsNothing() (gas: 362517)\n[PASS] test_buyFailures() (gas: 401318)\n[PASS] test_cancelRestoresTheSellersOwnKeyOnlyIfItHadOne() (gas: 2447090)\n[PASS] test_escrowRefundIsNonReentrant() (gas: 2019272)\n[PASS] test_escrowReleaseWhileRevokedHoldsThePriceForTheSellerWithoutRoyalty() (gas: 2573401)\n[PASS] test_escrowStateMachineFailures() (gas: 2243201)\n[PASS] test_factoryLockedAndClaimFailures() (gas: 1761959)\n[PASS] test_fulfilmentFitsCallbackGasLimit() (gas: 691257)\n[PASS] test_oldOwnersListingCannotSellTheNewOwnersPiece() (gas: 1667775)\n[PASS] test_onlyCoordinatorFulfilsAndRetryRules() (gas: 990117)\n[PASS] test_pieceKeyPostingIsRestricted() (gas: 1365163)\n[PASS] test_saleAdminFailures() (gas: 485235)\n[PASS] test_selfTransferKeepsTheKeyButKillsTheListing() (gas: 1866265)\n[PASS] test_vrfRequestCarriesConfiguredParameters() (gas: 573685)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 52.89ms (79.20ms CPU time)\n\nRan 27 tests for test/ArtHouse.t.sol:ArtHouseTest\n[PASS] testFuzz_splitConservesEveryWei(uint96,uint16,uint16) (runs: 256, μ: 812309, ~: 816454)\nLogs:\n  Bound result 94641763499323990206\n  Bound result 8485\n  Bound result 44\n\n[PASS] test_artistChangesPayoutOnlyWhenApproved() (gas: 164347)\n[PASS] test_brokerShareGoesToHouseWhenUnapprovedOrBuyer() (gas: 1680985)\n[PASS] test_chunksAboveOneSstore2ContractAreRejected() (gas: 5959378)\n[PASS] test_collectionImplementationAndCloneCannotBeReinitializedOrMintedDirectly() (gas: 99597)\n[PASS] test_curatorRepairsOrDropsAnUnfinalizedPiece() (gas: 1028357)\n[PASS] test_customSplitAndRoundingToHouse() (gas: 835865)\n[PASS] test_finalizeChecksContentHashAndClearResets() (gas: 927828)\n[PASS] test_mintSplitsAndEveryPartyWithdraws() (gas: 1467417)\n[PASS] test_mintWithIMDPullsExactPrice() (gas: 1526427)\n[PASS] test_mintWithIMDRejectsFeeOnTransfer() (gas: 1764432)\n[PASS] test_onlyCuratorManagesRoles() (gas: 88212)\n[PASS] test_openSaleNeedsSealAndPrice() (gas: 729120)\n[PASS] test_ownershipIsTwoStepAndRenounceDisabled() (gas: 133497)\n[PASS] test_pieceIdsAndChunkIndexesMustBeNext() (gas: 485383)\n[PASS] test_piecesOver100KBRejected() (gas: 21814734)\n[PASS] test_readCostIsLinearIn100KBOfSmallChunks() (gas: 117093665)\n[PASS] test_reapprovingAnArtistKeepsItsPayout() (gas: 200039)\n[PASS] test_recordSaleOnlyFromApprovedModules() (gas: 151713)\n[PASS] test_revokedArtistLosesWriteAccessUntilReapproved() (gas: 575134)\n[PASS] test_sealNeedsEveryPieceFinalized() (gas: 566500)\n[PASS] test_setSplitOnlyWhileSvgSaleClosed() (gas: 661384)\n[PASS] test_storageWritesOnlyFromHouseAndSetHouseOnce() (gas: 90745)\n[PASS] test_submissionsAcceptedOrRejectedOnce() (gas: 471049)\n[PASS] test_tokenURIEscapesTitleAndDescription() (gas: 1014541)\n[PASS] test_tokenURIIsOnChainBase64JSONWithSVG() (gas: 1090510)\n[PASS] test_withdrawIsNonReentrantAndRevertsForRejectingReceivers() (gas: 2157953)\nSuite result: ok. 27 passed; 0 failed; 0 skipped; finished in 96.98ms (125.64ms CPU time)\n\nRan 20 tests for test/FailurePaths.t.sol:HouseFailurePathsTest\n[PASS] testFuzz_jsonEscapeRoundTripsAndEmitsNoRawQuotesOrControls(bytes) (runs: 1000, μ: 78913, ~: 98308)\n[PASS] testFuzz_withdrawPaysOnlyOwnBalance(uint96,uint96) (runs: 512, μ: 255925, ~: 256653)\nLogs:\n  Bound result 2664407430\n  Bound result 387525644\n\n[PASS] test_chunkAndPieceLimitsAtTheExactBoundary() (gas: 68468858)\n[PASS] test_createCollectionNeedsApprovedArtistAndNames() (gas: 178883)\n[PASS] test_emptyPieceCannotBeFinalizedAndEmptyCollectionCannotBeSealed() (gas: 230407)\n[PASS] test_houseBalanceFollowsOwnershipAndNotOldOwner() (gas: 346733)\n[PASS] test_hundredChunkPieceFinalizesUnderBlockGas() (gas: 81871145)\n[PASS] test_jsonEscapePinnedEdges() (gas: 20876)\n[PASS] test_mintFailsWhileClosedUnpricedOrWrongValue() (gas: 1312741)\n[PASS] test_mintWithIMDWrongPriceOrNoAllowancePullsNothing() (gas: 913036)\n[PASS] test_reapprovingAnArtistKeepsItsPayoutAndRevocationRemovesWriteAccess() (gas: 688730)\n[PASS] test_repairFunctionsAreCuratorOnlyAndRespectFinalizeAndSeal() (gas: 1886904)\n[PASS] test_revokedModuleCannotRecordAndResaleRejectsZero() (gas: 507713)\n[PASS] test_royaltyFollowsCurrentPayoutAndIs750Bps() (gas: 813659)\n[PASS] test_splitMustSumToFullBps() (gas: 154696)\n[PASS] test_splitOfOpenSvgSaleIsFrozenButModuleCollectionIsNot() (gas: 934178)\n[PASS] test_strangersCannotUploadAndArtistCannotClear() (gas: 798614)\n[PASS] test_submissionUnknownIdsRevert() (gas: 288624)\n[PASS] test_tokenURIEscapesArtistText() (gas: 981829)\n[PASS] test_unfinalizedPieceCannotBeSoldAndModuleCollectionHasNoSvgSale() (gas: 221749)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 105.76ms (403.74ms CPU time)\n\nRan 7 tests for test/Artifacts.t.sol:ArtifactsSaleTest\n[PASS] testFuzz_randomWordAlwaysLandsOnUnassignedEdition(uint256[8]) (runs: 256, μ: 3124503, ~: 3127094)\n[PASS] test_allEditionsAssignedExactlyOnceThenSoldOut() (gas: 92388470)\n[PASS] test_buyPaysExactPriceThroughLedgerAndRequestsVRF() (gas: 733103)\n[PASS] test_fulfilmentAssignsAndIgnoresStaleOrForeignRequests() (gas: 806188)\n[PASS] test_openSaleNeedsLockedFactory() (gas: 192369)\n[PASS] test_retryAfterMissingCallbackMakesOldRequestStale() (gas: 935730)\n[PASS] test_saleStartsClosedAndNeedsPriceVrfAndLock() (gas: 533089)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 105.86ms (132.52ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:AuctionInvariantTest\n[PASS]\nAuctionInvariantTest invariants:\n[PASS] invariant_auctionHoldsExactlyRefundsPlusOpenHighBidPlusHeld\n[PASS] invariant_settlementIsFinalAndAssignsTheWinner\n AuctionInvariantTest invariants (runs: 64, calls: 3840, reverts: 0)\n\n╭----------------+-----------------+-------+---------+----------╮\n| Contract       | Selector        | Calls | Reverts | Discards |\n+===============================================================+\n| AuctionHandler | bid             | 654   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| AuctionHandler | forwardProceeds | 631   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| AuctionHandler | settle          | 665   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| AuctionHandler | toggleModule    | 645   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| AuctionHandler | warp            | 640   | 0       | 0        |\n|----------------+-----------------+-------+---------+----------|\n| AuctionHandler | withdrawRefund  | 605   | 0       | 0        |\n╰----------------+-----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 145752710625305962\n  Bound result 24000\n  Bound result 12000\n  Bound result 399999999976009599\n  Bound result 257\n  Bound result 6607\n  Bound result 851101713912471998\n  Bound result 92\n  Bound result 2233625729\n  Bound result 672\n  Bound result 9466\n  Bound result 3958\n  Bound result 600000000000000000\n  Bound result 16777215\n  Bound result 1760000000\n  Bound result 21600\n  Bound result 2578\n  Bound result 2361\n  Bound result 3000000000000000000\n  Bound result 1554\n  Bound result 202138933715713432\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 403.42ms (359.79ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:SaleInvariantTest\n[PASS]\nSaleInvariantTest invariants:\n[PASS] invariant_noEditionAssignedTwice\n[PASS] invariant_saleForwardsEveryWeiToLedger\n SaleInvariantTest invariants (runs: 48, calls: 3840, reverts: 0)\n\n╭-------------+--------------------+-------+---------+----------╮\n| Contract    | Selector           | Calls | Reverts | Discards |\n+===============================================================+\n| SaleHandler | buy                | 1260  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| SaleHandler | fulfil             | 1299  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| SaleHandler | repriceWhileClosed | 1281  | 0       | 0        |\n╰-------------+--------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 597568506119803\n  Bound result 2017772716499967598\n  Bound result 100000000000000000\n  Bound result 8854\n  Bound result 1823\n  Bound result 197795918\n  Bound result 2739577255543769830\n  Bound result 23\n  Bound result 377484399\n  Bound result 1109\n  Bound result 779902470294861374\n  Bound result 2971835090222096348\n  Bound result 1721935187225003281\n  Bound result 5012\n  Bound result 1034716920\n  Bound result 52\n  Bound result 4451858504895408589\n  Bound result 9009763770\n  Bound result 5000000000000000000\n  Bound result 1400000000000000000\n  Bound result 1001\n  Bound result 7\n  Bound result 2\n  Bound result 2882951354080033467\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 735.92ms (707.30ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:ArtHouseLedgerInvariantTest\n[PASS]\nArtHouseLedgerInvariantTest invariants:\n[PASS] invariant_ethHeldEqualsEthOwed\n[PASS] invariant_imdHeldEqualsImdOwed\n[PASS] invariant_oneTokenPerPaidMintAndNeverAboveRegistered\n ArtHouseLedgerInvariantTest invariants (runs: 64, calls: 3840, reverts: 0)\n\n╭---------------+---------------+-------+---------+----------╮\n| Contract      | Selector      | Calls | Reverts | Discards |\n+============================================================+\n| LedgerHandler | mintEth       | 394   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | mintImd       | 402   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | recordResale  | 354   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | recordSale    | 358   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | setPayout     | 381   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | setPrices     | 381   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | setSplit      | 406   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | toggleBroker  | 380   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | withdraw      | 387   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | withdrawHouse | 397   | 0       | 0        |\n╰---------------+---------------+-------+---------+----------╯\n\nLogs:\n  Bound result 20\n  Bound result 1094\n  Bound result 1939\n  Bound result 660\n  Bound result 200\n  Bound result 6400\n  Bound result 10479790529894303747\n  Bound result 9306\n  Bound result 3333\n  Bound result 6401\n  Bound result 663074054399\n  Bound result 827905\n  Bound result 10000000000000000000\n  Bound result 97\n  Bound result 12\n  Bound result 3333\n  Bound result 257\n  Bound result 2579\n  Bound result 24\n  Bound result 1906\n  Bound result 4945\n  Bound result 33999380336506330774\n  Bound result 102\n  Bound result 4\n  Bound result 24357156073073139154\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 789.12ms (776.09ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:EscrowInvariantTest\n[PASS]\nEscrowInvariantTest invariants:\n[PASS] invariant_escrowHoldsRefundsPlusFundedListings\n[PASS] invariant_ledgerHoldsWhatItOwes\n EscrowInvariantTest invariants (runs: 64, calls: 3840, reverts: 0)\n\n╭---------------+------------------+-------+---------+----------╮\n| Contract      | Selector         | Calls | Reverts | Discards |\n+===============================================================+\n| EscrowHandler | buy              | 471   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| EscrowHandler | cancel           | 485   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| EscrowHandler | confirmOrRelease | 466   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| EscrowHandler | list             | 488   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| EscrowHandler | plainTransfer    | 494   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| EscrowHandler | postKey          | 487   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| EscrowHandler | toggleModule     | 482   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| EscrowHandler | withdrawRefund   | 467   | 0       | 0        |\n╰---------------+------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 10987436384346132224\n  Bound result 2093\n  Bound result 4812111843760032\n  Bound result 561\n  Bound result 6862\n  Bound result 6999\n  Bound result 6\n  Bound result 8877314540973808302\n  Bound result 63\n  Bound result 257\n  Bound result 5706\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 957.87ms (912.38ms CPU time)\n\nRan 14 test suites in 960.83ms (3.42s CPU time): 105 tests passed, 0 failed, 0 skipped (105 total tests)\n","passed":true},{"durationMs":80,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"ArtCollection.approve(address,uint256)\",\"ArtCollection.initialize(address,address,uint256,string,string)\",\"ArtCollection.mint(address,uint256)\",\"ArtCollection.safeTransferFrom(address,address,uint256)\",\"ArtCollection.safeTransferFrom(address,address,uint256,bytes)\",\"ArtCollection.setApprovalForAll(address,bool)\",\"ArtCollection.transferFrom(address,address,uint256)\",\"ArtHouse.acceptOwnership()\",\"ArtHouse.acceptSubmission(uint256)\",\"ArtHouse.approveArtist(address,string,address)\",\"ArtHouse.approveBroker(address)\",\"ArtHouse.approveModule(address,uint256)\",\"ArtHouse.clearPiece(uint256,uint256)\",\"ArtHouse.closeSale(uint256)\",\"ArtHouse.createCollection(address,string,string)\",\"ArtHouse.createModuleCollection(address)\",\"ArtHouse.dropLastPiece(uint256)\",\"ArtHouse.finalizePiece(uint256,uint256)\",\"ArtHouse.mint(uint256,uint256,address)\",\"ArtHouse.mintWithIMD(uint256,uint256,address,uint256)\",\"ArtHouse.openSale(uint256)\",\"ArtHouse.recordResale(address)\",\"ArtHouse.recordSale(address,address)\",\"ArtHouse.redescribePiece(uint256,uint256,bytes32,string,string)\",\"ArtHouse.registerPiece(uint256,uint256,bytes32,string,string)\",\"ArtHouse.rejectSubmission(uint256)\",\"ArtHouse.revokeArtist(address)\",\"ArtHouse.revokeBroker(address)\",\"ArtHouse.revokeModule(address)\",\"ArtHouse.seal(uint256)\",\"ArtHouse.setPayout(address)\",\"ArtHouse.setPrices(uint256,uint128,uint128)\",\"ArtHouse.setSplit(uint256,uint16,uint16,uint16)\",\"ArtHouse.submit(bytes32,string)\",\"ArtHouse.transferOwnership(address)\",\"ArtHouse.uploadChunk(uint256,uint256,uint256,bytes)\",\"ArtHouse.withdraw(address)\",\"ArtHouse.withdrawHouse(address)\",\"ArtStorage.acceptOwnership()\",\"ArtStorage.clearPiece(uint256,uint256)\",\"ArtStorage.dropLastPiece(uint256)\",\"ArtStorage.finalizePiece(uint256,uint256)\",\"ArtStorage.redescribePiece(uint256,uint256,bytes32,string,string)\",\"ArtStorage.registerPiece(uint256,uint256,bytes32,string,string)\",\"ArtStorage.seal(uint256)\",\"ArtStorage.setHouse(address)\",\"ArtStorage.transferOwnership(address)\",\"ArtStorage.uploadChunk(uint256,uint256,uint256,bytes)\",\"ArtifactsAuction.acceptOwnership()\",\"ArtifactsAuction.bid()\",\"ArtifactsAuction.configure(uint256,uint64)\",\"ArtifactsAuction.forwardProceeds()\",\"ArtifactsAuction.settle()\",\"ArtifactsAuction.transferOwnership(address)\",\"ArtifactsAuction.withdrawRefund()\",\"ArtifactsEscrow.buy(uint16,bytes32)\",\"ArtifactsEscrow.cancel(uint16)\",\"ArtifactsEscrow.cancelListing(uint16)\",\"ArtifactsEscrow.confirm(uint16)\",\"ArtifactsEscrow.list(uint16,uint256)\",\"ArtifactsEscrow.postKey(uint16,bytes)\",\"ArtifactsEscrow.release(uint16)\",\"ArtifactsEscrow.withdrawRefund()\",\"ArtifactsFactory.acceptOwnership()\",\"ArtifactsFactory.assignEdition(uint16,address)\",\"ArtifactsFactory.assignSource(address)\",\"ArtifactsFactory.claim(uint16)\",\"ArtifactsFactory.lock()\",\"ArtifactsFactory.postContentKey(uint16,bytes)\",\"ArtifactsFactory.registerViewingKey(bytes32)\",\"ArtifactsFactory.registerViewingKeyFor(address,bytes32)\",\"ArtifactsFactory.setModules(address,address,address)\",\"ArtifactsFactory.setPiece(uint16,(string,string,uint8,string),bytes,bytes32)\",\"ArtifactsFactory.transferOwnership(address)\",\"ArtifactsPiece.approve(address,uint256)\",\"ArtifactsPiece.initialize(uint16,address,bytes)\",\"ArtifactsPiece.safeTransferFrom(address,address,uint256)\",\"ArtifactsPiece.safeTransferFrom(address,address,uint256,bytes)\",\"ArtifactsPiece.setApprovalForAll(address,bool)\",\"ArtifactsPiece.setWrappedKey(bytes)\",\"ArtifactsPiece.transferFrom(address,address,uint256)\",\"ArtifactsSale.acceptOwnership()\",\"ArtifactsSale.buy(address)\",\"ArtifactsSale.closeSale()\",\"ArtifactsSale.configureVRF(address,bytes32,uint256,bool)\",\"ArtifactsSale.openSale()\",\"ArtifactsSale.rawFulfillRandomWords(uint256,uint256[])\",\"ArtifactsSale.retryRequest(uint256)\",\"ArtifactsSale.setPrice(uint256)\",\"ArtifactsSale.transferOwnership(address)\"],\"files\":{\".gitignore\":4,\"README.md\":202,\"foundry.toml\":18,\"remappings.txt\":2,\"script/Deploy.s.sol\":50,\"src/ArtCollection.sol\":96,\"src/ArtHouse.sol\":479,\"src/ArtStorage.sol\":208,\"src/ArtifactsAuction.sol\":126,\"src/ArtifactsEscrow.sol\":186,\"src/ArtifactsFactory.sol\":219,\"src/ArtifactsPiece.sol\":128,\"src/ArtifactsSale.sol\":199,\"src/HouseOwnable.sol\":18,\"src/interfaces/IArtifactsFactory.sol\":37,\"src/libraries/ArtifactsSeal.sol\":105,\"src/libraries/JsonEscape.sol\":40,\"src/libraries/SSTORE2.sol\":55,\"test/ArtHouse.t.sol\":637,\"test/Artifacts.t.sol\":860,\"test/Deploy.t.sol\":52,\"test/FailurePaths.t.sol\":1227,\"test/Fixture.sol\":109,\"test/Invariants.t.sol\":742,\"test/mocks/B64.sol\":46,\"test/mocks/Mocks.sol\":73},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"2ba7209f7417c971f0162b3b3dcbe8c3eb2f88a7a9af0a8e4b669081effabd13","verifiedTreeHash":"10f244f5f2e45ed8410bca3c17427d18f11bd069","verifierVersion":"0.1.0+7129bf9d"},{"checks":[{"durationMs":79895,"exitCode":0,"name":"build","output":"Compiling 67 files with Solc 0.8.26\nSolc 0.8.26 finished in 79.80s\nCompiler run successful!\n","passed":true},{"durationMs":164,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/Deploy.t.sol:DeployTest\n[PASS] test_constructorsRejectZeroReferences() (gas: 4160)\n[PASS] test_deployWiresOwnersAndReferencesWithoutPostCalls() (gas: 25686229)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.40ms (946.60µs CPU time)\n\nRan 5 tests for test/Artifacts.t.sol:ArtifactsAuctionTest\n[PASS] test_bidsMustBeatReserveAndFivePercentIncrement() (gas: 473659)\n[PASS] test_configureNeedsLockedFactory() (gas: 45249)\n[PASS] test_configureRules() (gas: 300353)\n[PASS] test_lateBidExtendsEndByTenMinutes() (gas: 294774)\n[PASS] test_settleAssignsSourceAndPaysLedger() (gas: 1199044)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 16.65ms (1.64ms CPU time)\n\nRan 6 tests for test/Artifacts.t.sol:ArtifactsEscrowTest\n[PASS] test_buyTakesExactPriceAndHoldsPiece() (gas: 649395)\n[PASS] test_buyerCancelsUntilKeyPostedAndIsRefundedByPull() (gas: 612730)\n[PASS] test_confirmReleasesPaymentThroughLedgerWithRoyalty() (gas: 809505)\n[PASS] test_escrowOperatorRightsAreLimitedToTheEscrow() (gas: 89462)\n[PASS] test_listingRules() (gas: 368480)\n[PASS] test_releaseAfter72Hours() (gas: 594745)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 16.71ms (1.61ms CPU time)\n\nRan 3 tests for test/Artifacts.t.sol:ArtifactsRegistryTest\n[PASS] test_assignmentOnlyByItsModuleAfterLock() (gas: 58980581)\n[PASS] test_modulesSetOnceAndNotConfiguredStateReverts() (gas: 265815)\n[PASS] test_setPieceValidationAndLockNeedsAll256() (gas: 59203656)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 19.20ms (31.32ms CPU time)\n\nRan 20 tests for test/ArtHouse.t.sol:ArtHouseTest\n[PASS] testFuzz_splitConservesEveryWei(uint96,uint16,uint16) (runs: 256, μ: 802146, ~: 806319)\nLogs:\n  Bound result 514264337593543950335\n  Bound result 12\n  Bound result 5598\n\n[PASS] test_artistChangesPayoutOnlyWhenApproved() (gas: 164100)\n[PASS] test_brokerShareGoesToHouseWhenUnapprovedOrBuyer() (gas: 1658862)\n[PASS] test_collectionImplementationAndCloneCannotBeReinitializedOrMintedDirectly() (gas: 99495)\n[PASS] test_customSplitAndRoundingToHouse() (gas: 823824)\n[PASS] test_finalizeChecksContentHashAndClearResets() (gas: 910051)\n[PASS] test_mintSplitsAndEveryPartyWithdraws() (gas: 1452397)\n[PASS] test_mintWithIMDPullsExactPrice() (gas: 1511693)\n[PASS] test_mintWithIMDRejectsFeeOnTransfer() (gas: 1763916)\n[PASS] test_onlyCuratorManagesRoles() (gas: 88014)\n[PASS] test_openSaleNeedsSealAndPrice() (gas: 721555)\n[PASS] test_ownershipIsTwoStepAndRenounceDisabled() (gas: 133101)\n[PASS] test_pieceIdsAndChunkIndexesMustBeNext() (gas: 473718)\n[PASS] test_piecesOver100KBRejected() (gas: 21798256)\n[PASS] test_recordSaleOnlyFromApprovedModules() (gas: 151263)\n[PASS] test_sealNeedsEveryPieceFinalized() (gas: 556858)\n[PASS] test_storageWritesOnlyFromHouseAndSetHouseOnce() (gas: 90548)\n[PASS] test_submissionsAcceptedOrRejectedOnce() (gas: 470335)\n[PASS] test_tokenURIIsOnChainBase64JSONWithSVG() (gas: 1077793)\n[PASS] test_withdrawIsNonReentrantAndRevertsForRejectingReceivers() (gas: 2142845)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 29.05ms (37.25ms CPU time)\n\nRan 3 tests for test/Artifacts.t.sol:ArtifactsClaimTest\n[PASS] test_claimFlowDeploysOneTokenPieceContract() (gas: 1998622)\n[PASS] test_plainTransferClearsKeyAndPreviousOwnerRewraps() (gas: 1426471)\n[PASS] test_sealTokenURIMatchesReferenceFrame() (gas: 4590716)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 28.93ms (10.48ms CPU time)\n\nRan 7 tests for test/Artifacts.t.sol:ArtifactsSaleTest\n[PASS] testFuzz_randomWordAlwaysLandsOnUnassignedEdition(uint256[8]) (runs: 256, μ: 3103298, ~: 3106058)\n[PASS] test_allEditionsAssignedExactlyOnceThenSoldOut() (gas: 91804316)\n[PASS] test_buyPaysExactPriceThroughLedgerAndRequestsVRF() (gas: 727871)\n[PASS] test_fulfilmentAssignsAndIgnoresStaleOrForeignRequests() (gas: 801154)\n[PASS] test_openSaleNeedsLockedFactory() (gas: 192363)\n[PASS] test_retryAfterMissingCallbackMakesOldRequestStale() (gas: 930696)\n[PASS] test_saleStartsClosedAndNeedsPriceVrfAndLock() (gas: 527581)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 71.96ms (103.92ms CPU time)\n\nRan 7 test suites in 73.99ms (183.89ms CPU time): 46 tests passed, 0 failed, 0 skipped (46 total tests)\n","passed":true},{"durationMs":57,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"ArtCollection.approve(address,uint256)\",\"ArtCollection.initialize(address,address,uint256,string,string)\",\"ArtCollection.mint(address,uint256)\",\"ArtCollection.safeTransferFrom(address,address,uint256)\",\"ArtCollection.safeTransferFrom(address,address,uint256,bytes)\",\"ArtCollection.setApprovalForAll(address,bool)\",\"ArtCollection.transferFrom(address,address,uint256)\",\"ArtHouse.acceptOwnership()\",\"ArtHouse.acceptSubmission(uint256)\",\"ArtHouse.approveArtist(address,string,address)\",\"ArtHouse.approveBroker(address)\",\"ArtHouse.approveModule(address,uint256)\",\"ArtHouse.clearPiece(uint256,uint256)\",\"ArtHouse.closeSale(uint256)\",\"ArtHouse.createCollection(address,string,string)\",\"ArtHouse.createModuleCollection(address)\",\"ArtHouse.finalizePiece(uint256,uint256)\",\"ArtHouse.mint(uint256,uint256,address)\",\"ArtHouse.mintWithIMD(uint256,uint256,address,uint256)\",\"ArtHouse.openSale(uint256)\",\"ArtHouse.recordResale(address)\",\"ArtHouse.recordSale(address,address)\",\"ArtHouse.registerPiece(uint256,uint256,bytes32,string,string)\",\"ArtHouse.rejectSubmission(uint256)\",\"ArtHouse.revokeArtist(address)\",\"ArtHouse.revokeBroker(address)\",\"ArtHouse.revokeModule(address)\",\"ArtHouse.seal(uint256)\",\"ArtHouse.setPayout(address)\",\"ArtHouse.setPrices(uint256,uint128,uint128)\",\"ArtHouse.setSplit(uint256,uint16,uint16,uint16)\",\"ArtHouse.submit(bytes32,string)\",\"ArtHouse.transferOwnership(address)\",\"ArtHouse.uploadChunk(uint256,uint256,uint256,bytes)\",\"ArtHouse.withdraw(address)\",\"ArtHouse.withdrawHouse(address)\",\"ArtStorage.acceptOwnership()\",\"ArtStorage.clearPiece(uint256,uint256)\",\"ArtStorage.finalizePiece(uint256,uint256)\",\"ArtStorage.registerPiece(uint256,uint256,bytes32,string,string)\",\"ArtStorage.seal(uint256)\",\"ArtStorage.setHouse(address)\",\"ArtStorage.transferOwnership(address)\",\"ArtStorage.uploadChunk(uint256,uint256,uint256,bytes)\",\"ArtifactsAuction.acceptOwnership()\",\"ArtifactsAuction.bid()\",\"ArtifactsAuction.configure(uint256,uint64)\",\"ArtifactsAuction.settle()\",\"ArtifactsAuction.transferOwnership(address)\",\"ArtifactsAuction.withdrawRefund()\",\"ArtifactsEscrow.buy(uint16,bytes32)\",\"ArtifactsEscrow.cancel(uint16)\",\"ArtifactsEscrow.cancelListing(uint16)\",\"ArtifactsEscrow.confirm(uint16)\",\"ArtifactsEscrow.list(uint16,uint256)\",\"ArtifactsEscrow.postKey(uint16,bytes)\",\"ArtifactsEscrow.release(uint16)\",\"ArtifactsEscrow.withdrawRefund()\",\"ArtifactsFactory.acceptOwnership()\",\"ArtifactsFactory.assignEdition(uint16,address)\",\"ArtifactsFactory.assignSource(address)\",\"ArtifactsFactory.claim(uint16)\",\"ArtifactsFactory.lock()\",\"ArtifactsFactory.postContentKey(uint16,bytes)\",\"ArtifactsFactory.registerViewingKey(bytes32)\",\"ArtifactsFactory.registerViewingKeyFor(address,bytes32)\",\"ArtifactsFactory.setModules(address,address,address)\",\"ArtifactsFactory.setPiece(uint16,(string,string,uint8,string),bytes,bytes32)\",\"ArtifactsFactory.transferOwnership(address)\",\"ArtifactsPiece.approve(address,uint256)\",\"ArtifactsPiece.initialize(uint16,address,bytes)\",\"ArtifactsPiece.safeTransferFrom(address,address,uint256)\",\"ArtifactsPiece.safeTransferFrom(address,address,uint256,bytes)\",\"ArtifactsPiece.setApprovalForAll(address,bool)\",\"ArtifactsPiece.setWrappedKey(bytes)\",\"ArtifactsPiece.transferFrom(address,address,uint256)\",\"ArtifactsSale.acceptOwnership()\",\"ArtifactsSale.buy(address)\",\"ArtifactsSale.closeSale()\",\"ArtifactsSale.configureVRF(address,bytes32,uint256,bool)\",\"ArtifactsSale.openSale()\",\"ArtifactsSale.rawFulfillRandomWords(uint256,uint256[])\",\"ArtifactsSale.retryRequest(uint256)\",\"ArtifactsSale.setPrice(uint256)\",\"ArtifactsSale.transferOwnership(address)\"],\"files\":{\".gitignore\":4,\"README.md\":162,\"foundry.toml\":18,\"remappings.txt\":2,\"script/Deploy.s.sol\":50,\"src/ArtCollection.sol\":95,\"src/ArtHouse.sol\":440,\"src/ArtStorage.sol\":166,\"src/ArtifactsAuction.sol\":101,\"src/ArtifactsEscrow.sol\":165,\"src/ArtifactsFactory.sol\":219,\"src/ArtifactsPiece.sol\":120,\"src/ArtifactsSale.sol\":195,\"src/HouseOwnable.sol\":18,\"src/interfaces/IArtifactsFactory.sol\":32,\"src/libraries/ArtifactsSeal.sol\":105,\"src/libraries/SSTORE2.sol\":34,\"test/ArtHouse.t.sol\":467,\"test/Artifacts.t.sol\":680,\"test/Deploy.t.sol\":52,\"test/Fixture.sol\":109,\"test/mocks/B64.sol\":46,\"test/mocks/Mocks.sol\":73},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":8118,"exitCode":0,"name":"slither","output":"[high/medium] reentrancy-eth at src/ArtifactsSale.sol:127: Reentrancy in ArtifactsSale.buy(address) (src/ArtifactsSale.sol#127-138):\n[medium/medium] unused-return at src/ArtCollection.sol:71: ArtCollection.tokenURI(uint256) (src/ArtCollection.sol#71-85) ignores return value by (None,title,description,None,None,None) = artStorage.pieceInfo(collectionId,tokenId) (src/ArtCollection.sol#73)\n[medium/medium] unused-return at src/ArtHouse.sol:436: ArtHouse._requireFinalized(uint256,uint256) (src/ArtHouse.sol#436-439) ignores return value by (None,None,None,None,None,finalized) = artStorage.pieceInfo(id,pieceId) (src/ArtHouse.sol#437)\n[low/medium] missing-zero-check at src/ArtifactsPiece.sol:47: ArtifactsPiece.initialize(uint16,address,bytes).ciphertext_ (src/ArtifactsPiece.sol#47) lacks a zero-check on :\n[low/medium] missing-zero-check at src/ArtifactsPiece.sol:42: ArtifactsPiece.initialize(uint16,address,bytes).owner_ (src/ArtifactsPiece.sol#42) lacks a zero-check on :\n[low/medium] missing-zero-check at src/ArtCollection.sol:37: ArtCollection.initialize(address,address,uint256,string,string).house_ (src/ArtCollection.sol#37) lacks a zero-check on :\n[low/medium] reentrancy-benign at src/ArtifactsSale.sol:172: Reentrancy in ArtifactsSale._requestRandomness(uint256) (src/ArtifactsSale.sol#172-189):\n[low/medium] reentrancy-benign at src/ArtifactsSale.sol:127: Reentrancy in ArtifactsSale.buy(address) (src/ArtifactsSale.sol#127-138):\n[low/medium] reentrancy-events at src/ArtHouse.sol:183: Reentrancy in ArtHouse.createCollection(address,string,string) (src/ArtHouse.sol#183-195):\n[low/medium] reentrancy-events at src/ArtifactsSale.sol:172: Reentrancy in ArtifactsSale._requestRandomness(uint256) (src/ArtifactsSale.sol#172-189):\n[low/medium] timestamp at src/ArtifactsEscrow.sol:134: ArtifactsEscrow.release(uint16) (src/ArtifactsEscrow.sol#134-140) uses timestamp for comparisons\n[low/medium] timestamp at src/ArtifactsAuction.sol:91: ArtifactsAuction.settle() (src/ArtifactsAuction.sol#91-100) uses timestamp for comparisons\n[low/medium] timestamp at src/ArtifactsAuction.sol:53: ArtifactsAuction.configure(uint256,uint64) (src/ArtifactsAuction.sol#53-61) uses timestamp for comparisons\n[low/medium] timestamp at src/ArtifactsSale.sol:159: ArtifactsSale.retryRequest(uint256) (src/ArtifactsSale.sol#159-166) uses timestamp for comparisons\n[low/medium] timestamp at src/ArtifactsAuction.sol:68: ArtifactsAuction.bid() (src/ArtifactsAuction.sol#68-79) uses timestamp for comparisons","passed":true},{"durationMs":552,"exitCode":0,"name":"aderyn","output":"[high] abi-encode-packed-hash-collision at src/ArtCollection.sol:75: `abi.encodePacked()` Hash Collision (3 places)\n[high] contract-locks-ether at src/ArtifactsSale.sol:26: Contract locks Ether without a withdraw function\n[high] eth-send-unchecked-address at src/ArtifactsAuction.sol:81: ETH transferred without address checks (2 places)\n[high] reentrancy-state-change at src/ArtHouse.sol:284: Reentrancy: State change after external call (7 places)\n[low] centralization-risk at src/ArtHouse.sol:132: Centralization Risk (30 places)\n[low] internal-function-used-once at src/libraries/ArtifactsSeal.sol:22: Internal Function Used Only Once (2 places)\n[low] large-numeric-literal at src/ArtCollection.sol:89: Large Numeric Literal (5 places)\n[low] literal-instead-of-constant at src/libraries/ArtifactsSeal.sol:29: Literal Instead of Constant (7 places)\n[low] non-reentrant-not-first at src/ArtHouse.sol:348: `nonReentrant` is Not the First Modifier\n[low] require-revert-in-loop at src/ArtStorage.sol:122: Loop Contains `require`/`revert` (2 places)\n[low] state-change-without-event at src/ArtCollection.sol:36: State Change Without Event\n[low] state-no-address-check at src/ArtCollection.sol:45: Address State Variable Set Without Checks (3 places)\n[low] unchecked-return at src/ArtCollection.sol:72: Unchecked Return (6 places)\n[low] uninitialized-local-variable at src/ArtStorage.sol:162: Uninitialized Local Variable\n[low] unsafe-oz-erc721-mint at src/ArtifactsPiece.sol:53: Unsafe `ERC721::_mint()`","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"62b1cf2c2ec7abfe2a312b28f0bdaeb5a63f45dfa4846f65b4c2209af4f42e39","verifiedTreeHash":"8478a53585da22c2188b7b4ffb6bca9befb59f83","verifierVersion":"0.1.0+7129bf9d"},{"checks":[{"durationMs":75946,"exitCode":0,"name":"build","output":"Compiling 67 files with Solc 0.8.26\nSolc 0.8.26 finished in 75.81s\nCompiler run successful!\n","passed":true},{"durationMs":246,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/Deploy.t.sol:DeployTest\n[PASS] test_constructorsRejectZeroReferences() (gas: 4160)\n[PASS] test_deployWiresOwnersAndReferencesWithoutPostCalls() (gas: 25686229)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.41ms (926.06µs CPU time)\n\nRan 6 tests for test/Artifacts.t.sol:ArtifactsEscrowTest\n[PASS] test_buyTakesExactPriceAndHoldsPiece() (gas: 649395)\n[PASS] test_buyerCancelsUntilKeyPostedAndIsRefundedByPull() (gas: 612730)\n[PASS] test_confirmReleasesPaymentThroughLedgerWithRoyalty() (gas: 809505)\n[PASS] test_escrowOperatorRightsAreLimitedToTheEscrow() (gas: 89462)\n[PASS] test_listingRules() (gas: 368480)\n[PASS] test_releaseAfter72Hours() (gas: 594745)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 16.61ms (1.76ms CPU time)\n\nRan 20 tests for test/ArtHouse.t.sol:ArtHouseTest\n[PASS] testFuzz_splitConservesEveryWei(uint96,uint16,uint16) (runs: 256, μ: 802146, ~: 806319)\nLogs:\n  Bound result 23580824202280\n  Bound result 4\n  Bound result 3\n\n[PASS] test_artistChangesPayoutOnlyWhenApproved() (gas: 164100)\n[PASS] test_brokerShareGoesToHouseWhenUnapprovedOrBuyer() (gas: 1658862)\n[PASS] test_collectionImplementationAndCloneCannotBeReinitializedOrMintedDirectly() (gas: 99495)\n[PASS] test_customSplitAndRoundingToHouse() (gas: 823824)\n[PASS] test_finalizeChecksContentHashAndClearResets() (gas: 910051)\n[PASS] test_mintSplitsAndEveryPartyWithdraws() (gas: 1452397)\n[PASS] test_mintWithIMDPullsExactPrice() (gas: 1511693)\n[PASS] test_mintWithIMDRejectsFeeOnTransfer() (gas: 1763916)\n[PASS] test_onlyCuratorManagesRoles() (gas: 88014)\n[PASS] test_openSaleNeedsSealAndPrice() (gas: 721555)\n[PASS] test_ownershipIsTwoStepAndRenounceDisabled() (gas: 133101)\n[PASS] test_pieceIdsAndChunkIndexesMustBeNext() (gas: 473718)\n[PASS] test_piecesOver100KBRejected() (gas: 21798256)\n[PASS] test_recordSaleOnlyFromApprovedModules() (gas: 151263)\n[PASS] test_sealNeedsEveryPieceFinalized() (gas: 556858)\n[PASS] test_storageWritesOnlyFromHouseAndSetHouseOnce() (gas: 90548)\n[PASS] test_submissionsAcceptedOrRejectedOnce() (gas: 470335)\n[PASS] test_tokenURIIsOnChainBase64JSONWithSVG() (gas: 1077793)\n[PASS] test_withdrawIsNonReentrantAndRevertsForRejectingReceivers() (gas: 2142845)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 21.02ms (28.71ms CPU time)\n\nRan 3 tests for test/Artifacts.t.sol:ArtifactsRegistryTest\n[PASS] test_assignmentOnlyByItsModuleAfterLock() (gas: 58980581)\n[PASS] test_modulesSetOnceAndNotConfiguredStateReverts() (gas: 265815)\n[PASS] test_setPieceValidationAndLockNeedsAll256() (gas: 59203656)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 21.68ms (33.25ms CPU time)\n\nRan 3 tests for test/Artifacts.t.sol:ArtifactsClaimTest\n[PASS] test_claimFlowDeploysOneTokenPieceContract() (gas: 1998622)\n[PASS] test_plainTransferClearsKeyAndPreviousOwnerRewraps() (gas: 1426471)\n[PASS] test_sealTokenURIMatchesReferenceFrame() (gas: 4590716)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 23.57ms (9.33ms CPU time)\n\nRan 5 tests for test/Artifacts.t.sol:ArtifactsAuctionTest\n[PASS] test_bidsMustBeatReserveAndFivePercentIncrement() (gas: 473659)\n[PASS] test_configureNeedsLockedFactory() (gas: 45249)\n[PASS] test_configureRules() (gas: 300353)\n[PASS] test_lateBidExtendsEndByTenMinutes() (gas: 294774)\n[PASS] test_settleAssignsSourceAndPaysLedger() (gas: 1199044)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 28.01ms (1.66ms CPU time)\n\nRan 7 tests for test/Artifacts.t.sol:ArtifactsSaleTest\n[PASS] testFuzz_randomWordAlwaysLandsOnUnassignedEdition(uint256[8]) (runs: 256, μ: 3103298, ~: 3106058)\n[PASS] test_allEditionsAssignedExactlyOnceThenSoldOut() (gas: 91804316)\n[PASS] test_buyPaysExactPriceThroughLedgerAndRequestsVRF() (gas: 727871)\n[PASS] test_fulfilmentAssignsAndIgnoresStaleOrForeignRequests() (gas: 801154)\n[PASS] test_openSaleNeedsLockedFactory() (gas: 192363)\n[PASS] test_retryAfterMissingCallbackMakesOldRequestStale() (gas: 930696)\n[PASS] test_saleStartsClosedAndNeedsPriceVrfAndLock() (gas: 527581)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 67.02ms (99.75ms CPU time)\n\nRan 7 test suites in 69.04ms (179.33ms CPU time): 46 tests passed, 0 failed, 0 skipped (46 total tests)\n","passed":true},{"durationMs":50,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"ArtCollection.approve(address,uint256)\",\"ArtCollection.initialize(address,address,uint256,string,string)\",\"ArtCollection.mint(address,uint256)\",\"ArtCollection.safeTransferFrom(address,address,uint256)\",\"ArtCollection.safeTransferFrom(address,address,uint256,bytes)\",\"ArtCollection.setApprovalForAll(address,bool)\",\"ArtCollection.transferFrom(address,address,uint256)\",\"ArtHouse.acceptOwnership()\",\"ArtHouse.acceptSubmission(uint256)\",\"ArtHouse.approveArtist(address,string,address)\",\"ArtHouse.approveBroker(address)\",\"ArtHouse.approveModule(address,uint256)\",\"ArtHouse.clearPiece(uint256,uint256)\",\"ArtHouse.closeSale(uint256)\",\"ArtHouse.createCollection(address,string,string)\",\"ArtHouse.createModuleCollection(address)\",\"ArtHouse.finalizePiece(uint256,uint256)\",\"ArtHouse.mint(uint256,uint256,address)\",\"ArtHouse.mintWithIMD(uint256,uint256,address,uint256)\",\"ArtHouse.openSale(uint256)\",\"ArtHouse.recordResale(address)\",\"ArtHouse.recordSale(address,address)\",\"ArtHouse.registerPiece(uint256,uint256,bytes32,string,string)\",\"ArtHouse.rejectSubmission(uint256)\",\"ArtHouse.revokeArtist(address)\",\"ArtHouse.revokeBroker(address)\",\"ArtHouse.revokeModule(address)\",\"ArtHouse.seal(uint256)\",\"ArtHouse.setPayout(address)\",\"ArtHouse.setPrices(uint256,uint128,uint128)\",\"ArtHouse.setSplit(uint256,uint16,uint16,uint16)\",\"ArtHouse.submit(bytes32,string)\",\"ArtHouse.transferOwnership(address)\",\"ArtHouse.uploadChunk(uint256,uint256,uint256,bytes)\",\"ArtHouse.withdraw(address)\",\"ArtHouse.withdrawHouse(address)\",\"ArtStorage.acceptOwnership()\",\"ArtStorage.clearPiece(uint256,uint256)\",\"ArtStorage.finalizePiece(uint256,uint256)\",\"ArtStorage.registerPiece(uint256,uint256,bytes32,string,string)\",\"ArtStorage.seal(uint256)\",\"ArtStorage.setHouse(address)\",\"ArtStorage.transferOwnership(address)\",\"ArtStorage.uploadChunk(uint256,uint256,uint256,bytes)\",\"ArtifactsAuction.acceptOwnership()\",\"ArtifactsAuction.bid()\",\"ArtifactsAuction.configure(uint256,uint64)\",\"ArtifactsAuction.settle()\",\"ArtifactsAuction.transferOwnership(address)\",\"ArtifactsAuction.withdrawRefund()\",\"ArtifactsEscrow.buy(uint16,bytes32)\",\"ArtifactsEscrow.cancel(uint16)\",\"ArtifactsEscrow.cancelListing(uint16)\",\"ArtifactsEscrow.confirm(uint16)\",\"ArtifactsEscrow.list(uint16,uint256)\",\"ArtifactsEscrow.postKey(uint16,bytes)\",\"ArtifactsEscrow.release(uint16)\",\"ArtifactsEscrow.withdrawRefund()\",\"ArtifactsFactory.acceptOwnership()\",\"ArtifactsFactory.assignEdition(uint16,address)\",\"ArtifactsFactory.assignSource(address)\",\"ArtifactsFactory.claim(uint16)\",\"ArtifactsFactory.lock()\",\"ArtifactsFactory.postContentKey(uint16,bytes)\",\"ArtifactsFactory.registerViewingKey(bytes32)\",\"ArtifactsFactory.registerViewingKeyFor(address,bytes32)\",\"ArtifactsFactory.setModules(address,address,address)\",\"ArtifactsFactory.setPiece(uint16,(string,string,uint8,string),bytes,bytes32)\",\"ArtifactsFactory.transferOwnership(address)\",\"ArtifactsPiece.approve(address,uint256)\",\"ArtifactsPiece.initialize(uint16,address,bytes)\",\"ArtifactsPiece.safeTransferFrom(address,address,uint256)\",\"ArtifactsPiece.safeTransferFrom(address,address,uint256,bytes)\",\"ArtifactsPiece.setApprovalForAll(address,bool)\",\"ArtifactsPiece.setWrappedKey(bytes)\",\"ArtifactsPiece.transferFrom(address,address,uint256)\",\"ArtifactsSale.acceptOwnership()\",\"ArtifactsSale.buy(address)\",\"ArtifactsSale.closeSale()\",\"ArtifactsSale.configureVRF(address,bytes32,uint256,bool)\",\"ArtifactsSale.openSale()\",\"ArtifactsSale.rawFulfillRandomWords(uint256,uint256[])\",\"ArtifactsSale.retryRequest(uint256)\",\"ArtifactsSale.setPrice(uint256)\",\"ArtifactsSale.transferOwnership(address)\"],\"files\":{\".gitignore\":4,\"README.md\":162,\"foundry.toml\":18,\"launch.json\":55,\"remappings.txt\":2,\"script/Deploy.s.sol\":50,\"src/ArtCollection.sol\":95,\"src/ArtHouse.sol\":440,\"src/ArtStorage.sol\":166,\"src/ArtifactsAuction.sol\":101,\"src/ArtifactsEscrow.sol\":165,\"src/ArtifactsFactory.sol\":219,\"src/ArtifactsPiece.sol\":120,\"src/ArtifactsSale.sol\":195,\"src/HouseOwnable.sol\":18,\"src/interfaces/IArtifactsFactory.sol\":32,\"src/libraries/ArtifactsSeal.sol\":105,\"src/libraries/SSTORE2.sol\":34,\"test/ArtHouse.t.sol\":467,\"test/Artifacts.t.sol\":680,\"test/Deploy.t.sol\":52,\"test/Fixture.sol\":109,\"test/mocks/B64.sol\":46,\"test/mocks/Mocks.sol\":73},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"911eb76bdd6fe487d1372c901c6063fe3f07040991de91dac82ec51b70059917","verifiedTreeHash":"0d2ac6fb98a501ce83a3e20d5174179af5118fe0","verifierVersion":"0.1.0+7129bf9d"},{"checks":[{"durationMs":130451,"exitCode":0,"name":"build","output":"Compiling 69 files with Solc 0.8.26\nSolc 0.8.26 finished in 130.36s\nCompiler run successful!\n","passed":true},{"durationMs":859,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/Deploy.t.sol:DeployTest\n[PASS] test_constructorsRejectZeroReferences() (gas: 4160)\n[PASS] test_deployWiresOwnersAndReferencesWithoutPostCalls() (gas: 25686229)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.41ms (938.50µs CPU time)\n\nRan 12 tests for test/FailurePaths.t.sol:HouseFailurePathsTest\n[PASS] testFuzz_withdrawPaysOnlyOwnBalance(uint96,uint96) (runs: 512, μ: 250785, ~: 251535)\nLogs:\n  Bound result 3\n  Bound result 4356755564734239554\n\n[PASS] test_createCollectionNeedsApprovedArtistAndNames() (gas: 178571)\n[PASS] test_emptyPieceCannotBeFinalizedAndEmptyCollectionCannotBeSealed() (gas: 225504)\n[PASS] test_houseBalanceFollowsOwnershipAndNotOldOwner() (gas: 343725)\n[PASS] test_mintFailsWhileClosedUnpricedOrWrongValue() (gas: 1303934)\n[PASS] test_mintWithIMDWrongPriceOrNoAllowancePullsNothing() (gas: 905288)\n[PASS] test_revokedModuleCannotRecordAndResaleRejectsZero() (gas: 218742)\n[PASS] test_royaltyFollowsCurrentPayoutAndIs750Bps() (gas: 805798)\n[PASS] test_splitMustSumToFullBps() (gas: 145622)\n[PASS] test_strangersCannotUploadAndArtistCannotClear() (gas: 788085)\n[PASS] test_submissionUnknownIdsRevert() (gas: 288008)\n[PASS] test_unfinalizedPieceCannotBeSoldAndModuleCollectionHasNoSvgSale() (gas: 221007)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 15.88ms (18.54ms CPU time)\n\nRan 5 tests for test/Artifacts.t.sol:ArtifactsAuctionTest\n[PASS] test_bidsMustBeatReserveAndFivePercentIncrement() (gas: 473659)\n[PASS] test_configureNeedsLockedFactory() (gas: 45249)\n[PASS] test_configureRules() (gas: 300353)\n[PASS] test_lateBidExtendsEndByTenMinutes() (gas: 294774)\n[PASS] test_settleAssignsSourceAndPaysLedger() (gas: 1199044)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 17.71ms (1.39ms CPU time)\n\nRan 6 tests for test/Artifacts.t.sol:ArtifactsEscrowTest\n[PASS] test_buyTakesExactPriceAndHoldsPiece() (gas: 649395)\n[PASS] test_buyerCancelsUntilKeyPostedAndIsRefundedByPull() (gas: 612730)\n[PASS] test_confirmReleasesPaymentThroughLedgerWithRoyalty() (gas: 809505)\n[PASS] test_escrowOperatorRightsAreLimitedToTheEscrow() (gas: 89462)\n[PASS] test_listingRules() (gas: 368480)\n[PASS] test_releaseAfter72Hours() (gas: 594745)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 17.91ms (2.01ms CPU time)\n\nRan 20 tests for test/ArtHouse.t.sol:ArtHouseTest\n[PASS] testFuzz_splitConservesEveryWei(uint96,uint16,uint16) (runs: 256, μ: 802244, ~: 806319)\nLogs:\n  Bound result 94641763499323990206\n  Bound result 8485\n  Bound result 44\n\n[PASS] test_artistChangesPayoutOnlyWhenApproved() (gas: 164100)\n[PASS] test_brokerShareGoesToHouseWhenUnapprovedOrBuyer() (gas: 1658862)\n[PASS] test_collectionImplementationAndCloneCannotBeReinitializedOrMintedDirectly() (gas: 99495)\n[PASS] test_customSplitAndRoundingToHouse() (gas: 823824)\n[PASS] test_finalizeChecksContentHashAndClearResets() (gas: 910051)\n[PASS] test_mintSplitsAndEveryPartyWithdraws() (gas: 1452397)\n[PASS] test_mintWithIMDPullsExactPrice() (gas: 1511693)\n[PASS] test_mintWithIMDRejectsFeeOnTransfer() (gas: 1763916)\n[PASS] test_onlyCuratorManagesRoles() (gas: 88014)\n[PASS] test_openSaleNeedsSealAndPrice() (gas: 721555)\n[PASS] test_ownershipIsTwoStepAndRenounceDisabled() (gas: 133101)\n[PASS] test_pieceIdsAndChunkIndexesMustBeNext() (gas: 473718)\n[PASS] test_piecesOver100KBRejected() (gas: 21798256)\n[PASS] test_recordSaleOnlyFromApprovedModules() (gas: 151263)\n[PASS] test_sealNeedsEveryPieceFinalized() (gas: 556858)\n[PASS] test_storageWritesOnlyFromHouseAndSetHouseOnce() (gas: 90548)\n[PASS] test_submissionsAcceptedOrRejectedOnce() (gas: 470335)\n[PASS] test_tokenURIIsOnChainBase64JSONWithSVG() (gas: 1077793)\n[PASS] test_withdrawIsNonReentrantAndRevertsForRejectingReceivers() (gas: 2142845)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 31.18ms (38.76ms CPU time)\n\nRan 3 tests for test/Artifacts.t.sol:ArtifactsRegistryTest\n[PASS] test_assignmentOnlyByItsModuleAfterLock() (gas: 58980581)\n[PASS] test_modulesSetOnceAndNotConfiguredStateReverts() (gas: 265815)\n[PASS] test_setPieceValidationAndLockNeedsAll256() (gas: 59203656)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 32.57ms (40.16ms CPU time)\n\nRan 14 tests for test/FailurePaths.t.sol:ArtifactsFailurePathsTest\n[PASS] testFuzz_bidBelowFivePercentStepReverts(uint256,uint256) (runs: 512, μ: 224709, ~: 224771)\nLogs:\n  Bound result 384112544292253440529\n  Bound result 1\n\n[PASS] test_256thPurchaseIsSoldOut() (gas: 49874115)\n[PASS] test_auctionBidAtExactEndRevertsAndEarlySettleFails() (gas: 398289)\n[PASS] test_auctionRefundOfRejectingBidderBlocksOnlyItself() (gas: 723338)\n[PASS] test_auctionSettleNeedsApprovedModule() (gas: 543224)\n[PASS] test_buyFailures() (gas: 398530)\n[PASS] test_escrowRefundIsNonReentrant() (gas: 1907521)\n[PASS] test_escrowStateMachineFailures() (gas: 2108289)\n[PASS] test_factoryLockedAndClaimFailures() (gas: 1756858)\n[PASS] test_fulfilmentFitsCallbackGasLimit() (gas: 686167)\n[PASS] test_onlyCoordinatorFulfilsAndRetryRules() (gas: 985027)\n[PASS] test_pieceKeyPostingIsRestricted() (gas: 1357857)\n[PASS] test_saleAdminFailures() (gas: 482339)\n[PASS] test_vrfRequestCarriesConfiguredParameters() (gas: 568595)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 33.87ms (26.46ms CPU time)\n\nRan 3 tests for test/Artifacts.t.sol:ArtifactsClaimTest\n[PASS] test_claimFlowDeploysOneTokenPieceContract() (gas: 1998622)\n[PASS] test_plainTransferClearsKeyAndPreviousOwnerRewraps() (gas: 1426471)\n[PASS] test_sealTokenURIMatchesReferenceFrame() (gas: 4590716)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 69.44ms (11.92ms CPU time)\n\nRan 7 tests for test/Artifacts.t.sol:ArtifactsSaleTest\n[PASS] testFuzz_randomWordAlwaysLandsOnUnassignedEdition(uint256[8]) (runs: 256, μ: 3103562, ~: 3106040)\n[PASS] test_allEditionsAssignedExactlyOnceThenSoldOut() (gas: 91804316)\n[PASS] test_buyPaysExactPriceThroughLedgerAndRequestsVRF() (gas: 727871)\n[PASS] test_fulfilmentAssignsAndIgnoresStaleOrForeignRequests() (gas: 801154)\n[PASS] test_openSaleNeedsLockedFactory() (gas: 192363)\n[PASS] test_retryAfterMissingCallbackMakesOldRequestStale() (gas: 930696)\n[PASS] test_saleStartsClosedAndNeedsPriceVrfAndLock() (gas: 527581)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 75.97ms (111.44ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:AuctionInvariantTest\n[PASS]\nAuctionInvariantTest invariants:\n[PASS] invariant_auctionHoldsExactlyRefundsPlusOpenHighBid\n[PASS] invariant_settlementIsFinalAndAssignsTheWinner\n AuctionInvariantTest invariants (runs: 64, calls: 3840, reverts: 0)\n\n╭----------------+----------------+-------+---------+----------╮\n| Contract       | Selector       | Calls | Reverts | Discards |\n+==============================================================+\n| AuctionHandler | bid            | 901   | 0       | 0        |\n|----------------+----------------+-------+---------+----------|\n| AuctionHandler | settle         | 939   | 0       | 0        |\n|----------------+----------------+-------+---------+----------|\n| AuctionHandler | warp           | 1015  | 0       | 0        |\n|----------------+----------------+-------+---------+----------|\n| AuctionHandler | withdrawRefund | 985   | 0       | 0        |\n╰----------------+----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 6\n  Bound result 150376973295030144\n  Bound result 2060229857399139937\n  Bound result 2072688815456077002\n  Bound result 494419\n  Bound result 17937\n  Bound result 99\n  Bound result 732900053805287204\n  Bound result 259200\n  Bound result 66239936\n  Bound result 1001\n  Bound result 2071\n  Bound result 1850000000000000000\n  Bound result 9812\n  Bound result 9933\n  Bound result 2\n  Bound result 2462\n  Bound result 62\n  Bound result 2176\n  Bound result 1858116977375557498\n  Bound result 26\n  Bound result 6993\n  Bound result 10581\n  Bound result 4\n  Bound result 2221\n  Bound result 2728\n  Bound result 7001\n  Bound result 77884974118570491\n  Bound result 2\n  Bound result 3763\n  Bound result 1138\n  Bound result 600\n  Bound result 40\n  Bound result 2999999999999999976\n  Bound result 5\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 302.64ms (278.98ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:SaleInvariantTest\n[PASS]\nSaleInvariantTest invariants:\n[PASS] invariant_noEditionAssignedTwice\n[PASS] invariant_saleForwardsEveryWeiToLedger\n SaleInvariantTest invariants (runs: 48, calls: 3840, reverts: 0)\n\n╭-------------+--------------------+-------+---------+----------╮\n| Contract    | Selector           | Calls | Reverts | Discards |\n+===============================================================+\n| SaleHandler | buy                | 1260  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| SaleHandler | fulfil             | 1299  | 0       | 0        |\n|-------------+--------------------+-------+---------+----------|\n| SaleHandler | repriceWhileClosed | 1281  | 0       | 0        |\n╰-------------+--------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 597568506119803\n  Bound result 1351630574666719629\n  Bound result 99\n  Bound result 591\n  Bound result 8070\n  Bound result 197795918\n  Bound result 2739577255543769830\n  Bound result 900\n  Bound result 2480\n  Bound result 1395\n  Bound result 5534\n  Bound result 5782\n  Bound result 1721935187225003281\n  Bound result 2603961781\n  Bound result 1034716920\n  Bound result 52\n  Bound result 970240327175908273\n  Bound result 9566\n  Bound result 201\n  Bound result 200\n  Bound result 3333\n  Bound result 12\n  Bound result 2\n  Bound result 2882951354080033467\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 629.76ms (595.21ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:ArtHouseLedgerInvariantTest\n[PASS]\nArtHouseLedgerInvariantTest invariants:\n[PASS] invariant_ethHeldEqualsEthOwed\n[PASS] invariant_imdHeldEqualsImdOwed\n[PASS] invariant_oneTokenPerPaidMintAndNeverAboveRegistered\n ArtHouseLedgerInvariantTest invariants (runs: 64, calls: 3840, reverts: 380)\n\n╭---------------+---------------+-------+---------+----------╮\n| Contract      | Selector      | Calls | Reverts | Discards |\n+============================================================+\n| LedgerHandler | mintEth       | 394   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | mintImd       | 402   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | recordResale  | 354   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | recordSale    | 358   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | setPayout     | 381   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | setPrices     | 381   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | setSplit      | 406   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | toggleBroker  | 380   | 380     | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | withdraw      | 387   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | withdrawHouse | 397   | 0       | 0        |\n╰---------------+---------------+-------+---------+----------╯\n\nLogs:\n  Bound result 660\n  Bound result 1898\n  Bound result 1385\n  Bound result 300\n  Bound result 201\n  Bound result 6401\n  Bound result 10479790529894303747\n  Bound result 12126\n  Bound result 900000000000000000\n  Bound result 24000\n  Bound result 663074054399\n  Bound result 55934\n  Bound result 1000000000000000000\n  Bound result 122\n  Bound result 16\n  Bound result 2100000000000000000\n  Bound result 5\n  Bound result 2579\n  Bound result 24\n  Bound result 3531\n  Bound result 172\n  Bound result 33999380336506330774\n  Bound result 3598\n  Bound result 256\n  Bound result 49679171529474767612\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 640.21ms (628.35ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:EscrowInvariantTest\n[PASS]\nEscrowInvariantTest invariants:\n[PASS] invariant_escrowHoldsRefundsPlusFundedListings\n[PASS] invariant_ledgerHoldsWhatItOwes\n EscrowInvariantTest invariants (runs: 64, calls: 3840, reverts: 0)\n\n╭---------------+------------------+-------+---------+----------╮\n| Contract      | Selector         | Calls | Reverts | Discards |\n+===============================================================+\n| EscrowHandler | buy              | 541   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| EscrowHandler | cancel           | 569   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| EscrowHandler | confirmOrRelease | 552   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| EscrowHandler | list             | 524   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| EscrowHandler | plainTransfer    | 521   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| EscrowHandler | postKey          | 577   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| EscrowHandler | withdrawRefund   | 556   | 0       | 0        |\n╰---------------+------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 18575496948460560167\n  Bound result 17346771951523381345\n  Bound result 8815730633987730430\n  Bound result 11992315213526281017\n  Bound result 3050000000000000000\n  Bound result 11941527185510465488\n  Bound result 3339220236967\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 758.81ms (719.28ms CPU time)\n\nRan 13 test suites in 761.43ms (2.63s CPU time): 76 tests passed, 0 failed, 0 skipped (76 total tests)\n","passed":true},{"durationMs":67,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"ArtCollection.approve(address,uint256)\",\"ArtCollection.initialize(address,address,uint256,string,string)\",\"ArtCollection.mint(address,uint256)\",\"ArtCollection.safeTransferFrom(address,address,uint256)\",\"ArtCollection.safeTransferFrom(address,address,uint256,bytes)\",\"ArtCollection.setApprovalForAll(address,bool)\",\"ArtCollection.transferFrom(address,address,uint256)\",\"ArtHouse.acceptOwnership()\",\"ArtHouse.acceptSubmission(uint256)\",\"ArtHouse.approveArtist(address,string,address)\",\"ArtHouse.approveBroker(address)\",\"ArtHouse.approveModule(address,uint256)\",\"ArtHouse.clearPiece(uint256,uint256)\",\"ArtHouse.closeSale(uint256)\",\"ArtHouse.createCollection(address,string,string)\",\"ArtHouse.createModuleCollection(address)\",\"ArtHouse.finalizePiece(uint256,uint256)\",\"ArtHouse.mint(uint256,uint256,address)\",\"ArtHouse.mintWithIMD(uint256,uint256,address,uint256)\",\"ArtHouse.openSale(uint256)\",\"ArtHouse.recordResale(address)\",\"ArtHouse.recordSale(address,address)\",\"ArtHouse.registerPiece(uint256,uint256,bytes32,string,string)\",\"ArtHouse.rejectSubmission(uint256)\",\"ArtHouse.revokeArtist(address)\",\"ArtHouse.revokeBroker(address)\",\"ArtHouse.revokeModule(address)\",\"ArtHouse.seal(uint256)\",\"ArtHouse.setPayout(address)\",\"ArtHouse.setPrices(uint256,uint128,uint128)\",\"ArtHouse.setSplit(uint256,uint16,uint16,uint16)\",\"ArtHouse.submit(bytes32,string)\",\"ArtHouse.transferOwnership(address)\",\"ArtHouse.uploadChunk(uint256,uint256,uint256,bytes)\",\"ArtHouse.withdraw(address)\",\"ArtHouse.withdrawHouse(address)\",\"ArtStorage.acceptOwnership()\",\"ArtStorage.clearPiece(uint256,uint256)\",\"ArtStorage.finalizePiece(uint256,uint256)\",\"ArtStorage.registerPiece(uint256,uint256,bytes32,string,string)\",\"ArtStorage.seal(uint256)\",\"ArtStorage.setHouse(address)\",\"ArtStorage.transferOwnership(address)\",\"ArtStorage.uploadChunk(uint256,uint256,uint256,bytes)\",\"ArtifactsAuction.acceptOwnership()\",\"ArtifactsAuction.bid()\",\"ArtifactsAuction.configure(uint256,uint64)\",\"ArtifactsAuction.settle()\",\"ArtifactsAuction.transferOwnership(address)\",\"ArtifactsAuction.withdrawRefund()\",\"ArtifactsEscrow.buy(uint16,bytes32)\",\"ArtifactsEscrow.cancel(uint16)\",\"ArtifactsEscrow.cancelListing(uint16)\",\"ArtifactsEscrow.confirm(uint16)\",\"ArtifactsEscrow.list(uint16,uint256)\",\"ArtifactsEscrow.postKey(uint16,bytes)\",\"ArtifactsEscrow.release(uint16)\",\"ArtifactsEscrow.withdrawRefund()\",\"ArtifactsFactory.acceptOwnership()\",\"ArtifactsFactory.assignEdition(uint16,address)\",\"ArtifactsFactory.assignSource(address)\",\"ArtifactsFactory.claim(uint16)\",\"ArtifactsFactory.lock()\",\"ArtifactsFactory.postContentKey(uint16,bytes)\",\"ArtifactsFactory.registerViewingKey(bytes32)\",\"ArtifactsFactory.registerViewingKeyFor(address,bytes32)\",\"ArtifactsFactory.setModules(address,address,address)\",\"ArtifactsFactory.setPiece(uint16,(string,string,uint8,string),bytes,bytes32)\",\"ArtifactsFactory.transferOwnership(address)\",\"ArtifactsPiece.approve(address,uint256)\",\"ArtifactsPiece.initialize(uint16,address,bytes)\",\"ArtifactsPiece.safeTransferFrom(address,address,uint256)\",\"ArtifactsPiece.safeTransferFrom(address,address,uint256,bytes)\",\"ArtifactsPiece.setApprovalForAll(address,bool)\",\"ArtifactsPiece.setWrappedKey(bytes)\",\"ArtifactsPiece.transferFrom(address,address,uint256)\",\"ArtifactsSale.acceptOwnership()\",\"ArtifactsSale.buy(address)\",\"ArtifactsSale.closeSale()\",\"ArtifactsSale.configureVRF(address,bytes32,uint256,bool)\",\"ArtifactsSale.openSale()\",\"ArtifactsSale.rawFulfillRandomWords(uint256,uint256[])\",\"ArtifactsSale.retryRequest(uint256)\",\"ArtifactsSale.setPrice(uint256)\",\"ArtifactsSale.transferOwnership(address)\"],\"files\":{\".gitignore\":4,\"README.md\":162,\"foundry.toml\":18,\"remappings.txt\":2,\"script/Deploy.s.sol\":50,\"src/ArtCollection.sol\":95,\"src/ArtHouse.sol\":440,\"src/ArtStorage.sol\":166,\"src/ArtifactsAuction.sol\":101,\"src/ArtifactsEscrow.sol\":165,\"src/ArtifactsFactory.sol\":219,\"src/ArtifactsPiece.sol\":120,\"src/ArtifactsSale.sol\":195,\"src/HouseOwnable.sol\":18,\"src/interfaces/IArtifactsFactory.sol\":32,\"src/libraries/ArtifactsSeal.sol\":105,\"src/libraries/SSTORE2.sol\":34,\"test/ArtHouse.t.sol\":467,\"test/Artifacts.t.sol\":680,\"test/Deploy.t.sol\":52,\"test/FailurePaths.t.sol\":721,\"test/Fixture.sol\":109,\"test/Invariants.t.sol\":656,\"test/mocks/B64.sol\":46,\"test/mocks/Mocks.sol\":73},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"c9c52389921f55874052a096a9441e7a8e116e0e433297534785e16960ae2c0c","verifiedTreeHash":"82f3c9f1b88abdcd95bdfafc74301cdbe21a948f","verifierVersion":"0.1.0+7129bf9d"},{"checks":[{"durationMs":93873,"exitCode":0,"name":"build","output":"Compiling 68 files with Solc 0.8.26\nSolc 0.8.26 finished in 93.78s\nCompiler run successful!\n","passed":true},{"durationMs":191,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/Deploy.t.sol:DeployTest\n[PASS] test_constructorsRejectZeroReferences() (gas: 4160)\n[PASS] test_deployWiresOwnersAndReferencesWithoutPostCalls() (gas: 27295585)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.34ms (1.07ms CPU time)\n\nRan 2 tests for test/Artifacts.t.sol:ArtifactsWiringTest\n[PASS] test_correctWiringOpens() (gas: 271001)\n[PASS] test_openSaleAndConfigureNeedTheFactoryToNameThem() (gas: 267518)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 16.25ms (363.89µs CPU time)\n\nRan 7 tests for test/Artifacts.t.sol:ArtifactsAuctionTest\n[PASS] test_bidsMustBeatReserveAndFivePercentIncrement() (gas: 532721)\n[PASS] test_configureNeedsLockedFactory() (gas: 45450)\n[PASS] test_configureRules() (gas: 303622)\n[PASS] test_dustBidsStillNeedAStrictIncrement() (gas: 287290)\n[PASS] test_lateBidExtendsEndByTenMinutes() (gas: 297834)\n[PASS] test_settleAssignsSourceAndPaysLedger() (gas: 1208777)\n[PASS] test_settleWhileRevokedHoldsProceedsUntilForwarded() (gas: 621868)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 16.29ms (1.87ms CPU time)\n\nRan 10 tests for test/Artifacts.t.sol:ArtifactsEscrowTest\n[PASS] test_buyTakesExactPriceAndHoldsPiece() (gas: 800600)\n[PASS] test_buyerCancelsUntilKeyPostedAndIsRefundedByPull() (gas: 717813)\n[PASS] test_cancelRestoresSellerWrappedKey() (gas: 981218)\n[PASS] test_confirmReleasesPaymentThroughLedgerWithRoyalty() (gas: 885058)\n[PASS] test_escrowOperatorRightsAreLimitedToTheEscrow() (gas: 89516)\n[PASS] test_listingRules() (gas: 385028)\n[PASS] test_previousOwnerCannotOverwriteAPostedKey() (gas: 422773)\n[PASS] test_releaseAfter72Hours() (gas: 667555)\n[PASS] test_releaseWhenModuleRevokedHoldsPriceForSellerAndStillDelivers() (gas: 707626)\n[PASS] test_royaltyInfoSurvivesFactoryModuleRevocation() (gas: 111578)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 18.49ms (3.57ms CPU time)\n\nRan 3 tests for test/Artifacts.t.sol:ArtifactsClaimTest\n[PASS] test_claimFlowDeploysOneTokenPieceContract() (gas: 2000951)\n[PASS] test_plainTransferClearsKeyAndPreviousOwnerRewraps() (gas: 1434031)\n[PASS] test_sealTokenURIMatchesReferenceFrame() (gas: 4590716)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 28.68ms (9.54ms CPU time)\n\nRan 3 tests for test/Artifacts.t.sol:ArtifactsRegistryTest\n[PASS] test_assignmentOnlyByItsModuleAfterLock() (gas: 58986981)\n[PASS] test_modulesSetOnceAndNotConfiguredStateReverts() (gas: 265903)\n[PASS] test_setPieceValidationAndLockNeedsAll256() (gas: 59210106)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 48.59ms (33.47ms CPU time)\n\nRan 27 tests for test/ArtHouse.t.sol:ArtHouseTest\n[PASS] testFuzz_splitConservesEveryWei(uint96,uint16,uint16) (runs: 256, μ: 812122, ~: 816448)\nLogs:\n  Bound result 514264337593543950335\n  Bound result 12\n  Bound result 5598\n\n[PASS] test_artistChangesPayoutOnlyWhenApproved() (gas: 164347)\n[PASS] test_brokerShareGoesToHouseWhenUnapprovedOrBuyer() (gas: 1680985)\n[PASS] test_chunksAboveOneSstore2ContractAreRejected() (gas: 5959378)\n[PASS] test_collectionImplementationAndCloneCannotBeReinitializedOrMintedDirectly() (gas: 99597)\n[PASS] test_curatorRepairsOrDropsAnUnfinalizedPiece() (gas: 1028357)\n[PASS] test_customSplitAndRoundingToHouse() (gas: 835865)\n[PASS] test_finalizeChecksContentHashAndClearResets() (gas: 927828)\n[PASS] test_mintSplitsAndEveryPartyWithdraws() (gas: 1467417)\n[PASS] test_mintWithIMDPullsExactPrice() (gas: 1526427)\n[PASS] test_mintWithIMDRejectsFeeOnTransfer() (gas: 1764432)\n[PASS] test_onlyCuratorManagesRoles() (gas: 88212)\n[PASS] test_openSaleNeedsSealAndPrice() (gas: 729120)\n[PASS] test_ownershipIsTwoStepAndRenounceDisabled() (gas: 133497)\n[PASS] test_pieceIdsAndChunkIndexesMustBeNext() (gas: 485383)\n[PASS] test_piecesOver100KBRejected() (gas: 21814734)\n[PASS] test_readCostIsLinearIn100KBOfSmallChunks() (gas: 117093665)\n[PASS] test_reapprovingAnArtistKeepsItsPayout() (gas: 200039)\n[PASS] test_recordSaleOnlyFromApprovedModules() (gas: 151713)\n[PASS] test_revokedArtistLosesWriteAccessUntilReapproved() (gas: 575134)\n[PASS] test_sealNeedsEveryPieceFinalized() (gas: 566500)\n[PASS] test_setSplitOnlyWhileSvgSaleClosed() (gas: 661384)\n[PASS] test_storageWritesOnlyFromHouseAndSetHouseOnce() (gas: 90745)\n[PASS] test_submissionsAcceptedOrRejectedOnce() (gas: 471049)\n[PASS] test_tokenURIEscapesTitleAndDescription() (gas: 1014541)\n[PASS] test_tokenURIIsOnChainBase64JSONWithSVG() (gas: 1090510)\n[PASS] test_withdrawIsNonReentrantAndRevertsForRejectingReceivers() (gas: 2157953)\nSuite result: ok. 27 passed; 0 failed; 0 skipped; finished in 48.06ms (88.87ms CPU time)\n\nRan 7 tests for test/Artifacts.t.sol:ArtifactsSaleTest\n[PASS] testFuzz_randomWordAlwaysLandsOnUnassignedEdition(uint256[8]) (runs: 256, μ: 3124439, ~: 3127076)\n[PASS] test_allEditionsAssignedExactlyOnceThenSoldOut() (gas: 92388470)\n[PASS] test_buyPaysExactPriceThroughLedgerAndRequestsVRF() (gas: 733103)\n[PASS] test_fulfilmentAssignsAndIgnoresStaleOrForeignRequests() (gas: 806188)\n[PASS] test_openSaleNeedsLockedFactory() (gas: 192369)\n[PASS] test_retryAfterMissingCallbackMakesOldRequestStale() (gas: 935730)\n[PASS] test_saleStartsClosedAndNeedsPriceVrfAndLock() (gas: 533089)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 83.83ms (124.97ms CPU time)\n\nRan 8 test suites in 85.24ms (261.52ms CPU time): 61 tests passed, 0 failed, 0 skipped (61 total tests)\n","passed":true},{"durationMs":67,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"ArtCollection.approve(address,uint256)\",\"ArtCollection.initialize(address,address,uint256,string,string)\",\"ArtCollection.mint(address,uint256)\",\"ArtCollection.safeTransferFrom(address,address,uint256)\",\"ArtCollection.safeTransferFrom(address,address,uint256,bytes)\",\"ArtCollection.setApprovalForAll(address,bool)\",\"ArtCollection.transferFrom(address,address,uint256)\",\"ArtHouse.acceptOwnership()\",\"ArtHouse.acceptSubmission(uint256)\",\"ArtHouse.approveArtist(address,string,address)\",\"ArtHouse.approveBroker(address)\",\"ArtHouse.approveModule(address,uint256)\",\"ArtHouse.clearPiece(uint256,uint256)\",\"ArtHouse.closeSale(uint256)\",\"ArtHouse.createCollection(address,string,string)\",\"ArtHouse.createModuleCollection(address)\",\"ArtHouse.dropLastPiece(uint256)\",\"ArtHouse.finalizePiece(uint256,uint256)\",\"ArtHouse.mint(uint256,uint256,address)\",\"ArtHouse.mintWithIMD(uint256,uint256,address,uint256)\",\"ArtHouse.openSale(uint256)\",\"ArtHouse.recordResale(address)\",\"ArtHouse.recordSale(address,address)\",\"ArtHouse.redescribePiece(uint256,uint256,bytes32,string,string)\",\"ArtHouse.registerPiece(uint256,uint256,bytes32,string,string)\",\"ArtHouse.rejectSubmission(uint256)\",\"ArtHouse.revokeArtist(address)\",\"ArtHouse.revokeBroker(address)\",\"ArtHouse.revokeModule(address)\",\"ArtHouse.seal(uint256)\",\"ArtHouse.setPayout(address)\",\"ArtHouse.setPrices(uint256,uint128,uint128)\",\"ArtHouse.setSplit(uint256,uint16,uint16,uint16)\",\"ArtHouse.submit(bytes32,string)\",\"ArtHouse.transferOwnership(address)\",\"ArtHouse.uploadChunk(uint256,uint256,uint256,bytes)\",\"ArtHouse.withdraw(address)\",\"ArtHouse.withdrawHouse(address)\",\"ArtStorage.acceptOwnership()\",\"ArtStorage.clearPiece(uint256,uint256)\",\"ArtStorage.dropLastPiece(uint256)\",\"ArtStorage.finalizePiece(uint256,uint256)\",\"ArtStorage.redescribePiece(uint256,uint256,bytes32,string,string)\",\"ArtStorage.registerPiece(uint256,uint256,bytes32,string,string)\",\"ArtStorage.seal(uint256)\",\"ArtStorage.setHouse(address)\",\"ArtStorage.transferOwnership(address)\",\"ArtStorage.uploadChunk(uint256,uint256,uint256,bytes)\",\"ArtifactsAuction.acceptOwnership()\",\"ArtifactsAuction.bid()\",\"ArtifactsAuction.configure(uint256,uint64)\",\"ArtifactsAuction.forwardProceeds()\",\"ArtifactsAuction.settle()\",\"ArtifactsAuction.transferOwnership(address)\",\"ArtifactsAuction.withdrawRefund()\",\"ArtifactsEscrow.buy(uint16,bytes32)\",\"ArtifactsEscrow.cancel(uint16)\",\"ArtifactsEscrow.cancelListing(uint16)\",\"ArtifactsEscrow.confirm(uint16)\",\"ArtifactsEscrow.list(uint16,uint256)\",\"ArtifactsEscrow.postKey(uint16,bytes)\",\"ArtifactsEscrow.release(uint16)\",\"ArtifactsEscrow.withdrawRefund()\",\"ArtifactsFactory.acceptOwnership()\",\"ArtifactsFactory.assignEdition(uint16,address)\",\"ArtifactsFactory.assignSource(address)\",\"ArtifactsFactory.claim(uint16)\",\"ArtifactsFactory.lock()\",\"ArtifactsFactory.postContentKey(uint16,bytes)\",\"ArtifactsFactory.registerViewingKey(bytes32)\",\"ArtifactsFactory.registerViewingKeyFor(address,bytes32)\",\"ArtifactsFactory.setModules(address,address,address)\",\"ArtifactsFactory.setPiece(uint16,(string,string,uint8,string),bytes,bytes32)\",\"ArtifactsFactory.transferOwnership(address)\",\"ArtifactsPiece.approve(address,uint256)\",\"ArtifactsPiece.initialize(uint16,address,bytes)\",\"ArtifactsPiece.safeTransferFrom(address,address,uint256)\",\"ArtifactsPiece.safeTransferFrom(address,address,uint256,bytes)\",\"ArtifactsPiece.setApprovalForAll(address,bool)\",\"ArtifactsPiece.setWrappedKey(bytes)\",\"ArtifactsPiece.transferFrom(address,address,uint256)\",\"ArtifactsSale.acceptOwnership()\",\"ArtifactsSale.buy(address)\",\"ArtifactsSale.closeSale()\",\"ArtifactsSale.configureVRF(address,bytes32,uint256,bool)\",\"ArtifactsSale.openSale()\",\"ArtifactsSale.rawFulfillRandomWords(uint256,uint256[])\",\"ArtifactsSale.retryRequest(uint256)\",\"ArtifactsSale.setPrice(uint256)\",\"ArtifactsSale.transferOwnership(address)\"],\"files\":{\".gitignore\":4,\"README.md\":202,\"foundry.toml\":18,\"launch.json\":55,\"remappings.txt\":2,\"script/Deploy.s.sol\":50,\"src/ArtCollection.sol\":96,\"src/ArtHouse.sol\":479,\"src/ArtStorage.sol\":208,\"src/ArtifactsAuction.sol\":126,\"src/ArtifactsEscrow.sol\":186,\"src/ArtifactsFactory.sol\":219,\"src/ArtifactsPiece.sol\":128,\"src/ArtifactsSale.sol\":199,\"src/HouseOwnable.sol\":18,\"src/interfaces/IArtifactsFactory.sol\":37,\"src/libraries/ArtifactsSeal.sol\":105,\"src/libraries/JsonEscape.sol\":40,\"src/libraries/SSTORE2.sol\":55,\"test/ArtHouse.t.sol\":637,\"test/Artifacts.t.sol\":860,\"test/Deploy.t.sol\":52,\"test/Fixture.sol\":109,\"test/mocks/B64.sol\":46,\"test/mocks/Mocks.sol\":73},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"ecc263691e21d092548b339e9687fb1966bda78a1d49637db5241bac17c30a09","verifiedTreeHash":"1cf634c147ee577cadf9983de0e55b108e137181","verifierVersion":"0.1.0+7129bf9d"}]}