{"assessments":[],"deployments":[],"fuzz":[],"identity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"interpretation":"Records acceptance and evidence. Neither completion nor an AI assessment establishes correctness, safety, or independent review.","jobId":"4c9f4dc4-b2af-4eb4-a84e-bf8f9bf00e62","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"ba2443a7f0105b74d848cc9fa7b6874eb02041e602c28dc6dce2318879793794","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":"72a1b6ff38dd3a15f3ddbf4ee4a53b897feb590344b562b2997aeed95f5cf8ce","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":"721e711c3bd3fe4425fe7e2e6be0da7469ae203105f02b81e76330dfe04cc187","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":"5ca451a8b02a82007b45fcf8bd3f093d6f4c2fd7448e58c5a51e03770cc09208","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":"8c001309e9ab2d171d63e5efb18949b0dfaea13fa1939a1304648f9d780d58ff","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":"c0acc08f656c29fe92b69e2d58529093746be8d2ab7373a1848a926992875420","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":"aa775f2e28927162c3ad766cc35d7dc1c58976c905a1452643b856932e9542ce","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"4206c2321627f9923376a1e40bfa8b0c41d3d5f643fed2e7e686080a02ccfca2","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 bought by people and AI agents, with agents as brokers, and its first collection ARTIFACTS: 256 encrypted generative pieces, 255 editions and THE SOURCE. Deploy the contracts on Sepolia. Reference: https://github.com/mylesdaughtry/proof-house\n\nToken facts. No fungible token is created and there is no pool; IMD is an existing ERC-20 buyers may pay with. The only tokens are ERC-721 NFTs, minted only when a piece is bought. Token name: \"ARTIFACTS\" (each piece contract \"ARTIFACTS #NNN\"; THE SOURCE \"ARTIFACTS · THE SOURCE\"). Token symbol: \"ARTIFACTS\". Total supply: 256 (1 per piece contract), fixed forever. Later collections take name and symbol from the curator; supply = registered pieces.\n\nDeployment: constructors take only static values, $owner and earlier contracts; nothing is called after deployment. Deploy ArtStorage($owner), ArtHouse($owner, $contract:ArtStorage, IMD 0x44a1cd38474fb1748400e7deb5f8d786cce3f89a), ArtifactsPiece() as a locked EIP-1167 implementation, ArtifactsFactory($owner, $contract:ArtHouse, $contract:ArtifactsPiece); split any contract over the size limit, up to 8. Owner = curator: two-step ownership, no renounceOwnership, ETH sent by call, no upgradeability, no mint bypassing payment.\n\nArtHouse is the ledger. The curator approves and revokes artists (name; payout address only the artist can change, kept on re-approval) and brokers. Each sale splits per collection in basis points (default broker 15%, house 15%, artist 70%; house takes rounding); a broker earns only if approved and not the buyer, else its share goes to the house. Every piece pays a 7.5% (750 bps) ERC-2981 royalty to its artist's payout address. Balances are per currency (ETH, IMD); each party withdraws its own; the house balance goes to the current owner. The curator approves sale modules, like ArtifactsFactory, that record ETH sales here. Buying and withdrawing are nonReentrant, with state and credits written before tokens or ETH move.\n\nPlain SVG collections: ArtStorage keeps SSTORE2 chunks, written only by ArtHouse after the owner's one-time setHouse. registerPiece(collection, pieceId, contentHash, title, description) and uploadChunk(collection, pieceId, index, data) need the next pieceId and index; the curator or the collection's artist uploads; the curator can clear an unfinalized piece; finalizePiece checks keccak256 against contentHash; seal needs every piece finalized; pieces over 100 KB are rejected. Each collection is an ERC-721 + ERC-2981 clone of an ArtCollection that ArtHouse's constructor deploys, with on-chain base64 JSON tokenURI. mint(collection, pieceId, broker) takes exactly the ETH price; mintWithIMD(collection, pieceId, broker, price) needs price to equal the current IMD price and pulls exactly that. Sales start closed; the curator sets prices and opens or closes them. submit(contentHash, previewURI) lets anyone submit work for the curator to accept or reject.\n\nARTIFACTS:\n- Before the sale the curator uploads to ArtifactsFactory, then locks forever, each piece's public traits (title, palette, colour count, direction), its AES-256-GCM encrypted seed (SSTORE2) and keccak256 of its content key.\n- Editions sell at one ETH price the curator sets. The sale starts closed; the curator sets the price (above zero) and opens it, and can change the price only while closed. Buyers pay exactly the price through the ledger, with brokers. Each purchase requests Chainlink VRF v2.5 (coordinator, key hash, subscription set once by the owner after deployment); the callback assigns a random unassigned edition, none twice or skipped.\n- Claim: the buyer registers an X25519 viewing public key, the curator posts the content key wrapped to it, and the claim deploys the piece's contract: an EIP-1167 clone of ArtifactsPiece, a one-token ERC-721 with ERC-2981 holding its ciphertext pointer, traits, commitment and the owner's wrapped key.\n- Each edition's tokenURI is on-chain JSON whose image is a seal: the RULES.md R8 frame with the public traits and no dots.\n- Resale 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 transfers stay allowed; the previous owner may then post a re-wrapped key.\n- THE SOURCE is piece 256 with its own contract, sold by a single-lot ETH auction: the curator sets reserve and end time; each bid must beat the high bid by 5%; a bid in the final 10 minutes moves the end to 10 minutes after it; outbid bids are refunded by pull; proceeds go through the ledger. Its owner may set a write-once revealed flag. The auction can't open until the curator stores THE SOURCE's metadata with SSTORE2 (the bytes of artifacts/reference/the-source.payload.b64) and locks it forever; a view returns its keccak256. Once revealed, tokenURI returns \"data:application/json;base64,\" plus those bytes copied as is, under 10M gas. Until then it shows the seal.\n- Views: price, remaining, editions, piece contracts, traits, ciphertexts, commitments, wrapped keys, auction state, revealed.\n\nTests: split invariant per currency, VRF assignment, claim, escrow and re-keying, auction, reveal, access control, reentrancy, seals per RULES.md R8, and THE SOURCE's revealed tokenURI decoding to framed-MASTER.svg byte for byte.","parentJobId":null,"planHash":"8f0d8b9532eb80f517f36ecd8b4c0a41d465e5291531c9336788b3cbc32937f8","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"4c9f4dc4-b2af-4eb4-a84e-bf8f9bf00e62","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-1656-build-contracts-proof-house"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"52252","feedbackHash":"127d52eb216aa775edb4ab68640ab60de4f6e58db07df5aa97c373d365a3aafa","nodeKey":"audit_economics","submissionHash":"ba2443a7f0105b74d848cc9fa7b6874eb02041e602c28dc6dce2318879793794","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51488","feedbackHash":"9da703ea454d758f78ea1921ce7cb292d752601ba67be6d2bfdad2c8a2bbe76c","nodeKey":"audit_flow","submissionHash":"72a1b6ff38dd3a15f3ddbf4ee4a53b897feb590344b562b2997aeed95f5cf8ce","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51744","feedbackHash":"1b64a2949ff441fe3f84720ef4220609113bcfe7be29736617c03f6dfa5353f2","nodeKey":"audit_judge","submissionHash":"9f0ce7117069905034adcf1324aa65853f1cbf486d7336b0112369cec8dd4e93","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50995","feedbackHash":"2976b3636f17229280562af69decfbcb2b7d394d3273e083c2c3151934155b9c","nodeKey":"audit_judge","submissionHash":"721e711c3bd3fe4425fe7e2e6be0da7469ae203105f02b81e76330dfe04cc187","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50903","feedbackHash":"04c685a927b87eda3064265844c865dfc8c95f23050489efa1e43e790aafd4d2","nodeKey":"audit_math","submissionHash":"5ca451a8b02a82007b45fcf8bd3f093d6f4c2fd7448e58c5a51e03770cc09208","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52468","feedbackHash":"8f80511598e566a501baba080f2c4227c1522214e78c56fbca55e88befa97c60","nodeKey":"audit_permissions","submissionHash":"8c001309e9ab2d171d63e5efb18949b0dfaea13fa1939a1304648f9d780d58ff","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52129","feedbackHash":"424554a1e90d09fbb7849eb4b88ce67a58fb2ec7dc9ef455ce8cef6cbe87be73","nodeKey":"build_contract_project","submissionHash":"c0acc08f656c29fe92b69e2d58529093746be8d2ab7373a1848a926992875420","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51511","feedbackHash":"ddb88387b979eabd0bc68c06b15a848117a490e643a1b93474d05ec49e4e5d22","nodeKey":"build_contract_project","submissionHash":"a341628e5ab0b00d507ce6f9ccad84623fb869da8a0a96117476ff8f0039f509","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"50969","feedbackHash":"ae34ef7f507dfbc76f2a21ca62341a3c9d1e0b0eba75924431a1016ed19f1bae","nodeKey":"manifest","submissionHash":"1b775fec7168756a9cb3c9efe9d690171e21190d100b72a904c421219835a135","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51347","feedbackHash":"0eb68ea49e782b4706cd988deec24f245edfffa7fef63fd1db9d4c5d06f71d54","nodeKey":"manifest","submissionHash":"aa775f2e28927162c3ad766cc35d7dc1c58976c905a1452643b856932e9542ce","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51084","feedbackHash":"0cbffae60ac0c30806234b6fb3454dbad8a66ef63441ecfc9aaee121bb10291e","nodeKey":"write_foundry_tests","submissionHash":"4206c2321627f9923376a1e40bfa8b0c41d3d5f643fed2e7e686080a02ccfca2","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"50952","feedbackHash":"d56f6e94c2a63d67c109a341535adc67f0ed8e837f200570b1ce09a8f93518d8","nodeKey":"write_foundry_tests","submissionHash":"e22094f85fe3a92ea6ceaec2026c28dfd431c55d1574c2a47c7e5f52b26ddae2","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"59d2e7d308c80f5aeafe799aacc386620d8ba0735e1d90a0e3199efb214f0d41","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":"f40ae864203d3e727370b98c9b96065e7bcd84365dde39bdc8b0e05d609ec441","device":"93ca4a1020037bf1","findings":[],"hash":"1b775fec7168756a9cb3c9efe9d690171e21190d100b72a904c421219835a135","nodeId":"ce6b60a1-5fdc-4b53-a513-2fc3f1181f07","outcome":"completed","summary":"I've written `launch.json` for the five contracts in the accepted source, in the order each depends on the last. All 38 tests pass with the verifier's fuzz seed, and `launch.json` is the only file I changed.\n\n| # | Contract | Constructor arguments |\n|---|---|---|\n| 1 | `ArtStorage` | `$owner` |\n| 2 | `ArtHouse` | `$owner`, `$contract:ArtStorage`, IMD `0x44a1cd38474fb1748400e7deb5f8d786cce3f89a` |\n| 3 | `ArtifactsPiece` | none (the locked template every piece contract is copied from) |\n| 4 | `ArtifactsFactory` | `$owner`, `$contract:ArtHouse`, `$contract:ArtifactsPiece` |\n| 5 | `ArtifactsSource` | `$owner`, `$contract:ArtHouse`, `$contract:ArtifactsPiece` |\n\n- **Fifth contract:** the brief lists four contracts, but the accepted source moved THE SOURCE's auction out of `ArtifactsFactory` into its own `ArtifactsSource` to stay under the contract size limit. The brief allows splits like this, and `script/Deploy.s.sol` and the README use the same arguments.\n- **Sizes:** every contract that gets deployed fits under the 24,576-byte limit. The largest is `ArtifactsPiece` at 18,472 bytes.\n- **Size warning:** `forge build --sizes` still reports that some contract is over the limit. It isn't one of these five, and I didn't check which one it is.\n- **Notes:** at about 1,600 characters, well under the 4,000 limit. They describe each contract's role and what the curator does after deployment: call `ArtStorage.setHouse`, then approve and register both sale contracts on `ArtHouse`.\n\nI checked the manifest by reading it against the rules; there's no local tool that validates it.","treeHash":"d8d9f6bd83657cce6e78315da3a8709d79d87b62","usage":{"cachedInputTokens":255368,"inputTokens":14,"model":"claude-opus-5-5","outputTokens":3249,"runtime":"claude","turns":7,"wallClockMs":119727}},{"artifacts":[],"attempt":1,"bundleHash":"6d6b26638271e1143a8a88918e74260f700197890a9da12760ae96c458eb35ad","device":"7c0191a32541eb74","findings":[],"hash":"4206c2321627f9923376a1e40bfa8b0c41d3d5f643fed2e7e686080a02ccfca2","nodeId":"d9a39e2a-5bc3-4ece-8259-9d86acd555e3","outcome":"completed","summary":"The revision is complete: all 100 tests pass with the verifier's fuzz seed, and only files under `test/` changed.\n\n**What was wrong.** The implementation changed after my suite was accepted, and three of my tests encoded the old rules:\n\n- A unit test expected the paid seller to re-key the piece after an escrow release. The release now spends that right, so the test asserts the delivered key stands and both a garbage and an empty re-key are refused.\n- The escrow handler assumed re-keys were repeatable and unbounded. It now derives success from the new rules (previous owner only, empty slot, non-empty key, right spent on use), checks that a refused re-key changes nothing, and expects zero viewing keys and empty escrow keys to be rejected.\n- The auction handler computed the minimum raise rounding down. It now uses the round-up formula, with the validity check written independently of it so the two cannot agree by construction.\n\n**What was added.**\n\n- Re-request edges in the adversarial suite: the retry window is exact to the block, the same old id cannot be re-requested twice, a re-request can itself be re-requested after another full window, forgotten ids never fulfil, and sold, remaining, the ledger balance and the buyer's edition count stay fixed. A second test uses a new switchable coordinator mock to show a reverting re-request leaves the original request intact and retryable.\n- The factory invariant handler now re-requests pending purchases, both too early and after the window, and tries it as a stranger. The count invariant also checks every pending id still maps to its buyer with no edition.\n- A fuzzed property that the minimum raise is the exact ceiling of 105% of any high bid, one wei less is refused, and it always strictly exceeds the high bid.\n- Update-piece access edges: an empty collection, an approved artist of a different collection, a pending owner, and a revoked artist are all refused, while the event carries the corrected values and the curator can still finish the piece.\n\nEvery handler action is reached with no reverts across 96 runs of 60 calls. I found no defect that had to be reported rather than tested, so no findings file was written. Fork runs against the live Sepolia coordinator and IMD token remain owed, as before.","treeHash":"d44234a6447e67ffbfc114981b5174429cacc8b4","usage":{"cachedInputTokens":1417033,"inputTokens":514,"model":"claude-fable-5-1","outputTokens":27376,"runtime":"claude","turns":46,"wallClockMs":1070790}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"523ef565dd740e25","findings":[{"citation":"resolved","description":"The raise requirement is highBid + floor(highBid*500/10000). For any highBid < 20 wei the floor is 0 and the check degrades to msg.value < highBid, so a bid exactly equal to the current high bid is accepted and displaces the current high bidder (who is pushed to refunds). The brief requires every bid to beat the high bid by 5%; the lowest reserve the curator may set is 1 wei (setAuction only rejects reserve 0), so the degenerate region is reachable. Seam: boundary x precision (reserve near zero x integer floor). Impact is confined to auctions whose reserve is below 20 wei (bidder displacement without a strictly higher price), hence low. Fix: round the raise up, e.g. uint256 minBid = highBid + (highBid * MIN_RAISE_BPS + 9_999) / 10_000; or additionally require msg.value > highBid.","line":127,"path":"src/ArtifactsSource.sol","reproduction":"State: payload stored and locked. Curator: setAuction(1, block.timestamp + 1 hours). alice: bid{value: 19}(address(0)) -> highBidder=alice, highBid=19. bob: bid{value: 19}(address(0)). Expected: revert BidTooLow (19 does not beat 19 by 5%). Actual: accepted; highBidder=bob, highBid=19, refunds[alice]=19. At highBid=20 the same equal bid correctly reverts (20*500/10000 = 1). Verified with a Foundry test in test/scratch (test_bidEqualWinsAtTinyReserve).","severity":"low","snippet":"        if (highBid != 0 && msg.value < highBid + (highBid * MIN_RAISE_BPS) / 10_000) revert BidTooLow();","title":"ArtifactsSource.bid: the 5% minimum raise truncates to zero below 20 wei, so an equal bid replaces the high bidder"},{"citation":"resolved","description":"postEscrowKey sets keyPosted = true for any calldata, including zero-length bytes. keyPosted is exactly the condition that (a) blocks cancel() for the buyer and (b) enables release() by anyone after 72 hours. So a seller can post \"\" and the buyer's payment becomes unrecoverable while the token arrives with wrappedKey == \"\". The factory's own postWrappedKey rejects an empty key (line 211: if (wrapped.length == 0) revert KeyNotPosted()), so the two posting surfaces are asymmetric. README discloses that a dishonest seller can post a bad key; the zero-length case is the one degenerate input the contract can reject for free. rewrapFor (line 140) has the same gap for the post-transfer path. Boundary: empty bytes input. Fix: in postEscrowKey and rewrapFor, if (wrapped.length == 0) revert KeyNotPosted().","line":183,"path":"src/ArtifactsPiece.sol","reproduction":"Edition claimed by alice. alice: list(1 ether). bob: buy{value: 1 ether}(keccak256(\"bobvk\")). alice: postEscrowKey(\"\") -> succeeds, keyPosted=true, key=\"\". bob: cancel() -> reverts KeyAlreadyPosted (expected: still cancellable because no usable key was posted, or postEscrowKey should have reverted). warp +72h; carol: release() -> succeeds; ownerOf(1)=bob, wrappedKey()=\"\", pendingWithdrawals[alice]=0.925 ether. Verified with a Foundry test in test/scratch (test_emptyEscrowKeyLocksBuyer).","severity":"low","snippet":"        e.key = wrapped;","title":"ArtifactsPiece.postEscrowKey accepts an empty key, which removes the buyer's cancel right and lets the sale release with no wrapped key"},{"citation":"resolved","description":"registerPiece only accepts pieceId == pieceCount, so a piece id can be registered exactly once and its contentHash is immutable afterwards. clearPiece wipes the chunks and size but not the hash, and there is no path to amend or drop a registered piece. finalizePiece requires keccak256(chunks) == contentHash and sealCollection requires every piece finalized. A single mistyped hash (artist or curator) therefore leaves the collection permanently unsealable: setSaleOpen reverts NotSealed forever and every correctly finalized piece in it is stranded. Seam: boundary x invariant (the one-shot registration edge defeats the \"seal needs every piece finalized\" invariant with no recovery). No funds are at risk, so low; the curator must abandon the collection address and recreate it. Fix: let the curator (or artist) re-set contentHash/title/description on an unfinalized piece inside clearPiece or a dedicated setter, or let clearPiece also unregister the piece when it is the last one.","line":256,"path":"src/ArtHouse.sol","reproduction":"Curator: createCollection(\"Plain\",\"PLN\",artist) -> col. artist: registerPiece(col, 0, keccak256(\"typo\"), \"P\", \"D\"); uploadChunk(col, 0, 0, \"<svg/>\"); finalizePiece(col, 0) -> HashMismatch. artist: registerPiece(col, 0, keccak256(\"<svg/>\"), ...) -> BadPieceId. Curator: clearPiece(col, 0); artist: registerPiece(col, 0, keccak256(\"<svg/>\"), ...) -> BadPieceId. Curator: sealCollection(col) -> NotFinalized. Expected: some path to correct the hash; actual: none, collection dead. Verified with a Foundry test in test/scratch (test_wrongContentHashBlocksSeal).","severity":"low","snippet":"        if (pieceId != c.pieceCount) revert BadPieceId();","title":"ArtHouse.registerPiece: a piece registered with a wrong contentHash can never be finalized, so its collection can never be sealed or sold"},{"citation":"resolved","description":"For an input whose length is not a multiple of 3, the last iteration loads a 32-byte word whose low 3 bytes include 1 or 2 bytes beyond data.length. Those bytes contribute to the second-to-last (len%3==1) or third-to-last (len%3==2) output character before the '=' padding is written, so non-zero memory after the buffer corrupts the encoding. This is the defect OpenZeppelin fixed in Base64 v5 by caching and zeroing the word after the data. In this tree the only callers are ArtCollection.tokenURI and ArtifactsPiece.tokenURI; I fuzzed both over 400 length combinations (title, description, svg 1..24000 bytes; edition traits) and every output decoded back to the expected JSON and SVG, so no production path reaches dirty memory today (the one external call in ArtCollection.tokenURI is overwritten by its own returndata copy, and all other buffers are fresh allocations). It is reported as low because any future caller that encodes a buffer sitting before memory written by an event, revert or external-call encoding will emit a wrong character silently. Fix: before the loop, let afterPtr := add(add(data, 0x20), mload(data)); let afterCache := mload(afterPtr); mstore(afterPtr, 0); and restore mstore(afterPtr, afterCache) after it.","line":19,"path":"src/libs/Encoding.sol","reproduction":"Library-level: bytes memory d = new bytes(1); d[0] = 0xff; assembly { mstore(add(d, 0x21), not(0)) } (dirty the word right after the byte); Encoding.base64(d). Expected \"/w==\"; actual \"//==\". With a zeroed tail the same call returns \"/w==\". Verified with a Foundry test in test/scratch (test_base64DirtyMemory). Production tokenURI paths: no failing input found (400 fuzz runs each).","severity":"low","snippet":"                let input := mload(dataPtr)","title":"Encoding.base64 reads up to two bytes past the input and folds them into the last group, so it is only correct when the memory after the buffer is zero"}],"hash":"5ca451a8b02a82007b45fcf8bd3f093d6f4c2fd7448e58c5a51e03770cc09208","nodeId":"49facf25-fe68-4566-aca6-855cc9a7811e","outcome":"completed","summary":"All four snippets match the tree, and the findings file is in place at the repository root with 4 findings and 78 coverage rows. Nothing outside `test/scratch/` and `.imd-findings.json` was written.\n\n**Scope covered.** I applied the Math Precision, Boundary and Numerical Gap guides to every value-moving formula and every external boundary: the basis-point split and its rounding, ERC-2981 royalty math, the auction raise and timing arithmetic, the Fisher-Yates VRF assignment, SSTORE2 sizes and the EIP-170 edge, the 100 KB piece cap, the base64 encoder, and the revealed-tokenURI memory copy. The existing 38 tests pass. I added scratch probes for tiny-value sales, auction boundaries, callback gas, chunk limits, dirty-memory encoding, and a 400-run fuzz of both tokenURI paths.\n\n**Findings, all low.** Nothing in the math loses funds or pays the wrong party.\n\n- **Auction raise floors to zero below 20 wei.** With reserve 1 wei, a 19 wei bid is displaced by another 19 wei bid because 19×500/10000 truncates to 0. The brief requires a strict 5% beat.\n- **Empty escrow key accepted.** A seller posting zero-length bytes flips keyPosted, removes the buyer's cancel right, and the token releases after 72 hours with no wrapped key. The factory's equivalent function already rejects empty input.\n- **Wrong contentHash is permanent.** A piece id registers once, clearPiece keeps the hash, so one typo leaves the collection unsealable forever. No funds at risk.\n- **Base64 encoder relies on zeroed tail memory.** It reads up to two bytes past the input into the last group. Both production callers fuzzed clean, so this is latent, but it is the bug OpenZeppelin fixed in v5.\n\n**What holds.** Per-currency conservation holds down to 1 wei with the house taking the rounding. The VRF callback measured 165k gas cold against the 250k limit. The chunk limit of 24,575 bytes writes and 24,576 reverts. lockPayload costs 7.74M gas and the revealed tokenURI stays under 10M. Every listed entry point has a coverage row and none is marked unreached.","treeHash":null,"usage":{"cachedInputTokens":1489589,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":48413,"runtime":"claude","turns":34,"wallClockMs":957642}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"b5e3297a04468fd3","findings":[],"hash":"721e711c3bd3fe4425fe7e2e6be0da7469ae203105f02b81e76330dfe04cc187","nodeId":"2714ef68-eeaf-4267-ac32-c0813d08afc1","outcome":"completed","summary":"All 75 entry points have a row, the tree is clean apart from the findings file, and the scratch tests are removed.\n\n**Outcome: every finding from my previous round is fixed, and I found no new defect.** The findings file holds an empty list and a 78-row coverage record.\n\nSettlement of my earlier findings, each re-run against the revised code:\n\n- **Seller keeps rewrapFor rights after release (medium).** Fixed. `release()` now deletes `previousOwner`, and `rewrapFor` is one-shot and non-empty. The attached proof now fails only because the seller's call reverts `NotPreviousOwner`, which is the behaviour the finding asked for, so the author's dispute about the proof's assertion form is correct. The wrapped key stays `rewrapped-for-bob`.\n- **Wrong contentHash permanently strands a collection (medium).** Fixed by the new `updatePiece`. I reproduced the typo, correction, finalize and seal path, plus the `Finalized`, `Sealed`, `NotCurator` and `BadPieceId` failure paths.\n- **Revoked artist keeps curation rights (low).** Fixed. The artist path in the shared guard now requires approval. Register, upload, update and finalize all revert for a revoked artist, and re-approval restores them.\n- **Empty escrow key accepted (low).** Fixed. `postEscrowKey(\"\")` reverts and the buyer can still cancel.\n- **5% raise rounds to zero under 20 wei (low).** Fixed. Equal bids of 19 and 20 wei revert, and the 1 ether to 1.05 ether threshold is unchanged.\n- **Unfulfilled VRF request has no recovery (low).** Fixed by owner-only `rerequestRandomness` after 7,200 blocks. The old id is rejected afterwards, a second re-request of the same id reverts, and exactly one edition is assigned with `sold` unchanged.\n- **Piece accepts a zero viewing key (info).** Fixed.\n- **Split read at sale time (info).** Documented in the README trust section as agreed.\n\nOther checks this round: the full suite passes with 100 tests, every runtime is under the EIP-170 limit with the largest at 18,537 bytes, and the manifest's five contracts and constructor arguments match the code. The slither and aderyn leads traced to intended behaviour, so none became a finding.","treeHash":null,"usage":{"cachedInputTokens":997473,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":21161,"runtime":"claude","turns":29,"wallClockMs":515451}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"5835e48821d8827d","findings":[{"citation":"resolved","description":"_beforeTransfer runs for the escrow release transfer too (release() sets _releasing and calls _transfer(seller, buyer)), so after a keyed resale `previousOwner` is the seller. rewrapFor() (line 137: `if (msg.sender != previousOwner && msg.sender != controller) revert NotPreviousOwner();`) then lets the already-paid seller replace `wrappedKey` with arbitrary bytes at any time, with no limit and no way for the new owner to revoke it until the token moves again. The key the seller posted in escrow (escrow.key) is deleted at release and is not emitted in any event, so once overwritten the buyer's only on-chain copy of the content key is gone (recoverable only from archive state). The brief grants the re-wrap right to the previous owner only after a plain transfer, which clears the key; after an escrow sale the key was agreed and paid for and must not be writable by the seller. Fix: in release() clear `previousOwner` (or set a flag that disables rewrapFor until the next plain transfer), e.g. `previousOwner = address(0);` after `_transfer` in release().","line":240,"path":"src/ArtifactsPiece.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ArtStorage} from \"src/ArtStorage.sol\";\nimport {ArtHouse} from \"src/ArtHouse.sol\";\nimport {ArtifactsPiece} from \"src/ArtifactsPiece.sol\";\nimport {ArtifactsFactory} from \"src/ArtifactsFactory.sol\";\nimport {Traits, IVRFCoordinatorV2Plus} from \"src/interfaces/Interfaces.sol\";\n\ncontract ProofCoordinator {\n    uint256 public nextId = 1;\n    mapping(uint256 => address) public consumer;\n\n    function requestRandomWords(IVRFCoordinatorV2Plus.RandomWordsRequest calldata) external returns (uint256 id) {\n        id = nextId++;\n        consumer[id] = msg.sender;\n    }\n\n    function fulfill(uint256 id, uint256 word) external {\n        uint256[] memory words = new uint256[](1);\n        words[0] = word;\n        ArtifactsFactory(consumer[id]).rawFulfillRandomWords(id, words);\n    }\n}\n\ncontract ProofToken {\n    mapping(address => uint256) public balanceOf;\n\n    function transfer(address, uint256) external pure returns (bool) {\n        return true;\n    }\n\n    function transferFrom(address, address, uint256) external pure returns (bool) {\n        return true;\n    }\n}\n\n/// After a keyed escrow sale the seller remains `previousOwner` and can overwrite the buyer's wrapped key.\ncontract EscrowRekeyProof is Test {\n    address curator = makeAddr(\"curator\");\n    address artist = makeAddr(\"artist\");\n    address alice = makeAddr(\"alice\");\n    address bob = makeAddr(\"bob\");\n    address carol = makeAddr(\"carol\");\n\n    ProofCoordinator vrf;\n    ArtHouse house;\n    ArtifactsFactory factory;\n\n    function setUp() public {\n        vrf = new ProofCoordinator();\n        ArtStorage store = new ArtStorage(curator);\n        house = new ArtHouse(curator, address(store), address(new ProofToken()));\n        ArtifactsPiece impl = new ArtifactsPiece();\n        factory = new ArtifactsFactory(curator, address(house), address(impl));\n        vm.startPrank(curator);\n        store.setHouse(address(house));\n        house.approveArtist(artist, \"A\");\n        house.setSaleModule(address(factory), true);\n        house.registerModuleCollection(address(factory), artist);\n        factory.configureVRF(address(vrf), keccak256(\"k\"), 1, false);\n        for (uint256 i = 1; i <= 255; ++i) {\n            factory.setPiece(\n                i, Traits(\"T\", \"PRISM\", 3, \"HORIZONTAL\"), abi.encodePacked(\"c\", i), keccak256(abi.encodePacked(i))\n            );\n        }\n        factory.lock();\n        factory.setPrice(1 ether);\n        factory.setSaleOpen(true);\n        vm.stopPrank();\n        vm.deal(alice, 10 ether);\n        vm.deal(bob, 10 ether);\n    }\n\n    function test_sellerCannotOverwriteKeyAfterEscrowRelease() public {\n        // alice buys and claims an edition\n        vm.prank(alice);\n        uint256 id = factory.buy{value: 1 ether}(address(0));\n        vrf.fulfill(id, 7);\n        uint256 edition = factory.requestEdition(id);\n        vm.prank(alice);\n        factory.registerViewingKey(keccak256(\"alice-vk\"));\n        vm.prank(curator);\n        factory.postWrappedKey(edition, \"wrapped-for-alice\");\n        vm.prank(alice);\n        ArtifactsPiece piece = ArtifactsPiece(factory.claim(edition));\n\n        // keyed resale: alice -> bob, released on the 72h path by a third party\n        vm.prank(alice);\n        piece.list(2 ether);\n        vm.prank(bob);\n        piece.buy{value: 2 ether}(keccak256(\"bob-vk\"));\n        vm.prank(alice);\n        piece.postEscrowKey(\"GOOD-KEY-FOR-BOB\");\n        vm.warp(block.timestamp + 72 hours);\n        vm.prank(carol);\n        piece.release();\n        assertEq(piece.ownerOf(1), bob);\n        assertEq(piece.wrappedKey(), \"GOOD-KEY-FOR-BOB\");\n\n        // the paid seller tries to corrupt the buyer's only on-chain copy of the key\n        vm.prank(alice);\n        (bool ok,) = address(piece).call(abi.encodeWithSelector(ArtifactsPiece.rewrapFor.selector, bytes(\"garbage\")));\n        ok; // whether it reverts or not, the released key must survive\n        assertEq(piece.wrappedKey(), \"GOOD-KEY-FOR-BOB\", \"seller overwrote the key agreed in escrow\");\n    }\n}","reproduction":"1) alice claims an edition (piece P). 2) alice: P.list(2 ether). 3) bob: P.buy{value: 2 ether}(bobViewingKey). 4) alice: P.postEscrowKey(\"GOOD-KEY-FOR-BOB\"). 5) warp 72h; carol: P.release() -> ownerOf(1)==bob, wrappedKey()==\"GOOD-KEY-FOR-BOB\". 6) alice: P.rewrapFor(\"garbage\") -> succeeds. Expected: revert NotPreviousOwner (or no change), wrappedKey stays \"GOOD-KEY-FOR-BOB\". Actual: wrappedKey()==\"garbage\" and alice can repeat forever. Test: test/scratch/EscrowRekeyProof.t.sol fails on current code with 'seller overwrote the key agreed in escrow'.","severity":"medium","snippet":"        previousOwner = from;\n        delete wrappedKey;","title":"Escrow seller stays previousOwner after release and can overwrite the buyer's wrapped key via rewrapFor"},{"citation":"resolved","description":"postEscrowKey does not require `wrapped.length > 0` (ArtifactsFactory.postWrappedKey does: `if (wrapped.length == 0) revert KeyNotPosted();`). A seller can post an empty bytes value: keyPosted becomes true, cancel() reverts KeyAlreadyPosted, and after 72 hours anyone can release() the payment to the seller while the buyer receives the token with an empty wrappedKey. A dishonest seller posting a wrong key is a disclosed limitation, but an empty key is trivially detectable and should not count as 'posted'. Fix: `if (wrapped.length == 0) revert KeyNotPosted();` in postEscrowKey.","line":182,"path":"src/ArtifactsPiece.sol","reproduction":"alice owns piece P and lists it: P.list(2 ether). bob: P.buy{value: 2 ether}(key). alice: P.postEscrowKey(\"\") -> succeeds, escrow.keyPosted == true. bob: P.cancel() -> reverts KeyAlreadyPosted (expected: cancel still allowed because no key was delivered). After 72h carol: P.release() pays alice 2 ether minus royalty; bob holds the token with wrappedKey() == \"\". Verified by test_emptyEscrowKeyBlocksCancel in test/scratch/Leads.t.sol.","severity":"low","snippet":"        e.keyPosted = true;\n        e.key = wrapped;","title":"postEscrowKey accepts an empty key, which removes the buyer's cancel right with nothing delivered"},{"citation":"resolved","description":"The required increment `(highBid * 500) / 10_000` is 0 whenever highBid < 20 wei, so a bid equal to the current high bid passes the check and replaces the high bidder without beating it, contradicting 'each bid must beat the high bid by 5%'. Only reachable when the curator sets a reserve below 20 wei, so the economic impact is negligible, but the rule is violated at that boundary. Fix: require `msg.value > highBid` in addition (or round the increment up).","line":127,"path":"src/ArtifactsSource.sol","reproduction":"curator: storePayloadChunk(\"x\"); lockPayload(); setAuction(1, now + 1h). alice: bid{value: 10}(0) -> highBidder alice. bob: bid{value: 10}(0) -> expected revert BidTooLow; actual succeeds, highBidder == bob, highBid == 10. Verified by test_equalBidAcceptedWhenRaiseRoundsToZero in test/scratch/Leads.t.sol.","severity":"low","snippet":"        if (highBid != 0 && msg.value < highBid + (highBid * MIN_RAISE_BPS) / 10_000) revert BidTooLow();","title":"5% minimum raise rounds to zero for high bids under 20 wei, so an equal bid is accepted"},{"citation":"resolved","description":"_curatorOrArtist checks only `c.artist`, never `artists[c.artist].approved`, so after revokeArtist(artist) the revoked artist can still registerPiece, uploadChunk and finalizePiece on every unsealed collection created for them. Because a registered piece can never be removed (clearPiece only wipes chunks; the contentHash is fixed), a revoked artist can register a piece with an unmatchable contentHash and the collection can never be sealed or opened for sale, and the curator has no remedy except abandoning the collection. Fix: in _curatorOrArtist require `artists[c.artist].approved` for the artist path, and/or let the curator clear the last registered-but-unfinalized piece.","line":242,"path":"src/ArtHouse.sol","reproduction":"curator: createCollection(\"C\",\"C\",artist); registerPiece(col,0,keccak256(\"svg\"),..); uploadChunk(col,0,0,\"svg\"); finalizePiece(col,0); revokeArtist(artist). artist: registerPiece(col,1,keccak256(\"unknown preimage\"),\"junk\",\"\") -> expected revert (artist revoked); actual succeeds. curator: sealCollection(col) -> reverts NotFinalized forever (piece 1 cannot be finalized or removed). Verified by test/scratch/Revoke.t.sol test_revokedArtistStillCuratesAndBlocksSeal.","severity":"low","snippet":"        if (msg.sender != owner && msg.sender != c.artist) revert NotCurator();","title":"revokeArtist does not remove a revoked artist's curation rights on their existing unsealed collections"}],"hash":"72a1b6ff38dd3a15f3ddbf4ee4a53b897feb590344b562b2997aeed95f5cf8ce","nodeId":"cef7fd46-6da6-477f-8cab-8c38532437e9","outcome":"completed","summary":"The review is complete. The findings file is written and the source tree is unchanged.\n\n**Outcome.** The code is well built. All 38 project tests pass, and the core value paths in my area (Execution Trace, Periphery, First Principles) hold: credits are written before any transfer, every withdrawal zeroes before paying, VRF assignment is a correct lazy Fisher-Yates, and the EIP-1167, code-copy and SSTORE2 creation sequences decode correctly by hand. I found four defects, one medium and three low, each with a reproduction run in a scratch test.\n\n**Findings written to `.imd-findings.json`:**\n\n1. **Medium, with proof.** After a keyed escrow sale, the seller remains `previousOwner`, so they can call rewrapFor and overwrite the buyer's wrapped key forever. The escrowed key is deleted at release and never emitted, so the buyer's only on-chain copy can be destroyed by the seller they just paid. The proof test fails on this code and passes with `previousOwner = address(0)` after the release transfer.\n2. **Low.** postEscrowKey accepts empty bytes, which counts as \"posted\", removes the buyer's cancel right, and lets the 72-hour release pay the seller with nothing delivered. The factory's equivalent rejects empty keys.\n3. **Low.** The 5% minimum raise rounds to zero for high bids under 20 wei, so an equal bid replaces the high bidder. Only reachable with a sub-20-wei reserve.\n4. **Low.** revokeArtist does not strip a revoked artist's curation rights on their existing unsealed collections. A revoked artist can register a piece with an unmatchable content hash, and since registered pieces cannot be removed, the collection can never be sealed.\n\n**Coverage.** All 73 listed entry points have a row, 66 holding and 7 pointing at a finding, plus rows for the ledger and auction conservation invariants and for the periphery libraries. I also checked the live IMD token on Sepolia: it has code, reports symbol IMD, and both transfer functions return true, so the ledger's boolean decoding holds. The VRF callback measured about 147k gas against the 250k limit.\n\n**Not reached.** I did not exercise the Chainlink coordinator's failure handling against a real coordinator, and I did not fuzz the seal rendering beyond the project's own byte-for-byte tests.","treeHash":null,"usage":{"cachedInputTokens":1446299,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":47170,"runtime":"claude","turns":29,"wallClockMs":725946}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"c9743cd9807c4c3a","findings":[{"citation":"resolved","description":"_curatorOrArtist (src/ArtHouse.sol:241-243), used by registerPiece, uploadChunk and finalizePiece, only compares msg.sender with c.artist and never reads artists[c.artist].approved. revokeArtist (line 153-156) flips only the approved flag, so revocation has no effect on an artist's ability to write into their existing unsealed collections: they keep writing SSTORE2 chunks through ArtStorage, registering pieces with arbitrary content hashes and finalizing them. The brief makes revocation the curator's only tool against an artist; here it only blocks createCollection/registerModuleCollection for new collections. Combined with finding 2, a revoked artist can front-run the curator's sealCollection with registerPiece(col, pieceCount, bytes32(0), ...) and the collection can never be sealed or sold. Minimal fix: in _curatorOrArtist require artists[c.artist].approved when msg.sender == c.artist (curator path unchanged).","line":242,"path":"src/ArtHouse.sol","reproduction":"State: curator approveArtist(artist,'A'); col = createCollection('C','C',artist); curator revokeArtist(artist) so artists(artist).approved == false. Call as artist: registerPiece(col, 0, keccak256('svg'), 't', 'd'); uploadChunk(col, 0, 0, 'svg'); finalizePiece(col, 0). Expected: each reverts (NotApprovedArtist/NotCurator). Actual: all succeed and pieces(col,0).finalized == true. Verified with test/scratch/Leads.t.sol::test_revokedArtistStillCurates.","severity":"medium","snippet":"        if (msg.sender != owner && msg.sender != c.artist) revert NotCurator();","title":"Revoking an artist does not remove their curation rights: a revoked artist can still register, upload and finalize pieces on their collections"},{"citation":"resolved","description":"registerPiece writes contentHash exactly once per pieceId and requires pieceId == c.pieceCount, so there is no re-register path for an existing id. The curator's recovery tool, clearPiece (src/ArtHouse.sol:279-287), writes only `p.size = 0` and artStorage.clear(...) and leaves p.contentHash untouched. finalizePiece requires keccak256(data) == contentHash and sealCollection requires every registered piece finalized, so one piece whose stored hash matches no uploadable data (bytes32(0), a typo, or a hash deliberately chosen by the collection's artist, who may call registerPiece without the curator) permanently blocks sealing, setSaleOpen and therefore every mint on that ArtCollection clone. Asymmetry: register writes {contentHash,title,description,pieceCount}; clear resets only {size,chunks}. Minimal fix: let clearPiece (or a curator-only re-register) also reset contentHash/title/description, or allow registerPiece to overwrite an unfinalized piece.","line":259,"path":"src/ArtHouse.sol","reproduction":"State: col = createCollection('C','C',artist). artist calls registerPiece(col, 0, bytes32(0), 't', 'd') then uploadChunk(col,0,0,'svg'). finalizePiece(col,0) reverts HashMismatch for any data. Curator calls clearPiece(col,0) (succeeds) then registerPiece(col, 0, keccak256('svg'), 't', 'd') -> reverts BadPieceId; sealCollection(col) -> reverts NotFinalized, forever. Expected: the curator can repair or drop the piece and seal. Verified with test/scratch/Leads.t.sol::test_badContentHashBricksCollection.","severity":"medium","snippet":"        p.contentHash = contentHash;","title":"A piece registered with a wrong contentHash can never be corrected, so the collection can never be sealed or opened (clearPiece resets chunks but not the hash)"},{"citation":"resolved","description":"_beforeTransfer sets previousOwner = from on every transfer, including the escrow release transfer (release() -> _transfer with _releasing = true). After release() stores the escrowed key (`wrappedKey = key`, line 218), the seller is previousOwner and may call rewrapFor(any bytes) at any later time, overwriting the current owner's wrapped key with garbage or empty bytes, repeatedly, with no way for the owner to restore it (only previousOwner/controller may write wrappedKey). The re-key permission was meant for plain transfers only (brief: 'Plain transfers stay allowed; the previous owner may then post a re-wrapped key'), and should not survive once a key for the current owner has been delivered through escrow. Minimal fix: clear previousOwner in release() after the transfer, or make rewrapFor refuse when wrappedKey is already non-empty.","line":137,"path":"src/ArtifactsPiece.sol","reproduction":"State: piece owned by alice with wrappedKey 'k-alice'. alice list(1 ether); bob buy{1 ether}(vk); alice postEscrowKey('k-bob'); bob release(). Now ownerOf(1)==bob and wrappedKey()=='k-bob'. alice calls rewrapFor('garbage'): expected revert NotPreviousOwner (or no effect); actual wrappedKey()=='garbage', and rewrapFor('') then empties it. Verified with test/scratch/Leads.t.sol::test_sellerOverwritesKeyAfterRelease.","severity":"low","snippet":"        if (msg.sender != previousOwner && msg.sender != controller) revert NotPreviousOwner();","title":"previousOwner keeps rewrapFor rights forever, so an escrow seller can overwrite the buyer's wrapped key after release"},{"citation":"resolved","description":"setSplit is onlyOwner and accepts any collection, including the module collections ArtifactsFactory and ArtifactsSource, at any time; _credit reads the split at recordSale time. THE SOURCE's winning bid sits in ArtifactsSource until settle(), so the curator can call setSplit(source, 0, 10000, 0) after the final bid and before anyone calls settle(), redirecting the artist's 70% to the house. The curator is the trusted owner by design, so this is recorded as a privileged-power trust assumption (access x asymmetry seam), not a bypass. If the author wants it closed, snapshot the split when the auction opens or forbid setSplit on a collection whose auction is live.","line":210,"path":"src/ArtHouse.sol","reproduction":"State: source auction open, alice bid 2 ether, endTime passed. Curator calls house.setSplit(address(source), 0, 10000, 0) then anyone calls source.settle(). Actual: balances(artist, ETH) == 0, balances(house, ETH) == 2 ether. Under the split in force when the bid was placed the artist would receive 1.4 ether.","severity":"info","snippet":"        if (!collections[collection].exists) revert UnknownCollection();","title":"Trust assumption: curator may change a collection's split while sale proceeds are in flight (e.g. between the auction's last bid and settle)"},{"citation":"resolved","description":"The factory rejects a zero viewing key (NoViewingKey) and buy() on the piece rejects a zero key, but the piece's standalone registerViewingKey has no check. A current owner who registers 0 (or re-registers 0 after a plain transfer) makes rewrapFor revert with NoViewingKey, so the previous owner cannot post the re-wrapped key. Only the owner themself is affected; reported for symmetry with the factory.","line":130,"path":"src/ArtifactsPiece.sol","reproduction":"bob calls piece.registerViewingKey(bytes32(0)). Expected: revert NoViewingKey (as ArtifactsFactory does). Actual: succeeds, viewingKeyOf(bob) == 0. Verified with test/scratch/Leads.t.sol::test_zeroViewingKeyAccepted.","severity":"info","snippet":"        viewingKeyOf[msg.sender] = key;","title":"ArtifactsPiece.registerViewingKey accepts bytes32(0), unlike ArtifactsFactory.registerViewingKey"}],"hash":"8c001309e9ab2d171d63e5efb18949b0dfaea13fa1939a1304648f9d780d58ff","nodeId":"247ba439-6afe-49a3-8452-5e95218b38de","outcome":"completed","summary":"The review is complete and `.imd-findings.json` holds five substantiated findings plus a coverage row for every one of the 73 listed entry points (all answered, none unreached).\n\n**Findings, by severity**\n\n- **Medium. Revocation does not revoke curation rights.** The curator-or-artist check in ArtHouse compares only against the collection's artist address and never reads the approved flag. A revoked artist still registers, uploads and finalizes pieces on their unsealed collections. Reproduced in a scratch test.\n- **Medium. A wrong contentHash is permanent.** registerPiece writes the hash once and clearPiece resets only chunks and size. One piece whose hash matches no data blocks finalizePiece, so sealCollection and setSaleOpen revert forever for that collection. The artist can do this without the curator, and with the first finding, after being revoked. Reproduced.\n- **Low. Escrow seller keeps rewrap rights after release.** previousOwner is set on the release transfer and never cleared, so the seller can overwrite the buyer's wrapped key with garbage at any later time. Reproduced.\n- **Info, trust assumption. setSplit on in-flight proceeds.** The curator can change THE SOURCE collection's split between the final bid and settle, moving the artist's share to the house. Verified with a scratch test against the fixture. Recorded as a privileged power, not a bypass.\n- **Info. Zero viewing key accepted** on ArtifactsPiece, unlike the factory. Only the registrant is affected.\n\n**What held in my area.** Two-step ownership on all four owned contracts with no renounce path and explicit owner arguments. Implementation locking for both clone templates, with initialization atomic to creation. Sale-module gating of recordSale, house-only writes to ArtStorage and ArtCollection, coordinator-only VRF callback, buyer-or-curator claim, and the auction, escrow and withdrawal guards. No mint path exists outside exact payment at the ledger, claim, or settle.\n\n**Not covered.** The IMD token at the Sepolia address was not checked against the ledger's boolean-return assumption, and the base64 and revealed tokenURI paths are outside my area and were not traced. No finding reached critical or high, so no proof files are attached. The scratch tests under test/scratch are disposable and nothing in src or test was changed.","treeHash":null,"usage":{"cachedInputTokens":1115817,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":39343,"runtime":"claude","turns":29,"wallClockMs":612583}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"81314195390981c7","findings":[{"citation":"resolved","description":"Merged from audit_permissions, audit_economics, write_foundry_tests and audit_flow (same root cause). _beforeTransfer sets previousOwner = from on every transfer including the escrow release (release() -> _transfer), and previousOwner is never cleared. rewrapFor has no one-shot or 'key slot empty' guard, so after release() stored the escrowed key (wrappedKey = key, line 218) the already-paid seller can replace it with arbitrary or empty bytes, repeatedly; the new owner cannot restore it (only previousOwner/controller may write). Same after a plain transfer once the correct re-wrap was posted. Breaks the guarantee that the piece holds the owner's wrapped key; the original remains only in historical calldata. Fix: clear previousOwner in release() after the transfer, and make rewrapFor one-shot (e.g. require wrappedKey.length == 0 or clear previousOwner after posting).","line":137,"path":"src/ArtifactsPiece.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ArtStorage} from \"src/ArtStorage.sol\";\nimport {ArtHouse} from \"src/ArtHouse.sol\";\nimport {ArtifactsPiece} from \"src/ArtifactsPiece.sol\";\nimport {ArtifactsFactory} from \"src/ArtifactsFactory.sol\";\nimport {IVRFCoordinatorV2Plus, Traits} from \"src/interfaces/Interfaces.sol\";\n\ninterface IVRFConsumer {\n    function rawFulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) external;\n}\n\ncontract VrfStub {\n    uint256 public nextId = 1;\n    mapping(uint256 => address) public consumer;\n\n    function requestRandomWords(IVRFCoordinatorV2Plus.RandomWordsRequest calldata) external returns (uint256 id) {\n        id = nextId++;\n        consumer[id] = msg.sender;\n    }\n\n    function fulfill(uint256 id, uint256 word) external {\n        uint256[] memory words = new uint256[](1);\n        words[0] = word;\n        IVRFConsumer(consumer[id]).rawFulfillRandomWords(id, words);\n    }\n}\n\n/// @notice After an escrow release the seller (now `previousOwner`) can overwrite the wrapped key the buyer paid for.\ncontract RewrapOverwriteTest is Test {\n    address curator = makeAddr(\"curator\");\n    address artist = makeAddr(\"artist\");\n    address alice = makeAddr(\"alice\");\n    address bob = makeAddr(\"bob\");\n\n    ArtHouse house;\n    ArtifactsFactory factory;\n    VrfStub vrf;\n\n    function setUp() public {\n        vrf = new VrfStub();\n        ArtStorage store = new ArtStorage(curator);\n        house = new ArtHouse(curator, address(store), address(0x1111));\n        ArtifactsPiece impl = new ArtifactsPiece();\n        factory = new ArtifactsFactory(curator, address(house), address(impl));\n\n        vm.startPrank(curator);\n        store.setHouse(address(house));\n        house.approveArtist(artist, \"Myles\");\n        house.setSaleModule(address(factory), true);\n        house.registerModuleCollection(address(factory), artist);\n        factory.configureVRF(address(vrf), keccak256(\"kh\"), 1, false);\n        for (uint256 i = 1; i <= 255; ++i) {\n            factory.setPiece(\n                i,\n                Traits({title: \"FLUX\", palette: \"PRISM\", colourCount: 3, direction: \"HORIZONTAL\"}),\n                abi.encodePacked(\"ct\", i),\n                keccak256(abi.encodePacked(\"K\", i))\n            );\n        }\n        factory.lock();\n        factory.setPrice(1 ether);\n        factory.setSaleOpen(true);\n        vm.stopPrank();\n        vm.deal(alice, 10 ether);\n        vm.deal(bob, 10 ether);\n    }\n\n    function test_sellerOverwritesDeliveredKeyAfterRelease() public {\n        // alice buys and claims an edition\n        vm.prank(alice);\n        uint256 id = factory.buy{value: 1 ether}(address(0));\n        vrf.fulfill(id, 7);\n        uint256 edition = factory.requestEdition(id);\n        vm.prank(alice);\n        factory.registerViewingKey(keccak256(\"alice-vk\"));\n        vm.prank(curator);\n        factory.postWrappedKey(edition, \"wrapped-for-alice\");\n        vm.prank(alice);\n        ArtifactsPiece piece = ArtifactsPiece(factory.claim(edition));\n\n        // keyed resale escrow: bob pays, alice posts the re-wrapped key, bob confirms\n        vm.prank(alice);\n        piece.list(2 ether);\n        vm.prank(bob);\n        piece.buy{value: 2 ether}(keccak256(\"bob-vk\"));\n        vm.prank(alice);\n        piece.postEscrowKey(\"rewrapped-for-bob\");\n        vm.prank(bob);\n        piece.release();\n        assertEq(piece.ownerOf(1), bob);\n        assertEq(piece.wrappedKey(), \"rewrapped-for-bob\");\n\n        // alice has been paid, yet as `previousOwner` she can still replace the key bob paid for\n        vm.prank(alice);\n        piece.rewrapFor(\"garbage\");\n\n        // expected: the delivered key is still readable from the piece; actual: it is gone\n        assertEq(piece.wrappedKey(), \"rewrapped-for-bob\", \"seller destroyed the key the buyer paid for\");\n    }\n}","reproduction":"Claimed edition piece P owned by alice. alice P.list(2 ether); bob P.buy{value:2 ether}(keccak256('bob-vk')); alice P.postEscrowKey('rewrapped-for-bob'); bob P.release() -> ownerOf(1)==bob, wrappedKey()=='rewrapped-for-bob'. alice P.rewrapFor('garbage'). Expected: revert NotPreviousOwner / key unchanged. Actual: succeeds, wrappedKey()=='garbage'. Reproduced: proof below fails on this tree with 'seller destroyed the key the buyer paid for: 0x67617262616765 != 0x7265...626f62'.","severity":"medium","snippet":"        if (msg.sender != previousOwner && msg.sender != controller) revert NotPreviousOwner();","title":"Previous owner keeps rewrapFor rights forever: an escrow seller (or any prior owner) can overwrite the current owner's wrapped key after it was delivered"},{"citation":"resolved","description":"Merged from audit_permissions, audit_economics and audit_math. registerPiece only accepts pieceId == c.pieceCount, so each id is registered once; clearPiece (lines 279-287) resets only size and chunks, never contentHash/title/description, and nothing removes a piece. finalizePiece requires keccak256(chunks) == contentHash, sealCollection requires every piece finalized, and setSaleOpen(open=true) requires the seal. A mistyped hash (curator or artist), or a hash of unknown preimage registered by the collection's artist (see finding 3), therefore strands the whole collection and all its finalized pieces; the curator's clear tool cannot repair it. Fix: let clearPiece (curator) also reset contentHash or allow re-registering an unfinalized piece.","line":284,"path":"src/ArtHouse.sol","reproduction":"curator createCollection('C','C',artist)=col; artist registerPiece(col,0,keccak256('typo'),'t','d'); uploadChunk(col,0,0,'<svg/>'); finalizePiece(col,0) -> HashMismatch. curator clearPiece(col,0) succeeds; curator registerPiece(col,0,keccak256('<svg/>'),'t','d') -> BadPieceId; sealCollection(col) -> NotFinalized forever, so setSaleOpen(col,true) -> NotSealed forever. Expected: a recovery path. Reproduced with test/scratch/Mine.t.sol::test_wrongHashBricks (all reverts observed as listed).","severity":"medium","snippet":"        p.size = 0;","title":"A registered piece's contentHash can never be corrected: one wrong hash makes the collection permanently unsealable and unsellable"},{"citation":"resolved","description":"Merged from audit_permissions, audit_economics and audit_flow. _curatorOrArtist (used by registerPiece, uploadChunk, finalizePiece) never reads artists[c.artist].approved, so after revokeArtist the revoked artist can still register pieces (with arbitrary hashes, which per finding 2 can permanently block sealing), upload SSTORE2 chunks and finalize. Revocation only stops new collections. Fix: require artists[c.artist].approved on the artist path.","line":242,"path":"src/ArtHouse.sol","reproduction":"curator createCollection('C','C',artist)=col; curator revokeArtist(artist). artist registerPiece(col,0,keccak256('svg'),'t','d'); uploadChunk(col,0,0,'svg'); finalizePiece(col,0). Expected: NotCurator/NotApprovedArtist. Actual: all succeed. Reproduced with test/scratch/Mine.t.sol::test_revokedArtistCurates.","severity":"low","snippet":"        if (msg.sender != owner && msg.sender != c.artist) revert NotCurator();","title":"revokeArtist does not remove the artist's curation rights over their existing unsealed collections"},{"citation":"resolved","description":"Merged from audit_economics, audit_math, write_foundry_tests and audit_flow. postEscrowKey sets keyPosted for zero-length bytes, while ArtifactsFactory.postWrappedKey rejects an empty key (if (wrapped.length == 0) revert KeyNotPosted()). Once keyPosted is true cancel() reverts and anyone may release() after paidAt+72h. A wrong non-empty key cannot be detected on-chain (disclosed), but the empty one can. rewrapFor has the same gap. Fix: revert KeyNotPosted on empty input in postEscrowKey and rewrapFor.","line":182,"path":"src/ArtifactsPiece.sol","reproduction":"alice owns piece P: list(1 ether); bob buy{value:1 ether}(vk); alice postEscrowKey('') -> succeeds; bob cancel() -> KeyAlreadyPosted; warp 72h; anyone release() -> pendingWithdrawals[alice]==0.925 ether, ownerOf(1)==bob, wrappedKey()==''. Expected: postEscrowKey('') reverts. Reproduced: .imd proof Proof_b8387ed52b50 fails on this tree with 'next call did not revert as expected'.","severity":"low","snippet":"        e.keyPosted = true;","title":"postEscrowKey accepts an empty key, which ends the buyer's cancel right and lets the payment release after 72h with no key"},{"citation":"resolved","description":"Merged from audit_math, write_foundry_tests and audit_flow. (highBid*500)/10000 is 0 for highBid < 20 wei, so the check becomes msg.value >= highBid and an equal bid wins, violating 'each bid must beat the high bid by 5%'. setAuction only rejects reserve 0, so reachable with a reserve below 20 wei; negligible value. Fix: round the raise up or also require msg.value > highBid.","line":127,"path":"src/ArtifactsSource.sol","reproduction":"curator storePayloadChunk('x'); lockPayload(); setAuction(1, now+1h). a bid{value:19}; b bid{value:19}. Expected BidTooLow. Actual: highBidder==b, refunds[a]==19. Reproduced with test/scratch/Mine.t.sol::test_equalTinyBid.","severity":"low","snippet":"        if (highBid != 0 && msg.value < highBid + (highBid * MIN_RAISE_BPS) / 10_000) revert BidTooLow();","title":"5% minimum raise rounds to zero below 20 wei, so an equal bid displaces the high bidder"},{"citation":"resolved","description":"From write_foundry_tests. buy() increments sold, credits the ledger and records requestBuyer, but if the coordinator never calls back (e.g. a v2.5 request expiring while the subscription is underfunded, or the consumer removed) there is no owner- or buyer-callable re-request or refund. The buyer has paid with no edition, and since sold counts it, the sale can 'sell out' with editions never assigned. Fix: an owner-callable re-request for a pending requestId older than N blocks.","line":161,"path":"src/ArtifactsFactory.sol","reproduction":"With a coordinator stub that records but never fulfils: alice buy{value:price}(address(0)) returns id. Thereafter requestEdition(id)==0 forever, sold==1, remaining()==254, alice's ETH is credited to artist/house in ArtHouse.balances, and no function in ArtifactsFactory can re-request randomness or refund alice (verified by reading every entry point of ArtifactsFactory).","severity":"low","snippet":"        sold += 1;","title":"A purchase whose VRF request is never fulfilled has no re-request or refund path; the edition slot is consumed forever"},{"citation":"resolved","description":"Merged from audit_permissions and write_foundry_tests. The factory rejects a zero viewing key; the piece does not. Registering zero makes rewrapFor revert NoViewingKey for that owner. Only self-affecting.","line":130,"path":"src/ArtifactsPiece.sol","reproduction":"owner calls piece.registerViewingKey(bytes32(0)): expected revert NoViewingKey (as ArtifactsFactory.registerViewingKey); actual succeeds, viewingKeyOf(owner)==0.","severity":"info","snippet":"        viewingKeyOf[msg.sender] = key;","title":"ArtifactsPiece.registerViewingKey accepts bytes32(0), unlike ArtifactsFactory"},{"citation":"resolved","description":"Merged from audit_permissions and audit_economics. setSplit is onlyOwner, applies to module collections at any time, and _credit reads the split when settle()/buy() calls recordSale. Owner power by design; documented, not a bypass.","line":211,"path":"src/ArtHouse.sol","reproduction":"Source auction: bob bids 1 ether with approved broker B; curator setSplit(source,0,10000,0); after endTime settle(): balances[house]==1 ether, artist and B get 0 instead of 0.7/0.15 ether.","severity":"info","snippet":"        if (uint256(brokerBps) + houseBps + artistBps != BPS) revert BadSplit();","title":"Trust assumption: the curator can change a module collection's split after bids/purchases are committed (read at recordSale time)"}],"hash":"9f0ce7117069905034adcf1324aa65853f1cbf486d7336b0112369cec8dd4e93","nodeId":"2714ef68-eeaf-4267-ac32-c0813d08afc1","outcome":"completed","summary":"The review is finished and written to `.imd-findings.json`. It holds 8 findings (2 medium, 4 low, 2 info) and covers all 73 entry points plus one invariant check. Nothing is critical or high. The existing suite passes (88/88), and the working tree is unchanged; my scratch tests are deleted.\n\n**Medium**\n1. **The previous owner can overwrite the current owner's key at any time** (`src/ArtifactsPiece.sol:137`). After an escrow sale completes, the seller still counts as the previous owner and can replace the key the buyer paid for, as often as they like. The same applies after a plain transfer. Four specialists reported this; I merged them into one finding. One specialist's proof test fails on the current code for that reason, and I attached it.\n2. **One wrong `contentHash` makes a collection unsellable for good** (`src/ArtHouse.sol:284`). `clearPiece` resets the piece's size and chunks but not its hash, and a piece ID can only be registered once. So a mistyped hash means the piece can never be finalized, the collection can never be sealed, and its sale can never open. I confirmed this with a scratch test. Three specialists reported it; merged.\n\n**Low**\n3. Revoking an artist doesn't stop them from registering, uploading or finalizing pieces in their existing collections (`ArtHouse.sol:242`). Combined with finding 2, a revoked artist can block a collection on purpose. Confirmed with a scratch test.\n4. A seller can post an empty escrow key (`ArtifactsPiece.sol:182`). That ends the buyer's right to cancel, and after 72 hours anyone can release the payment to the seller. The factory's equivalent function rejects empty keys. Confirmed by the specialist's proof test.\n5. THE SOURCE's 5% minimum raise rounds down to zero when the high bid is under 20 wei, so a bid equal to the high bid wins (`ArtifactsSource.sol:127`). This only matters if the curator sets a reserve that tiny. Confirmed with a scratch test.\n6. If Chainlink never answers a randomness request, there is no way to re-request or refund (`ArtifactsFactory.sol:161`). The buyer has paid, gets no edition, and the slot still counts as sold. I confirmed this by reading every factory entry point, not with a test.\n\n**Info**\n7. A piece contract accepts an all-zero viewing key, which the factory rejects. Only the owner who registers it is affected.\n8. Trust assumption, not a defect: the curator can change a collection's revenue split after bids are placed, because the split is read when the sale is recorded.\n\n**Dropped or checked and fine**\n- I dropped the base64 dirty-memory issue. It only reproduces when the library is called directly, and the specialist found no failing input through the real `tokenURI` functions.\n- None of the static-analysis warnings held up against the code.\n- All deployed contracts fit the size limits. The size warning comes from the `Deploy` script, which isn't in the manifest.\n- The manifest's constructor arguments match the brief, including the IMD address.","treeHash":null,"usage":{"cachedInputTokens":1091719,"inputTokens":26,"model":"claude-opus-5-5","outputTokens":19506,"runtime":"claude","turns":16,"wallClockMs":523015}},{"artifacts":[],"attempt":1,"bundleHash":"c4d4a2faa99480a098f80f96d173e694dee025ea2cdb8262cef8fdfe26abe5b9","device":"fbcdfc017217af1f","findings":[],"hash":"a341628e5ab0b00d507ce6f9ccad84623fb869da8a0a96117476ff8f0039f509","nodeId":"26d4bfb8-7fa7-4e99-a820-46b6d52fb280","outcome":"completed","summary":"Done. The Proof House contracts, tests and documentation are complete and verified locally.\n\n**What was built** (Foundry project at the repository root, solc 0.8.26, via-IR, `bytecode_hash = \"none\"`, forge-std vendored as plain files):\n\n- `ArtStorage($owner)`: SSTORE2 chunk store, written only by ArtHouse after the one-time `setHouse`.\n- `ArtHouse($owner, ArtStorage, IMD)`: the ledger. Artists with artist-only payout kept on re-approval, brokers, sale modules, per-collection basis-point splits with the house taking rounding and unearned broker shares, balances per currency with pull withdrawals, 7.5% ERC-2981 royalties, plain SVG collections as `ArtCollection` clones with on-chain base64 JSON, curation with the 100 KB cap, exact-price ETH and IMD minting, and open submissions.\n- `ArtifactsPiece()`: locked implementation. One-token ERC-721 + ERC-2981 holding ciphertext pointer, traits, commitment and wrapped key, with keyed resale escrow, 72-hour release, re-keying after plain transfers, the R8 seal tokenURI, and THE SOURCE's write-once reveal.\n- `ArtifactsFactory($owner, ArtHouse, ArtifactsPiece)`: publication and lock of 255 pieces, closed-by-default sale, VRF v2.5 set once by the owner, Fisher-Yates edition assignment in the callback, and claim deploying EIP-1167 clones.\n- `ArtifactsSource($owner, ArtHouse, ArtifactsPiece)`: split out for size. Stores THE SOURCE payload with SSTORE2 and locks it, runs the 5% / 10-minute single-lot auction with pull refunds through the ledger, and settles.\n\n**Verification**: 39 tests pass with the verifier's fuzz seed, `forge build` and `forge fmt --check` pass, every launched runtime is under 24 KB with no DELEGATECALL, CALLCODE or SELFDESTRUCT. Tests cover split invariants per currency, all 255 VRF assignments without repeats or gaps, claim, escrow and re-keying, the auction, reveal, access control, reentrancy, seals byte-for-byte against the reference frame, and THE SOURCE's revealed tokenURI equal to the payload file and decoding to framed-MASTER.svg.\n\n**Two design decisions worth knowing**:\n- THE SOURCE's contract is a full byte copy of ArtifactsPiece's runtime rather than a proxy. A proxy copies the 1.8 MB answer a second time, which pushed the revealed tokenURI to about 13M gas. The copy keeps it near 7M.\n- The reference files live under `reference/`, not `artifacts/`, because the harness's git exclude list silently drops `artifacts/`.\n\n**After launch**, the curator must run the steps listed in the README: `setHouse`, approve the artist, register both ARTIFACTS modules as sale modules and collections, `configureVRF` with Chainlink's Sepolia coordinator and the curator's subscription, publish and lock the pieces, set the price, store and lock the payload, then open the sale and auction. Nothing here is a security audit, and the curator holds every content key until the last claim, as the reference protocol discloses.","treeHash":"efe4d6f9a6942b2181dd9a9a4aba8496f0e735b7","usage":{"cachedInputTokens":3169464,"inputTokens":898,"model":"claude-fable-5-1","outputTokens":94599,"runtime":"claude","turns":56,"wallClockMs":1355672}},{"artifacts":[],"attempt":1,"bundleHash":"d8970fbcf16613921e36f0320722163c560c36049f924709cf1a99c0abb598e4","device":"af9a875696459139","findings":[],"hash":"aa775f2e28927162c3ad766cc35d7dc1c58976c905a1452643b856932e9542ce","nodeId":"ce6b60a1-5fdc-4b53-a513-2fc3f1181f07","outcome":"completed","summary":"The manifest needed no change: launch.json as accepted still describes the current tree exactly, and I left it byte-identical.\n\n**What I verified**\n\n- **Constructors match.** The latest contributor commit (c00dccf) added rerequestRandomness, updatePiece and one-shot re-keying but touched no constructor. The five entries still line up with the signatures: ArtStorage(owner), ArtHouse(owner, ArtStorage, IMD), ArtifactsPiece(), ArtifactsFactory(owner, ArtHouse, ArtifactsPiece), ArtifactsSource(owner, ArtHouse, ArtifactsPiece). The deploy script in `script/Deploy.s.sol` wires them the same way.\n- **Schema shape.** A script checked that the file has only kind, contracts and notes, that names are unique identifiers under 32 characters, that every argument is a string that is `$owner`, a backward `$contract:` reference or a static address, and that notes are a string of 1,605 characters.\n- **Floor rehearsal.** A scratch test in `test/scratch/Manifest.t.sol` deploys the five contracts from a stand-in factory in manifest order with the manifest's arguments, then replays the protected checks: every runtime is present and under the EIP-170 limit, has no DELEGATECALL, CALLCODE or SELFDESTRUCT outside push data, and every owner is the curator rather than the factory or IMD. It also confirms the storage, IMD and piece-implementation wiring and that the piece implementation is locked.\n- **Full suite.** `forge build` succeeds with lint warnings only. `forge test` with the verifier's fuzz seed passes all 49 tests, including the two split-invariant fuzz tests, the VRF assignment over all 255 editions, escrow, auction, reveal and the byte-for-byte revealed tokenURI.\n\n**Working tree.** `git status` is clean apart from my scratch test, which is discarded before submission. No configuration, library or source file was touched.","treeHash":"ccf7efed4921ff05876b3320da6c9483a45087a0","usage":{"cachedInputTokens":536303,"inputTokens":290,"model":"claude-fable-5-1","outputTokens":13299,"runtime":"claude","turns":22,"wallClockMs":368836}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"c0fc4ea4f50e3380","findings":[{"citation":"resolved","description":"release() runs _transfer(seller, buyer), whose _beforeTransfer sets previousOwner = seller, then stores the escrow key in wrappedKey. previousOwner is never cleared and rewrapFor() has no guard that the key slot is empty or that a re-key is pending, so the seller, who has already been credited price - royalty in pendingWithdrawals, can call rewrapFor(anything) at any later time and replace the wrapped key that was the paid-for deliverable of the escrow. The same holds after a plain transfer: the previous owner can post a correct re-wrap and later replace it. The on-chain record that the spec promises (piece holds the owner's wrapped key) is destroyed by a party that has nothing left at stake. Minimal fix: in release() set previousOwner = address(0) (the escrow key already delivered the re-key), and/or only allow rewrapFor while wrappedKey.length == 0.","line":137,"path":"src/ArtifactsPiece.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ArtStorage} from \"src/ArtStorage.sol\";\nimport {ArtHouse} from \"src/ArtHouse.sol\";\nimport {ArtifactsPiece} from \"src/ArtifactsPiece.sol\";\nimport {ArtifactsFactory} from \"src/ArtifactsFactory.sol\";\nimport {IVRFCoordinatorV2Plus, Traits} from \"src/interfaces/Interfaces.sol\";\n\ninterface IVRFConsumer {\n    function rawFulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) external;\n}\n\ncontract VrfStub {\n    uint256 public nextId = 1;\n    mapping(uint256 => address) public consumer;\n\n    function requestRandomWords(IVRFCoordinatorV2Plus.RandomWordsRequest calldata) external returns (uint256 id) {\n        id = nextId++;\n        consumer[id] = msg.sender;\n    }\n\n    function fulfill(uint256 id, uint256 word) external {\n        uint256[] memory words = new uint256[](1);\n        words[0] = word;\n        IVRFConsumer(consumer[id]).rawFulfillRandomWords(id, words);\n    }\n}\n\n/// @notice After an escrow release the seller (now `previousOwner`) can overwrite the wrapped key the buyer paid for.\ncontract RewrapOverwriteTest is Test {\n    address curator = makeAddr(\"curator\");\n    address artist = makeAddr(\"artist\");\n    address alice = makeAddr(\"alice\");\n    address bob = makeAddr(\"bob\");\n\n    ArtHouse house;\n    ArtifactsFactory factory;\n    VrfStub vrf;\n\n    function setUp() public {\n        vrf = new VrfStub();\n        ArtStorage store = new ArtStorage(curator);\n        house = new ArtHouse(curator, address(store), address(0x1111));\n        ArtifactsPiece impl = new ArtifactsPiece();\n        factory = new ArtifactsFactory(curator, address(house), address(impl));\n\n        vm.startPrank(curator);\n        store.setHouse(address(house));\n        house.approveArtist(artist, \"Myles\");\n        house.setSaleModule(address(factory), true);\n        house.registerModuleCollection(address(factory), artist);\n        factory.configureVRF(address(vrf), keccak256(\"kh\"), 1, false);\n        for (uint256 i = 1; i <= 255; ++i) {\n            factory.setPiece(\n                i,\n                Traits({title: \"FLUX\", palette: \"PRISM\", colourCount: 3, direction: \"HORIZONTAL\"}),\n                abi.encodePacked(\"ct\", i),\n                keccak256(abi.encodePacked(\"K\", i))\n            );\n        }\n        factory.lock();\n        factory.setPrice(1 ether);\n        factory.setSaleOpen(true);\n        vm.stopPrank();\n        vm.deal(alice, 10 ether);\n        vm.deal(bob, 10 ether);\n    }\n\n    function test_sellerOverwritesDeliveredKeyAfterRelease() public {\n        // alice buys and claims an edition\n        vm.prank(alice);\n        uint256 id = factory.buy{value: 1 ether}(address(0));\n        vrf.fulfill(id, 7);\n        uint256 edition = factory.requestEdition(id);\n        vm.prank(alice);\n        factory.registerViewingKey(keccak256(\"alice-vk\"));\n        vm.prank(curator);\n        factory.postWrappedKey(edition, \"wrapped-for-alice\");\n        vm.prank(alice);\n        ArtifactsPiece piece = ArtifactsPiece(factory.claim(edition));\n\n        // keyed resale escrow: bob pays, alice posts the re-wrapped key, bob confirms\n        vm.prank(alice);\n        piece.list(2 ether);\n        vm.prank(bob);\n        piece.buy{value: 2 ether}(keccak256(\"bob-vk\"));\n        vm.prank(alice);\n        piece.postEscrowKey(\"rewrapped-for-bob\");\n        vm.prank(bob);\n        piece.release();\n        assertEq(piece.ownerOf(1), bob);\n        assertEq(piece.wrappedKey(), \"rewrapped-for-bob\");\n\n        // alice has been paid, yet as `previousOwner` she can still replace the key bob paid for\n        vm.prank(alice);\n        piece.rewrapFor(\"garbage\");\n\n        // expected: the delivered key is still readable from the piece; actual: it is gone\n        assertEq(piece.wrappedKey(), \"rewrapped-for-bob\", \"seller destroyed the key the buyer paid for\");\n    }\n}","reproduction":"State: alice owns edition piece P (claimed). Calls: alice P.list(2 ether); bob P.buy{2 ether}(bobVk); alice P.postEscrowKey(\"rewrapped-for-bob\"); bob P.release() -> ownerOf(1)==bob, wrappedKey()==\"rewrapped-for-bob\", pendingWithdrawals[alice]==1.85 ether. Then alice P.rewrapFor(\"garbage\"). Expected: revert (alice is a paid, former owner with no re-key duty) and wrappedKey() still \"rewrapped-for-bob\". Actual: call succeeds and wrappedKey() == \"garbage\". Test test/scratch/RewrapOverwrite.t.sol fails on this tree with 'seller destroyed the key the buyer paid for'.","severity":"medium","snippet":"        if (msg.sender != previousOwner && msg.sender != controller) revert NotPreviousOwner();","title":"Escrow seller keeps rewrapFor rights after release and can overwrite the key the buyer paid for"},{"citation":"resolved","description":"registerPiece fixes contentHash at the next pieceId and pieceCount only grows; clearPiece wipes the chunks and size but keeps contentHash, and there is no path to change the hash or drop the piece. finalizePiece needs keccak256(chunks) == contentHash, and sealCollection needs every piece finalized, and setSaleOpen needs the seal. One mistyped hash (or a hash of content that was never meant to be uploaded) therefore permanently blocks the whole collection: every other finalized piece and all upload gas is stranded, and the only remedy is a brand-new collection address. The spec says the curator can clear an unfinalized piece so it can be re-uploaded; with a wrong hash the re-upload can never pass. A revoked or hostile artist can also use this deliberately, since _curatorOrArtist still admits a revoked artist (see finding 4). Minimal fix: let clearPiece (curator) reset contentHash/title/description so the curator or artist can re-register the same pieceId, or allow registerPiece to overwrite an unfinalized, chunk-less piece.","line":284,"path":"src/ArtHouse.sol","reproduction":"State: approved artist A, collection C created by the curator. Calls: A registerPiece(C, 0, keccak256(\"typo\"), ...); A uploadChunk(C, 0, 0, \"<svg/>\"); A finalizePiece(C, 0) -> HashMismatch. curator clearPiece(C, 0) succeeds but pieces(C,0).contentHash is still keccak256(\"typo\"). curator registerPiece(C, 0, keccak256(\"<svg/>\"), ...) -> BadPieceId (pieceCount is 1). curator sealCollection(C) -> NotFinalized forever; setSaleOpen(C, true) -> NotSealed forever. Expected: after clearPiece the piece can be corrected and the collection sealed. Actual: C is permanently unsellable (test/scratch/BrickedCollection.t.sol test_wrongHashCannotBeFixed passes, documenting the dead end).","severity":"medium","snippet":"        p.size = 0;\n        artStorage.clear(collection, pieceId);","title":"A piece registered with a wrong contentHash can never be finalized, re-registered or removed, so its collection can never be sealed or sold"},{"citation":"resolved","description":"The factory's postWrappedKey rejects wrapped.length == 0 (KeyNotPosted) but the escrow's postEscrowKey does not. Once keyPosted is true the buyer can no longer cancel(), and after paidAt + 72h anyone (the seller) can release() and take price - royalty while the buyer receives the token with wrappedKey == \"\". Nothing on-chain can validate a wrapped key, so a garbage key has the same effect, but the empty case is a detectable asymmetry with the factory path and should at least be rejected. Minimal fix: if (wrapped.length == 0) revert KeyNotPosted(); in postEscrowKey.","line":183,"path":"src/ArtifactsPiece.sol","reproduction":"State: alice owns piece P, lists at 1 ether; bob P.buy{1 ether}(bobVk). Calls: alice P.postEscrowKey(\"\") -> succeeds, keyPosted == true. bob P.cancel() -> KeyAlreadyPosted. warp 72h; alice P.release() -> succeeds, pendingWithdrawals[alice] == 0.925 ether, ownerOf(1) == bob, P.wrappedKey() == \"\". Expected: an empty key is refused as it is in ArtifactsFactory.postWrappedKey. Actual: accepted.","severity":"low","snippet":"        e.key = wrapped;","title":"postEscrowKey accepts an empty key, which locks the buyer out of cancel and starts the 72-hour release clock"},{"citation":"resolved","description":"_curatorOrArtist compares msg.sender with c.artist only and never reads artists[c.artist].approved, so revokeArtist does not remove curation rights over the artist's existing unsealed collections. A revoked artist can keep appending pieces (each with an arbitrary contentHash) which, combined with finding 2, blocks sealing and sale of that collection and consumes SSTORE2 deployments paid by the artist. Minimal fix: also require artists[c.artist].approved in _curatorOrArtist when msg.sender is the artist.","line":242,"path":"src/ArtHouse.sol","reproduction":"State: artist A approved, collection C created for A. Calls: curator revokeArtist(A); A registerPiece(C, 0, keccak256(\"x\"), \"P\", \"D\"). Expected: NotCurator / NotApprovedArtist. Actual: succeeds, collections(C).pieceCount == 1 and the piece can never be finalized by the curator (the preimage of the hash is unknown), so sealCollection(C) reverts NotFinalized (test/scratch/BrickedCollection.t.sol test_revokedArtistCanStillRegister shows the register call succeeding).","severity":"low","snippet":"        if (msg.sender != owner && msg.sender != c.artist) revert NotCurator();","title":"A revoked artist can still register, upload and finalize pieces of their unsealed collection"},{"citation":"resolved","description":"Bidders commit ETH against a split they can read at bid time, but _credit reads collections[source].artistBps/brokerBps/houseBps when settle() runs, and ArtHouse.setSplit has no check that an auction or sale is live. The curator (owner) can therefore call setSplit(source, 0, 10000, 0) between the last bid and settle and move the artist's 70% and the broker's 15% to the house. The same applies to ArtifactsFactory sales, where the split can be changed while the sale is open. This is an intentional owner power, reported as a privileged-actor risk rather than a bypass; if the design wants the split fixed per sale, snapshot it in setAuction/setSaleOpen or block setSplit while open.","line":157,"path":"src/ArtifactsSource.sol","reproduction":"State: payload locked, setAuction(1 ether, now+1h), bob bids 1 ether naming approved broker B. Calls: curator setSplit(source, 0, 10000, 0); warp past endTime; anyone settle(). Expected by bidders: balances[artist] += 0.7 ether, balances[B] += 0.15 ether. Actual: balances[house] == 1 ether, artist and broker 0.","severity":"info","snippet":"        house.recordSale{value: highBid}(address(this), highBidder, highBroker);","title":"Trust assumption: THE SOURCE's split is read at settle, so the curator can change it after bids are committed"}],"hash":"ba2443a7f0105b74d848cc9fa7b6874eb02041e602c28dc6dce2318879793794","nodeId":"de9303c6-203c-4fd6-b45d-376392963b16","outcome":"completed","summary":"All snippets match the tree and the findings file is complete. Review done.\n\n**Result:** 5 findings written to `.imd-findings.json` with a coverage row for every one of the 76 listed entry points plus 4 invariant rows. No file in `src/`, `test/` (outside `test/scratch/`), or configuration was changed.\n\n**Findings, most severe first**\n\n1. **Medium, escrow re-key overwrite.** After `release()`, the paid seller stays `previousOwner` and `rewrapFor` has no guard, so the seller can replace the wrapped key the buyer paid for. Proof test under `test/scratch/RewrapOverwrite.t.sol` fails on this tree and is attached as the finding's proof.\n2. **Medium, uncorrectable piece hash.** A wrong `contentHash` in `registerPiece` can never be fixed, since `clearPiece` keeps the hash and pieces cannot be re-registered. The collection can then never be sealed or opened. Confirmed by a scratch test walking the dead end.\n3. **Low, empty escrow key accepted.** `postEscrowKey(\"\")` locks the buyer out of cancel and starts the 72-hour clock, unlike the factory path which rejects an empty key.\n4. **Low, revoked artist keeps curation rights.** The curator-or-artist check never reads the approved flag, so a revoked artist can keep adding pieces, which combined with finding 2 blocks sealing.\n5. **Info, trust assumption.** The curator can change a collection's split while THE SOURCE auction or the editions sale is live. The split is read at settle time.\n\n**What held**\n\n- ETH and IMD conservation in the ledger, the split arithmetic, and pull withdrawals.\n- VRF flow: coordinator-only callback, replay guard, Fisher–Yates assignment, and a measured callback cost of about 165k gas under the 250k limit.\n- Auction rules, refund accounting and settlement proceeds.\n- The IMD token on Sepolia returns a boolean from `transfer`, so IMD withdrawals will not revert on decoding.\n\n**Not reached in depth:** the seal SVG rendering, base64 encoding and the revealed tokenURI assembly, which sit outside the economics area. Their entry points are marked as holding on access grounds only.","treeHash":null,"usage":{"cachedInputTokens":1094170,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":44029,"runtime":"claude","turns":24,"wallClockMs":626862}},{"artifacts":[],"attempt":1,"bundleHash":"6f9caf3960c3f54d2a1d5c2db6d8c0ec062cacfb9b81382a480c92be2457d484","device":"63c29c49a249ab7e","findings":[],"hash":"c0acc08f656c29fe92b69e2d58529093746be8d2ab7373a1848a926992875420","nodeId":"26d4bfb8-7fa7-4e99-a820-46b6d52fb280","outcome":"completed","summary":"The responses file is excluded by the harness's own `.git/info/exclude`, so it is read from the working tree by design. The revision is complete.\n\n**Outcome.** All eight findings reproduced and are answered in `.imd-responses.json`. Seven are fixed in code or docs. The rewrap-overwrite finding is fixed too, but its proof is disputed on form only.\n\n**What changed**\n\n- `src/ArtifactsPiece.sol`: release clears the previous owner's re-key right. rewrapFor is one shot, refuses an empty key, and clears the right after posting. postEscrowKey refuses an empty key. registerViewingKey refuses a zero key.\n- `src/ArtHouse.sol`: a revoked artist loses curation rights over existing collections. New updatePiece lets the curator or approved artist correct an unfinalized piece's hash, title and description without losing uploaded chunks.\n- `src/ArtifactsSource.sol`: the 5% minimum raise rounds up, so every bid strictly exceeds the high bid.\n- `src/ArtifactsFactory.sol`: owner-only rerequestRandomness for a request unanswered for 7,200 blocks. The old id is forgotten so a late callback cannot award a second edition.\n- README: documents all of the above plus the split-at-sale-time trust assumption.\n- Tests: seven new tests across the three suites covering each fix's success and failure paths.\n\n**Why the proof is disputed.** The finding says the expected result is a revert with NotPreviousOwner and an unchanged key, which is what the code now does. The proof calls the seller's late rewrapFor without expectRevert, so it can only pass if an unauthorised call silently succeeds as a no-op. I kept the revert and explained this in the response. A copy of the proof with that one expectRevert added passes.\n\n**Verification.** forge build, forge fmt --check, and the 46-test suite with the verifier's fuzz seed all pass. The verbatim proof now fails only with NotPreviousOwner, the behaviour the finding asked for.","treeHash":"570ffc8d9870d7439bb1aa43f2449afb9a97ad4f","usage":{"cachedInputTokens":1672415,"inputTokens":578,"model":"claude-fable-5-1","outputTokens":30532,"runtime":"claude","turns":47,"wallClockMs":714325}},{"artifacts":[],"attempt":1,"bundleHash":"1cf2699ba39d4033299bfdc3477534fa7a08f0246bd7e98d51fbba9839f7589a","device":"ce6eaff570c608ab","findings":[{"description":"The brief says the buyer may cancel until the key is posted. postEscrowKey marks keyPosted=true for any bytes, including empty bytes, while ArtifactsFactory.postWrappedKey rejects an empty key. A seller who posts \"\" leaves the buyer with no cancel path and, after 72 hours, anyone can release the payment to the seller. The contract cannot verify a wrapped key in general (disclosed in PROTOCOL.md), but a zero-length key is trivially detectable on-chain and should be rejected like the factory does.","line":177,"path":"src/ArtifactsPiece.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ArtStorage} from \"src/ArtStorage.sol\";\nimport {ArtHouse} from \"src/ArtHouse.sol\";\nimport {ArtifactsPiece} from \"src/ArtifactsPiece.sol\";\nimport {ArtifactsFactory} from \"src/ArtifactsFactory.sol\";\nimport {Traits, IVRFCoordinatorV2Plus} from \"src/interfaces/Interfaces.sol\";\n\ninterface IVRFConsumer {\n    function rawFulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) external;\n}\n\ncontract Coord {\n    address public consumer;\n\n    function requestRandomWords(IVRFCoordinatorV2Plus.RandomWordsRequest calldata) external returns (uint256) {\n        consumer = msg.sender;\n        return 1;\n    }\n\n    function fulfill() external {\n        uint256[] memory w = new uint256[](1);\n        IVRFConsumer(consumer).rawFulfillRandomWords(1, w);\n    }\n}\n\n/// @dev Fails on the current code: postEscrowKey(\"\") counts as a posted key, so the buyer can no longer cancel\n/// and anyone may release the payment to the seller after 72 hours. Passes once empty keys are rejected.\ncontract ProofEmptyKeyTest is Test {\n    address curator = makeAddr(\"curator\");\n    address artist = makeAddr(\"artist\");\n    address alice = makeAddr(\"alice\");\n    address bob = makeAddr(\"bob\");\n\n    function test_emptyEscrowKeyIsRejected() public {\n        ArtStorage store = new ArtStorage(curator);\n        ArtHouse house = new ArtHouse(curator, address(store), address(0x1234));\n        ArtifactsPiece impl = new ArtifactsPiece();\n        ArtifactsFactory factory = new ArtifactsFactory(curator, address(house), address(impl));\n        Coord coord = new Coord();\n        vm.startPrank(curator);\n        store.setHouse(address(house));\n        house.approveArtist(artist, \"A\");\n        house.setSaleModule(address(factory), true);\n        house.registerModuleCollection(address(factory), artist);\n        factory.configureVRF(address(coord), keccak256(\"k\"), 1, false);\n        for (uint256 i = 1; i <= 255; ++i) {\n            factory.setPiece(i, Traits(\"FLUX\", \"PRISM\", 3, \"HORIZONTAL\"), \"c\", keccak256(abi.encode(i)));\n        }\n        factory.lock();\n        factory.setPrice(1 ether);\n        factory.setSaleOpen(true);\n        vm.stopPrank();\n        vm.deal(alice, 2 ether);\n        vm.deal(bob, 2 ether);\n        vm.prank(alice);\n        factory.buy{value: 1 ether}(address(0));\n        coord.fulfill();\n        vm.prank(alice);\n        factory.registerViewingKey(keccak256(\"a\"));\n        vm.prank(curator);\n        factory.postWrappedKey(1, \"wrapped\");\n        vm.prank(alice);\n        ArtifactsPiece p = ArtifactsPiece(factory.claim(1));\n\n        vm.prank(alice);\n        p.list(1 ether);\n        vm.prank(bob);\n        p.buy{value: 1 ether}(keccak256(\"b\"));\n        vm.prank(alice);\n        vm.expectRevert(); // an empty wrapped key must not count as posted\n        p.postEscrowKey(\"\");\n        // and the buyer keeps the right to cancel\n        vm.prank(bob);\n        p.cancel();\n        assertEq(p.pendingWithdrawals(bob), 1 ether);\n    }\n}","reproduction":"alice lists for 1 ETH; bob buys with a viewing key; alice calls postEscrowKey(\"\"). Expected: revert (KeyNotPosted or similar). Actual: keyPosted=true; bob's cancel() reverts KeyAlreadyPosted; after 72h anyone's release() pays alice.","severity":"low","title":"postEscrowKey accepts an empty wrapped key, which strips the buyer's cancel right"},{"description":"The brief lets the previous owner post a re-wrapped key after a plain transfer. In release(), _beforeTransfer also sets previousOwner = seller, and rewrapFor has no one-shot or escrow-aware guard, so the seller can overwrite the buyer's confirmed escrow key with garbage at any later time while the buyer holds the token. The original key is still recoverable from past state and logs, so this is griefing of on-chain state rather than loss of access.","line":136,"path":"src/ArtifactsPiece.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ArtStorage} from \"src/ArtStorage.sol\";\nimport {ArtHouse} from \"src/ArtHouse.sol\";\nimport {ArtifactsPiece} from \"src/ArtifactsPiece.sol\";\nimport {ArtifactsFactory} from \"src/ArtifactsFactory.sol\";\nimport {Traits, IVRFCoordinatorV2Plus} from \"src/interfaces/Interfaces.sol\";\n\ninterface IVRFConsumer {\n    function rawFulfillRandomWords(uint256 requestId, uint256[] calldata randomWords) external;\n}\n\ncontract Coord {\n    address public consumer;\n\n    function requestRandomWords(IVRFCoordinatorV2Plus.RandomWordsRequest calldata) external returns (uint256) {\n        consumer = msg.sender;\n        return 1;\n    }\n\n    function fulfill() external {\n        uint256[] memory w = new uint256[](1);\n        IVRFConsumer(consumer).rawFulfillRandomWords(1, w);\n    }\n}\n\n/// @dev Fails on the current code: after a keyed escrow sale the seller stays `previousOwner` and can overwrite\n/// the buyer's confirmed wrapped key with rewrapFor at any later time. Passes once the re-wrap right is limited\n/// to plain transfers (cleared by release) or to a single post.\ncontract ProofRewrapTest is Test {\n    address curator = makeAddr(\"curator\");\n    address artist = makeAddr(\"artist\");\n    address alice = makeAddr(\"alice\");\n    address bob = makeAddr(\"bob\");\n\n    function test_previousOwnerCannotOverwriteConfirmedKey() public {\n        ArtStorage store = new ArtStorage(curator);\n        ArtHouse house = new ArtHouse(curator, address(store), address(0x1234));\n        ArtifactsPiece impl = new ArtifactsPiece();\n        ArtifactsFactory factory = new ArtifactsFactory(curator, address(house), address(impl));\n        Coord coord = new Coord();\n        vm.startPrank(curator);\n        store.setHouse(address(house));\n        house.approveArtist(artist, \"A\");\n        house.setSaleModule(address(factory), true);\n        house.registerModuleCollection(address(factory), artist);\n        factory.configureVRF(address(coord), keccak256(\"k\"), 1, false);\n        for (uint256 i = 1; i <= 255; ++i) {\n            factory.setPiece(i, Traits(\"FLUX\", \"PRISM\", 3, \"HORIZONTAL\"), \"c\", keccak256(abi.encode(i)));\n        }\n        factory.lock();\n        factory.setPrice(1 ether);\n        factory.setSaleOpen(true);\n        vm.stopPrank();\n        vm.deal(alice, 2 ether);\n        vm.deal(bob, 2 ether);\n        vm.prank(alice);\n        factory.buy{value: 1 ether}(address(0));\n        coord.fulfill();\n        vm.prank(alice);\n        factory.registerViewingKey(keccak256(\"a\"));\n        vm.prank(curator);\n        factory.postWrappedKey(1, \"wrapped\");\n        vm.prank(alice);\n        ArtifactsPiece p = ArtifactsPiece(factory.claim(1));\n\n        vm.prank(alice);\n        p.list(1 ether);\n        vm.prank(bob);\n        p.buy{value: 1 ether}(keccak256(\"b\"));\n        vm.prank(alice);\n        p.postEscrowKey(\"good-key-for-bob\");\n        vm.prank(bob);\n        p.release();\n        assertEq(p.ownerOf(1), bob);\n        assertEq(p.wrappedKey(), \"good-key-for-bob\");\n        vm.prank(alice);\n        vm.expectRevert(); // the sale is complete: the seller has no re-wrap right left\n        p.rewrapFor(\"garbage\");\n        assertEq(p.wrappedKey(), \"good-key-for-bob\");\n    }\n}","reproduction":"alice sells to bob through escrow (postEscrowKey(\"good-key-for-bob\"), bob release()). wrappedKey()==\"good-key-for-bob\". alice calls rewrapFor(\"garbage\"). Expected: revert. Actual: wrappedKey()==\"garbage\".","severity":"low","title":"previousOwner keeps an unlimited rewrapFor right, including after a completed keyed escrow sale"},{"description":"bid() requires msg.value >= highBid + highBid*500/10000. For highBid < 20 wei the increment is 0, so a bid equal to the high bid is accepted and takes over the high-bidder slot. Only reachable with a sub-20-wei reserve; a ceiling division or a minimum reserve closes it.","line":127,"path":"src/ArtifactsSource.sol","reproduction":"setAuction(1 wei, now+1h); alice bids 10 wei; bob bids 10 wei. Expected: BidTooLow. Actual: bob becomes high bidder at 10 wei and alice is refunded.","severity":"low","title":"5% minimum raise rounds to zero below 20 wei, so an equal bid is accepted"},{"description":"The factory rejects bytes32(0) as a viewing key; the piece does not. Registering zero later makes rewrapFor revert NoViewingKey for the current owner, which is harmless but inconsistent and can confuse clients that treat a non-reverting registration as valid.","line":129,"path":"src/ArtifactsPiece.sol","reproduction":"owner calls piece.registerViewingKey(bytes32(0)). Expected: revert NoViewingKey (as the factory). Actual: succeeds and viewingKeyOf(owner)==0.","severity":"info","title":"ArtifactsPiece.registerViewingKey accepts the zero key, unlike ArtifactsFactory"},{"description":"buy() records the sale in the ledger, increments sold and requests randomness. If the coordinator never fulfils (subscription drained after the request, callback gas exhausted, consumer removed), the buyer has paid, has no edition, cannot claim, and the slot stays counted in sold, so the 255-edition sale can sell out with editions never assigned. README lists this as a trust assumption for the curator to monitor, but there is no re-request or refund function for the curator either. A curator-callable re-request for a request older than N blocks, or a refund path, would bound the damage.","line":156,"path":"src/ArtifactsFactory.sol","reproduction":"alice buy{value: price}(0) returns requestId; the coordinator never calls rawFulfillRandomWords. Expected: some owner- or buyer-callable path to re-request or refund. Actual: requestEdition(id)==0 forever, sold==1, house holds the ETH split to artist/house.","severity":"low","title":"A purchase whose VRF callback never arrives has no on-chain recourse"}],"hash":"e22094f85fe3a92ea6ceaec2026c28dfd431c55d1574c2a47c7e5f52b26ddae2","nodeId":"d9a39e2a-5bc3-4ece-8259-9d86acd555e3","outcome":"completed","summary":"The findings file is in place at the repository root; the harness excludes it from git deliberately, as it does `test/scratch/`. Everything is verified, so the work is complete.\n\n## Summary\n\n**Delivered** (all under `test/`, nothing outside it touched): 50 new tests on top of the existing 38, all 88 passing both with the verifier's fuzz seed and without it.\n\n**Invariant suites over random call sequences** (`test/invariants/`, each with a handler, several actors, ghost variables, and `fail-on-revert = true` so every handler path is asserted, 0 reverts across ~22,000 calls):\n- `ArtHouseInvariant.t.sol`: ETH and IMD held equal what is owed and equal in-minus-out; nobody withdraws more than credited; the exact split is checked per sale against the live bps with the house taking rounding; supply fixed and seal never lifts; survives split, broker, payout, price and two-step ownership changes mid-sequence.\n- `ArtifactsFactoryInvariant.t.sol`: assigned ≤ sold ≤ 255, every purchase is pending or assigned, no edition twice, ledger holds every purchase, claimed tokens belong to their buyers, replayed and stranger callbacks always rejected.\n- `PieceEscrowInvariant.t.sol`: piece balance equals escrowed payment plus pending withdrawals; one token, one owner, moving only by release or plain transfer; seller keeps the token while a buyer is in escrow; each of list/buy/postKey/cancel/release/transfer/rewrap asserts \"succeeds iff the rules allow it\".\n- `SourceAuctionInvariant.t.sol`: auction balance equals live high bid plus refunds owed; high bid monotone; 5% rule and 10-minute extension checked on every bid; settlement final with proceeds split in the ledger.\n\n**Adversarial edge suite** (`test/Adversarial.t.sol`): zero dependencies and no-code dependencies in constructors, pending owner powerless, revoked artist and broker behaviour, split extremes and a 512-run split fuzz, fee-on-transfer and false-returning IMD rejected, stale IMD price rejected, 24,576-byte chunk rejected, VRF coordinator that reverts or returns a duplicate id, empty callback, 1-wei price, wrapped-key re-posting, EIP-1167 bytecode of a claimed clone, seal byte-for-byte against the R8 reference frame for fuzzed trait vocabularies, escrow boundaries (exact 72 h, operator approvals, rejecting and re-entering receivers), THE SOURCE reveal gate via a stub controller, auction boundaries (exact reserve, exact +5%, exactly 10 minutes left), launched runtimes under EIP-170 with no DELEGATECALL/CALLCODE/SELFDESTRUCT.\n\n**Findings reported** in `.imd-findings.json` rather than tested around, each confirmed by a scratch test that fails on the current code: empty escrow key strips the buyer's cancel right (low, with proof), previous owner can overwrite a confirmed escrow key (low, with proof), 5% raise rounds to zero under 20 wei (low), piece accepts a zero viewing key (info), and an unfulfilled VRF request has no on-chain recourse (low). No critical or high defects were found.\n\n**Still owed**: fork runs against the live Sepolia IMD token and Chainlink coordinator could not run here with no network; the mocks reproduce their documented behaviour only.","treeHash":"b378ba025a7caccf2bd282547b9b12223f52d5ae","usage":{"cachedInputTokens":3676959,"inputTokens":738,"model":"claude-fable-5-1","outputTokens":99238,"runtime":"claude","turns":60,"wallClockMs":1810125}}],"verification":[{"checks":[{"durationMs":44036,"exitCode":0,"name":"build","output":"Compiling 39 files with Solc 0.8.26\nSolc 0.8.26 finished in 43.95s\nCompiler run successful!\n","passed":true},{"durationMs":2070,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/Artifacts.t.sol:ArtifactsPieceEscrowTest\n[PASS] test_escrowCancelBeforeKey_andDelist() (gas: 479568)\n[PASS] test_escrowHappyPath_buyerConfirms() (gas: 896900)\n[PASS] test_escrowTimeoutRelease() (gas: 473128)\n[PASS] test_plainTransferClearsKey_previousOwnerRekeys() (gas: 502300)\n[PASS] test_reentrancyOnEscrowWithdrawBlocked() (gas: 819586)\n[PASS] test_sourceOnlyReveal() (gas: 37353)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 36.70ms (1.73ms CPU time)\n\nRan 18 tests for test/ArtHouse.t.sol:ArtHouseTest\n[PASS] testFuzz_ethSplitInvariant(uint96,bool,bool) (runs: 64, μ: 1155433, ~: 1139447)\n[PASS] testFuzz_imdSplitInvariant(uint96,bool) (runs: 64, μ: 1258658, ~: 1275956)\n[PASS] test_artistPayoutOnlyArtist_keptOnReapproval() (gas: 183316)\n[PASS] test_collectionMintOnlyHouse() (gas: 76575)\n[PASS] test_curationFlow_andFailures() (gas: 916261)\n[PASS] test_customSplit() (gas: 1102714)\n[PASS] test_deploymentWiring() (gas: 96667)\n[PASS] test_mintFailures() (gas: 1494452)\n[PASS] test_onlyCuratorApproves() (gas: 122517)\n[PASS] test_pieceOver100KBRejected() (gas: 21294399)\n[PASS] test_recordSaleOnlyModules() (gas: 76106)\n[PASS] test_reentrancyOnWithdrawBlocked() (gas: 1809358)\n[PASS] test_sealNeedsEveryPieceFinalized() (gas: 517350)\n[PASS] test_setHouseOnce() (gas: 78200)\n[PASS] test_submissions() (gas: 260059)\n[PASS] test_tokenURIOnChain() (gas: 1167025)\n[PASS] test_twoStepOwnership_noRenounce() (gas: 196292)\n[PASS] test_withdrawNothingReverts_andHouseGoesToCurrentOwner() (gas: 1212364)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 41.15ms (71.55ms CPU time)\n\nRan 9 tests for test/Artifacts.t.sol:ArtifactsFactoryTest\n[PASS] test_buyFailures() (gas: 60773432)\n[PASS] test_buyRecordsSaleAndAssignsEveryEditionOnce() (gas: 143287368)\n[PASS] test_claimFlow_andFailures() (gas: 62152804)\n[PASS] test_publishAndLock_accessAndFailures() (gas: 60751769)\n[PASS] test_saleControl() (gas: 60819000)\n[PASS] test_sealTokenURIMatchesR8Frame() (gas: 517384269)\n[PASS] test_viewingKeyZeroRejected() (gas: 32711)\n[PASS] test_vrfCallbackCannotBeReplayed() (gas: 61220414)\n[PASS] test_vrfMustBeConfiguredOnce() (gas: 81590)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 680.88ms (865.33ms CPU time)\n\nRan 5 tests for test/Source.t.sol:ArtifactsSourceTest\n[PASS] test_auctionNeedsLockedPayload() (gas: 521262)\n[PASS] test_auctionReopensWhenNoBids() (gas: 364838)\n[PASS] test_auctionRules() (gas: 5311676)\n[PASS] test_bidReentrancyAndRefundPull() (gas: 680831)\n[PASS] test_revealedTokenURIIsPayloadByteForByte() (gas: 2432252652)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 1.97s (1.97s CPU time)\n\nRan 4 test suites in 1.97s (2.72s CPU time): 38 tests passed, 0 failed, 0 skipped (38 total tests)\n","passed":true},{"durationMs":58,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"ArtCollection.approve(address,uint256)\",\"ArtCollection.initialize(address,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.approveArtist(address,string)\",\"ArtHouse.clearPiece(address,uint256)\",\"ArtHouse.createCollection(string,string,address)\",\"ArtHouse.decideSubmission(uint256,bool)\",\"ArtHouse.finalizePiece(address,uint256)\",\"ArtHouse.mint(address,uint256,address)\",\"ArtHouse.mintWithIMD(address,uint256,address,uint256)\",\"ArtHouse.recordSale(address,address,address)\",\"ArtHouse.registerModuleCollection(address,address)\",\"ArtHouse.registerPiece(address,uint256,bytes32,string,string)\",\"ArtHouse.revokeArtist(address)\",\"ArtHouse.sealCollection(address)\",\"ArtHouse.setBroker(address,bool)\",\"ArtHouse.setPayout(address)\",\"ArtHouse.setPrices(address,uint256,uint256)\",\"ArtHouse.setSaleModule(address,bool)\",\"ArtHouse.setSaleOpen(address,bool)\",\"ArtHouse.setSplit(address,uint16,uint16,uint16)\",\"ArtHouse.submit(bytes32,string)\",\"ArtHouse.transferOwnership(address)\",\"ArtHouse.uploadChunk(address,uint256,uint256,bytes)\",\"ArtHouse.withdraw(address)\",\"ArtHouse.withdrawHouse(address)\",\"ArtStorage.acceptOwnership()\",\"ArtStorage.clear(address,uint256)\",\"ArtStorage.setHouse(address)\",\"ArtStorage.transferOwnership(address)\",\"ArtStorage.write(address,uint256,uint256,bytes)\",\"ArtifactsFactory.acceptOwnership()\",\"ArtifactsFactory.buy(address)\",\"ArtifactsFactory.claim(uint256)\",\"ArtifactsFactory.configureVRF(address,bytes32,uint256,bool)\",\"ArtifactsFactory.lock()\",\"ArtifactsFactory.postWrappedKey(uint256,bytes)\",\"ArtifactsFactory.rawFulfillRandomWords(uint256,uint256[])\",\"ArtifactsFactory.registerViewingKey(bytes32)\",\"ArtifactsFactory.setPiece(uint256,(string,string,uint8,string),bytes,bytes32)\",\"ArtifactsFactory.setPrice(uint256)\",\"ArtifactsFactory.setSaleOpen(bool)\",\"ArtifactsFactory.transferOwnership(address)\",\"ArtifactsPiece.approve(address,uint256)\",\"ArtifactsPiece.buy(bytes32)\",\"ArtifactsPiece.cancel()\",\"ArtifactsPiece.delist()\",\"ArtifactsPiece.initialize(address,address,uint256,address,(string,string,uint8,string),address,bytes32,bytes,bool)\",\"ArtifactsPiece.list(uint256)\",\"ArtifactsPiece.postEscrowKey(bytes)\",\"ArtifactsPiece.registerViewingKey(bytes32)\",\"ArtifactsPiece.release()\",\"ArtifactsPiece.rewrapFor(bytes)\",\"ArtifactsPiece.safeTransferFrom(address,address,uint256)\",\"ArtifactsPiece.safeTransferFrom(address,address,uint256,bytes)\",\"ArtifactsPiece.setApprovalForAll(address,bool)\",\"ArtifactsPiece.setRevealed()\",\"ArtifactsPiece.transferFrom(address,address,uint256)\",\"ArtifactsPiece.withdraw()\",\"ArtifactsSource.acceptOwnership()\",\"ArtifactsSource.bid(address)\",\"ArtifactsSource.lockPayload()\",\"ArtifactsSource.resetPayload()\",\"ArtifactsSource.setAuction(uint256,uint64)\",\"ArtifactsSource.settle()\",\"ArtifactsSource.storePayloadChunk(bytes)\",\"ArtifactsSource.transferOwnership(address)\",\"ArtifactsSource.withdrawRefund()\"],\"files\":{\".gitignore\":3,\"README.md\":134,\"foundry.toml\":25,\"launch.json\":40,\"reference/PROTOCOL.md\":86,\"reference/RULES.md\":135,\"reference/check-framed.js\":25,\"reference/framed/framed-003.svg\":1,\"reference/framed/framed-MASTER.svg\":1,\"reference/rules.js\":106,\"reference/the-source.payload.b64\":1,\"remappings.txt\":1,\"script/Deploy.s.sol\":36,\"src/ArtCollection.sol\":54,\"src/ArtHouse.sol\":447,\"src/ArtStorage.sol\":82,\"src/ArtifactsFactory.sol\":272,\"src/ArtifactsPiece.sol\":326,\"src/ArtifactsSource.sol\":182,\"src/base/ERC721Lite.sol\":122,\"src/base/ReentrancyGuard.sol\":15,\"src/base/TwoStepOwnable.sol\":40,\"src/interfaces/Interfaces.sol\":68,\"src/libs/Clones.sol\":27,\"src/libs/Encoding.sol\":64,\"src/libs/SSTORE2.sol\":41,\"src/libs/Seal.sol\":80,\"test/ArtHouse.t.sol\":364,\"test/Artifacts.t.sol\":362,\"test/Fixture.sol\":99,\"test/Source.t.sol\":221,\"test/mocks/Mocks.sol\":88,\"test/utils/Base64Decode.sol\":76},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"1b775fec7168756a9cb3c9efe9d690171e21190d100b72a904c421219835a135","verifiedTreeHash":"d8d9f6bd83657cce6e78315da3a8709d79d87b62","verifierVersion":"0.1.0+7129bf9d"},{"checks":[{"durationMs":133450,"exitCode":0,"name":"build","output":"Compiling 45 files with Solc 0.8.26\nSolc 0.8.26 finished in 133.37s\nCompiler run successful!\n","passed":true},{"durationMs":9593,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 10 tests for test/Artifacts.t.sol:ArtifactsPieceEscrowTest\n[PASS] test_escrowCancelBeforeKey_andDelist() (gas: 479598)\n[PASS] test_escrowHappyPath_buyerConfirms() (gas: 897142)\n[PASS] test_escrowKeyMustNotBeEmpty() (gas: 276284)\n[PASS] test_escrowTimeoutRelease() (gas: 473392)\n[PASS] test_pieceViewingKeyZeroRejected() (gas: 172988)\n[PASS] test_plainTransferClearsKey_previousOwnerRekeys() (gas: 518575)\n[PASS] test_reentrancyOnEscrowWithdrawBlocked() (gas: 819674)\n[PASS] test_rewrapIsOneShotAndNonEmpty() (gas: 296131)\n[PASS] test_sellerCannotOverwriteKeyAfterRelease() (gas: 659532)\n[PASS] test_sourceOnlyReveal() (gas: 37397)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 31.01ms (2.02ms CPU time)\n\nRan 6 tests for test/Adversarial.t.sol:PieceEdgeTest\n[PASS] test_cancelThenSecondBuyerAndRoyaltyToChangedPayout() (gas: 799105)\n[PASS] test_listingEdges() (gas: 738926)\n[PASS] test_releaseAtExactly72HoursByStranger() (gas: 660829)\n[PASS] test_revealNeedsLockedPayload_andEmptyPayloadURI() (gas: 880566)\n[PASS] test_safeTransferReceivers() (gas: 666148)\n[PASS] test_transferAuthEdges() (gas: 370785)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 36.77ms (2.06ms CPU time)\n\nRan 1 test for test/invariants/SourceAuctionInvariant.t.sol:SourceAuctionInvariantTest\n[PASS]\nSourceAuctionInvariantTest invariants:\n[PASS] invariant_auctionConservation\n[PASS] invariant_highBidMonotone\n[PASS] invariant_settlementIsFinal\n SourceAuctionInvariantTest invariants (runs: 96, calls: 5760, reverts: 0)\n\n╭----------------+--------------------+-------+---------+----------╮\n| Contract       | Selector           | Calls | Reverts | Discards |\n+==================================================================+\n| AuctionHandler | bid                | 816   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| AuctionHandler | payloadStaysLocked | 775   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| AuctionHandler | recordHigh         | 787   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| AuctionHandler | setAuction         | 793   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| AuctionHandler | settle             | 872   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| AuctionHandler | warp               | 893   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| AuctionHandler | withdrawRefund     | 824   | 0       | 0        |\n╰----------------+--------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 200\n  Bound result 1060\n  Bound result 392\n  Bound result 560\n  Bound result 600\n  Bound result 2000000000000009744\n  Bound result 1353\n  Bound result 4000000001457747772\n  Bound result 2\n  Bound result 3700\n  Bound result 1400\n  Bound result 423\n  Bound result 1600\n  Bound result 363347540626008517\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 769.30ms (765.38ms CPU time)\n\nRan 1 test for test/invariants/ArtifactsFactoryInvariant.t.sol:ArtifactsFactoryInvariantTest\n[PASS]\nArtifactsFactoryInvariantTest invariants:\n[PASS] invariant_claimedPiecesBelongToBuyers\n[PASS] invariant_counts\n[PASS] invariant_ledgerHoldsEveryPurchase\n[PASS] invariant_lockedAndPriced\n ArtifactsFactoryInvariantTest invariants (runs: 96, calls: 5760, reverts: 0)\n\n╭----------------+-------------------+-------+---------+----------╮\n| Contract       | Selector          | Calls | Reverts | Discards |\n+=================================================================+\n| FactoryHandler | buy               | 562   | 0       | 0        |\n|----------------+-------------------+-------+---------+----------|\n| FactoryHandler | claim             | 599   | 0       | 0        |\n|----------------+-------------------+-------+---------+----------|\n| FactoryHandler | fulfil            | 568   | 0       | 0        |\n|----------------+-------------------+-------+---------+----------|\n| FactoryHandler | lockedForever     | 573   | 0       | 0        |\n|----------------+-------------------+-------+---------+----------|\n| FactoryHandler | replay            | 576   | 0       | 0        |\n|----------------+-------------------+-------+---------+----------|\n| FactoryHandler | rerequest         | 615   | 0       | 0        |\n|----------------+-------------------+-------+---------+----------|\n| FactoryHandler | setPrice          | 551   | 0       | 0        |\n|----------------+-------------------+-------+---------+----------|\n| FactoryHandler | strangerFulfil    | 572   | 0       | 0        |\n|----------------+-------------------+-------+---------+----------|\n| FactoryHandler | strangerRerequest | 576   | 0       | 0        |\n|----------------+-------------------+-------+---------+----------|\n| FactoryHandler | toggleSale        | 568   | 0       | 0        |\n╰----------------+-------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 1760\n  Bound result 7996306732279200559\n  Bound result 1\n  Bound result 4264337593543950335\n  Bound result 4264337593543950335\n  Bound result 4264337593543950332\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 831.12ms (798.41ms CPU time)\n\nRan 7 tests for test/Adversarial.t.sol:SourceEdgeTest\n[PASS] testFuzz_minimumRaiseIsCeilingOfFivePercent(uint96,uint96) (runs: 512, μ: 542710, ~: 542820)\nLogs:\n  Bound result 1806830020967297409571\n  Bound result 1897477873093902723102\n\n[PASS] testFuzz_payloadHashIsKeccakOfConcatenation(bytes,bytes,bytes) (runs: 64, μ: 416022, ~: 412496)\n[PASS] test_bidBoundaries() (gas: 800653)\n[PASS] test_payloadChunkLimits() (gas: 5433939)\n[PASS] test_setAuctionEdges() (gas: 373429)\n[PASS] test_settleEdges_brokerSplit() (gas: 5240729)\n[PASS] test_settleWithUnapprovedBrokerPaysHouse() (gas: 4526377)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 970.34ms (61.01ms CPU time)\n\nRan 21 tests for test/Adversarial.t.sol:ArtHouseEdgeTest\n[PASS] testFuzz_everyoneWithdrawsExactlyTheirShare(uint96,uint96,uint96) (runs: 256, μ: 1944088, ~: 1946252)\nLogs:\n  Bound result 22110\n  Bound result 102066948\n  Bound result 2920324621732422699540872995\n\n[PASS] testFuzz_splitConservesAndHouseTakesRounding(uint16,uint16,uint96,uint8) (runs: 512, μ: 177879, ~: 173245)\nLogs:\n  Bound result 4\n  Bound result 5551\n  Bound result 54761851\n\n[PASS] test_artistEdges() (gas: 1246500)\n[PASS] test_clearThenReuploadKeepsSizeAccounting() (gas: 40763178)\n[PASS] test_collectionErc721Edges() (gas: 1450480)\n[PASS] test_constructorsDoNotCallDependencies() (gas: 34892)\n[PASS] test_constructorsRejectZeroDependencies() (gas: 4484)\n[PASS] test_curationEdges() (gas: 779300)\n[PASS] test_mintWithIMD_falseReturnRejected() (gas: 1083739)\n[PASS] test_mintWithIMD_feeOnTransferRejected() (gas: 1309376)\n[PASS] test_mintWithIMD_stalePriceRejected() (gas: 1224775)\n[PASS] test_mintZeroPricesReject() (gas: 941678)\n[PASS] test_moduleCollectionsHaveNoPlainCuration() (gas: 331171)\n[PASS] test_pendingOwnerHasNoPowerBeforeAccepting() (gas: 196075)\n[PASS] test_registerModuleCollectionTwiceReverts() (gas: 44701)\n[PASS] test_revokedBrokerKeepsEarnedBalance_butEarnsNoMore() (gas: 1246184)\n[PASS] test_sealEmptyCollectionRevertsAndSealIsOnce() (gas: 656753)\n[PASS] test_splitExtremes() (gas: 1249821)\n[PASS] test_submissionEdges() (gas: 245710)\n[PASS] test_updatePieceAccessEdges() (gas: 1013518)\n[PASS] test_withdrawToRevertingPayoutKeepsBalance() (gas: 1311098)\nSuite result: ok. 21 passed; 0 failed; 0 skipped; finished in 970.38ms (108.26ms CPU time)\n\nRan 3 tests for test/Adversarial.t.sol:RuntimeTest\n[PASS] test_implementationsAreLocked() (gas: 103776)\n[PASS] test_launchedRuntimesAreBoundedAndHaveNoEscapeOpcodes() (gas: 38581836)\n[PASS] test_noRenounceAnywhere() (gas: 152728)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 972.72ms (86.34ms CPU time)\n\nRan 20 tests for test/ArtHouse.t.sol:ArtHouseTest\n[PASS] testFuzz_ethSplitInvariant(uint96,bool,bool) (runs: 64, μ: 1169477, ~: 1153452)\n[PASS] testFuzz_imdSplitInvariant(uint96,bool) (runs: 64, μ: 1272737, ~: 1289966)\n[PASS] test_artistPayoutOnlyArtist_keptOnReapproval() (gas: 183614)\n[PASS] test_collectionMintOnlyHouse() (gas: 76647)\n[PASS] test_curationFlow_andFailures() (gas: 936843)\n[PASS] test_customSplit() (gas: 1116638)\n[PASS] test_deploymentWiring() (gas: 97111)\n[PASS] test_mintFailures() (gas: 1508406)\n[PASS] test_onlyCuratorApproves() (gas: 122771)\n[PASS] test_pieceOver100KBRejected() (gas: 21310020)\n[PASS] test_recordSaleOnlyModules() (gas: 76190)\n[PASS] test_reentrancyOnWithdrawBlocked() (gas: 1823251)\n[PASS] test_revokedArtistLosesCuration() (gas: 581215)\n[PASS] test_sealNeedsEveryPieceFinalized() (gas: 526484)\n[PASS] test_setHouseOnce() (gas: 78294)\n[PASS] test_submissions() (gas: 260284)\n[PASS] test_tokenURIOnChain() (gas: 1181175)\n[PASS] test_twoStepOwnership_noRenounce() (gas: 196660)\n[PASS] test_withdrawNothingReverts_andHouseGoesToCurrentOwner() (gas: 1226246)\n[PASS] test_wrongHashIsCorrectable() (gas: 775847)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 972.74ms (72.00ms CPU time)\n\nRan 10 tests for test/Artifacts.t.sol:ArtifactsFactoryTest\n[PASS] test_buyFailures() (gas: 60790592)\n[PASS] test_buyRecordsSaleAndAssignsEveryEditionOnce() (gas: 149031299)\n[PASS] test_claimFlow_andFailures() (gas: 62192467)\n[PASS] test_publishAndLock_accessAndFailures() (gas: 60769263)\n[PASS] test_rerequestRandomnessAfterDroppedRequest() (gas: 61541896)\n[PASS] test_saleControl() (gas: 60836424)\n[PASS] test_sealTokenURIMatchesR8Frame() (gas: 517423734)\n[PASS] test_viewingKeyZeroRejected() (gas: 32777)\n[PASS] test_vrfCallbackCannotBeReplayed() (gas: 61259813)\n[PASS] test_vrfMustBeConfiguredOnce() (gas: 81788)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 1.00s (866.82ms CPU time)\n\nRan 1 test for test/invariants/PieceEscrowInvariant.t.sol:PieceEscrowInvariantTest\n[PASS]\nPieceEscrowInvariantTest invariants:\n[PASS] invariant_escrowConservation\n[PASS] invariant_escrowSellerOwns\n[PASS] invariant_royaltyAndIdentity\n[PASS] invariant_singleToken\n PieceEscrowInvariantTest invariants (runs: 96, calls: 5760, reverts: 0)\n\n╭---------------+--------------------+-------+---------+----------╮\n| Contract      | Selector           | Calls | Reverts | Discards |\n+=================================================================+\n| EscrowHandler | approveAndTransfer | 456   | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| EscrowHandler | buy                | 447   | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| EscrowHandler | cancel             | 428   | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| EscrowHandler | delist             | 428   | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| EscrowHandler | list               | 463   | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| EscrowHandler | postKey            | 404   | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| EscrowHandler | registerKey        | 441   | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| EscrowHandler | release            | 481   | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| EscrowHandler | rewrap             | 443   | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| EscrowHandler | transfer           | 434   | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| EscrowHandler | warp               | 449   | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| EscrowHandler | withdraw           | 448   | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| EscrowHandler | withdrawRoyalty    | 438   | 0       | 0        |\n╰---------------+--------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 10185\n  Bound result 64000\n  Bound result 9999\n  Bound result 9538\n  Bound result 104\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.30s (1.27s CPU time)\n\nRan 6 tests for test/Source.t.sol:ArtifactsSourceTest\n[PASS] test_auctionNeedsLockedPayload() (gas: 521271)\n[PASS] test_auctionReopensWhenNoBids() (gas: 364837)\n[PASS] test_auctionRules() (gas: 5324781)\n[PASS] test_bidReentrancyAndRefundPull() (gas: 680829)\n[PASS] test_revealedTokenURIIsPayloadByteForByte() (gas: 2432265669)\n[PASS] test_tinyBidsMustStrictlyExceed() (gas: 836156)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 1.91s (1.91s CPU time)\n\nRan 1 test for test/invariants/ArtHouseInvariant.t.sol:ArtHouseInvariantTest\n[PASS]\nArtHouseInvariantTest invariants:\n[PASS] invariant_constants\n[PASS] invariant_ethConservation\n[PASS] invariant_imdConservation\n[PASS] invariant_noOverWithdrawal\n[PASS] invariant_supplyAndSeal\n ArtHouseInvariantTest invariants (runs: 128, calls: 5120, reverts: 0)\n\n╭---------------+---------------+-------+---------+----------╮\n| Contract      | Selector      | Calls | Reverts | Discards |\n+============================================================+\n| LedgerHandler | handOver      | 410   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | mintETH       | 423   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | mintIMD       | 432   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | moduleSale    | 414   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | setBroker     | 414   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | setPayout     | 447   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | setPrices     | 430   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | setSaleOpen   | 430   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | setSplit      | 422   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | strangerCalls | 418   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | withdraw      | 422   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | withdrawHouse | 458   | 0       | 0        |\n╰---------------+---------------+-------+---------+----------╯\n\nLogs:\n  Bound result 500\n  Bound result 242\n  Bound result 2765\n  Bound result 808\n  Bound result 256\n  Bound result 7\n  Bound result 128\n  Bound result 7550\n  Bound result 5778\n  Bound result 6808\n  Bound result 1835\n  Bound result 4550\n  Bound result 777\n  Bound result 4001\n  Bound result 7555\n  Bound result 114\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.89s (2.88s CPU time)\n\nRan 13 tests for test/Adversarial.t.sol:ArtifactsEdgeTest\n[PASS] testFuzz_anyWordsAssignDistinctEditions(uint256[8]) (runs: 256, μ: 63561744, ~: 63563542)\n[PASS] testFuzz_sealFramesAnyTraits(uint8,uint8,uint8,uint8,uint8) (runs: 16, μ: 513938889, ~: 513936932)\nLogs:\n  Bound result 4\n\n[PASS] test_buyRevertsAtomicallyWhenCoordinatorReverts() (gas: 58884497)\n[PASS] test_buyerAsBrokerEarnsNothing_andOneWeiPriceWorks() (gas: 61162379)\n[PASS] test_callbackWithNoWordsReverts() (gas: 61195576)\n[PASS] test_claimedPieceIsAnEip1167CloneOfTheImplementation() (gas: 61892952)\n[PASS] test_configureVRFRejectsZeroes() (gas: 222997)\n[PASS] test_duplicateRequestIdRejected() (gas: 59009129)\n[PASS] test_nonOwnerCannotRunTheSale() (gas: 60643586)\n[PASS] test_rerequestIsAtomicWhenCoordinatorReverts() (gas: 59508546)\n[PASS] test_rerequestWindowBoundaryAndChaining() (gas: 62383096)\n[PASS] test_setPieceEdges() (gas: 60872710)\n[PASS] test_wrappedKeyEdges() (gas: 61927607)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 9.50s (10.72s CPU time)\n\nRan 13 test suites in 9.50s (22.15s CPU time): 100 tests passed, 0 failed, 0 skipped (100 total tests)\n","passed":true},{"durationMs":74,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"ArtCollection.approve(address,uint256)\",\"ArtCollection.initialize(address,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.approveArtist(address,string)\",\"ArtHouse.clearPiece(address,uint256)\",\"ArtHouse.createCollection(string,string,address)\",\"ArtHouse.decideSubmission(uint256,bool)\",\"ArtHouse.finalizePiece(address,uint256)\",\"ArtHouse.mint(address,uint256,address)\",\"ArtHouse.mintWithIMD(address,uint256,address,uint256)\",\"ArtHouse.recordSale(address,address,address)\",\"ArtHouse.registerModuleCollection(address,address)\",\"ArtHouse.registerPiece(address,uint256,bytes32,string,string)\",\"ArtHouse.revokeArtist(address)\",\"ArtHouse.sealCollection(address)\",\"ArtHouse.setBroker(address,bool)\",\"ArtHouse.setPayout(address)\",\"ArtHouse.setPrices(address,uint256,uint256)\",\"ArtHouse.setSaleModule(address,bool)\",\"ArtHouse.setSaleOpen(address,bool)\",\"ArtHouse.setSplit(address,uint16,uint16,uint16)\",\"ArtHouse.submit(bytes32,string)\",\"ArtHouse.transferOwnership(address)\",\"ArtHouse.updatePiece(address,uint256,bytes32,string,string)\",\"ArtHouse.uploadChunk(address,uint256,uint256,bytes)\",\"ArtHouse.withdraw(address)\",\"ArtHouse.withdrawHouse(address)\",\"ArtStorage.acceptOwnership()\",\"ArtStorage.clear(address,uint256)\",\"ArtStorage.setHouse(address)\",\"ArtStorage.transferOwnership(address)\",\"ArtStorage.write(address,uint256,uint256,bytes)\",\"ArtifactsFactory.acceptOwnership()\",\"ArtifactsFactory.buy(address)\",\"ArtifactsFactory.claim(uint256)\",\"ArtifactsFactory.configureVRF(address,bytes32,uint256,bool)\",\"ArtifactsFactory.lock()\",\"ArtifactsFactory.postWrappedKey(uint256,bytes)\",\"ArtifactsFactory.rawFulfillRandomWords(uint256,uint256[])\",\"ArtifactsFactory.registerViewingKey(bytes32)\",\"ArtifactsFactory.rerequestRandomness(uint256)\",\"ArtifactsFactory.setPiece(uint256,(string,string,uint8,string),bytes,bytes32)\",\"ArtifactsFactory.setPrice(uint256)\",\"ArtifactsFactory.setSaleOpen(bool)\",\"ArtifactsFactory.transferOwnership(address)\",\"ArtifactsPiece.approve(address,uint256)\",\"ArtifactsPiece.buy(bytes32)\",\"ArtifactsPiece.cancel()\",\"ArtifactsPiece.delist()\",\"ArtifactsPiece.initialize(address,address,uint256,address,(string,string,uint8,string),address,bytes32,bytes,bool)\",\"ArtifactsPiece.list(uint256)\",\"ArtifactsPiece.postEscrowKey(bytes)\",\"ArtifactsPiece.registerViewingKey(bytes32)\",\"ArtifactsPiece.release()\",\"ArtifactsPiece.rewrapFor(bytes)\",\"ArtifactsPiece.safeTransferFrom(address,address,uint256)\",\"ArtifactsPiece.safeTransferFrom(address,address,uint256,bytes)\",\"ArtifactsPiece.setApprovalForAll(address,bool)\",\"ArtifactsPiece.setRevealed()\",\"ArtifactsPiece.transferFrom(address,address,uint256)\",\"ArtifactsPiece.withdraw()\",\"ArtifactsSource.acceptOwnership()\",\"ArtifactsSource.bid(address)\",\"ArtifactsSource.lockPayload()\",\"ArtifactsSource.resetPayload()\",\"ArtifactsSource.setAuction(uint256,uint64)\",\"ArtifactsSource.settle()\",\"ArtifactsSource.storePayloadChunk(bytes)\",\"ArtifactsSource.transferOwnership(address)\",\"ArtifactsSource.withdrawRefund()\"],\"files\":{\".gitignore\":3,\"README.md\":149,\"foundry.toml\":25,\"reference/PROTOCOL.md\":86,\"reference/RULES.md\":135,\"reference/check-framed.js\":25,\"reference/framed/framed-003.svg\":1,\"reference/framed/framed-MASTER.svg\":1,\"reference/rules.js\":106,\"reference/the-source.payload.b64\":1,\"remappings.txt\":1,\"script/Deploy.s.sol\":36,\"src/ArtCollection.sol\":54,\"src/ArtHouse.sol\":472,\"src/ArtStorage.sol\":82,\"src/ArtifactsFactory.sol\":297,\"src/ArtifactsPiece.sol\":333,\"src/ArtifactsSource.sol\":183,\"src/base/ERC721Lite.sol\":122,\"src/base/ReentrancyGuard.sol\":15,\"src/base/TwoStepOwnable.sol\":40,\"src/interfaces/Interfaces.sol\":68,\"src/libs/Clones.sol\":27,\"src/libs/Encoding.sol\":64,\"src/libs/SSTORE2.sol\":41,\"src/libs/Seal.sol\":80,\"test/Adversarial.t.sol\":1325,\"test/ArtHouse.t.sol\":426,\"test/Artifacts.t.sol\":474,\"test/Fixture.sol\":99,\"test/Source.t.sol\":252,\"test/invariants/ArtHouseInvariant.t.sol\":405,\"test/invariants/ArtifactsFactoryInvariant.t.sol\":276,\"test/invariants/PieceEscrowInvariant.t.sol\":311,\"test/invariants/SourceAuctionInvariant.t.sol\":216,\"test/mocks/AdversarialMocks.sol\":130,\"test/mocks/Mocks.sol\":88,\"test/utils/Base64Decode.sol\":76},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"4206c2321627f9923376a1e40bfa8b0c41d3d5f643fed2e7e686080a02ccfca2","verifiedTreeHash":"d44234a6447e67ffbfc114981b5174429cacc8b4","verifierVersion":"0.1.0+7129bf9d"},{"checks":[{"durationMs":44041,"exitCode":0,"name":"build","output":"Compiling 39 files with Solc 0.8.26\nSolc 0.8.26 finished in 43.96s\nCompiler run successful!\n","passed":true},{"durationMs":1899,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/Artifacts.t.sol:ArtifactsPieceEscrowTest\n[PASS] test_escrowCancelBeforeKey_andDelist() (gas: 479568)\n[PASS] test_escrowHappyPath_buyerConfirms() (gas: 896900)\n[PASS] test_escrowTimeoutRelease() (gas: 473128)\n[PASS] test_plainTransferClearsKey_previousOwnerRekeys() (gas: 502300)\n[PASS] test_reentrancyOnEscrowWithdrawBlocked() (gas: 819586)\n[PASS] test_sourceOnlyReveal() (gas: 37353)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 17.05ms (1.48ms CPU time)\n\nRan 18 tests for test/ArtHouse.t.sol:ArtHouseTest\n[PASS] testFuzz_ethSplitInvariant(uint96,bool,bool) (runs: 64, μ: 1155433, ~: 1139447)\n[PASS] testFuzz_imdSplitInvariant(uint96,bool) (runs: 64, μ: 1258658, ~: 1275956)\n[PASS] test_artistPayoutOnlyArtist_keptOnReapproval() (gas: 183316)\n[PASS] test_collectionMintOnlyHouse() (gas: 76575)\n[PASS] test_curationFlow_andFailures() (gas: 916261)\n[PASS] test_customSplit() (gas: 1102714)\n[PASS] test_deploymentWiring() (gas: 96667)\n[PASS] test_mintFailures() (gas: 1494452)\n[PASS] test_onlyCuratorApproves() (gas: 122517)\n[PASS] test_pieceOver100KBRejected() (gas: 21294399)\n[PASS] test_recordSaleOnlyModules() (gas: 76106)\n[PASS] test_reentrancyOnWithdrawBlocked() (gas: 1809358)\n[PASS] test_sealNeedsEveryPieceFinalized() (gas: 517350)\n[PASS] test_setHouseOnce() (gas: 78200)\n[PASS] test_submissions() (gas: 260059)\n[PASS] test_tokenURIOnChain() (gas: 1167025)\n[PASS] test_twoStepOwnership_noRenounce() (gas: 196292)\n[PASS] test_withdrawNothingReverts_andHouseGoesToCurrentOwner() (gas: 1212364)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 22.38ms (47.21ms CPU time)\n\nRan 9 tests for test/Artifacts.t.sol:ArtifactsFactoryTest\n[PASS] test_buyFailures() (gas: 60773432)\n[PASS] test_buyRecordsSaleAndAssignsEveryEditionOnce() (gas: 143287368)\n[PASS] test_claimFlow_andFailures() (gas: 62152804)\n[PASS] test_publishAndLock_accessAndFailures() (gas: 60751769)\n[PASS] test_saleControl() (gas: 60819000)\n[PASS] test_sealTokenURIMatchesR8Frame() (gas: 517384269)\n[PASS] test_viewingKeyZeroRejected() (gas: 32711)\n[PASS] test_vrfCallbackCannotBeReplayed() (gas: 61220414)\n[PASS] test_vrfMustBeConfiguredOnce() (gas: 81590)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 651.93ms (796.92ms CPU time)\n\nRan 5 tests for test/Source.t.sol:ArtifactsSourceTest\n[PASS] test_auctionNeedsLockedPayload() (gas: 521262)\n[PASS] test_auctionReopensWhenNoBids() (gas: 364838)\n[PASS] test_auctionRules() (gas: 5311676)\n[PASS] test_bidReentrancyAndRefundPull() (gas: 680831)\n[PASS] test_revealedTokenURIIsPayloadByteForByte() (gas: 2432252652)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 1.82s (1.82s CPU time)\n\nRan 4 test suites in 1.82s (2.51s CPU time): 38 tests passed, 0 failed, 0 skipped (38 total tests)\n","passed":true},{"durationMs":57,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"ArtCollection.approve(address,uint256)\",\"ArtCollection.initialize(address,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.approveArtist(address,string)\",\"ArtHouse.clearPiece(address,uint256)\",\"ArtHouse.createCollection(string,string,address)\",\"ArtHouse.decideSubmission(uint256,bool)\",\"ArtHouse.finalizePiece(address,uint256)\",\"ArtHouse.mint(address,uint256,address)\",\"ArtHouse.mintWithIMD(address,uint256,address,uint256)\",\"ArtHouse.recordSale(address,address,address)\",\"ArtHouse.registerModuleCollection(address,address)\",\"ArtHouse.registerPiece(address,uint256,bytes32,string,string)\",\"ArtHouse.revokeArtist(address)\",\"ArtHouse.sealCollection(address)\",\"ArtHouse.setBroker(address,bool)\",\"ArtHouse.setPayout(address)\",\"ArtHouse.setPrices(address,uint256,uint256)\",\"ArtHouse.setSaleModule(address,bool)\",\"ArtHouse.setSaleOpen(address,bool)\",\"ArtHouse.setSplit(address,uint16,uint16,uint16)\",\"ArtHouse.submit(bytes32,string)\",\"ArtHouse.transferOwnership(address)\",\"ArtHouse.uploadChunk(address,uint256,uint256,bytes)\",\"ArtHouse.withdraw(address)\",\"ArtHouse.withdrawHouse(address)\",\"ArtStorage.acceptOwnership()\",\"ArtStorage.clear(address,uint256)\",\"ArtStorage.setHouse(address)\",\"ArtStorage.transferOwnership(address)\",\"ArtStorage.write(address,uint256,uint256,bytes)\",\"ArtifactsFactory.acceptOwnership()\",\"ArtifactsFactory.buy(address)\",\"ArtifactsFactory.claim(uint256)\",\"ArtifactsFactory.configureVRF(address,bytes32,uint256,bool)\",\"ArtifactsFactory.lock()\",\"ArtifactsFactory.postWrappedKey(uint256,bytes)\",\"ArtifactsFactory.rawFulfillRandomWords(uint256,uint256[])\",\"ArtifactsFactory.registerViewingKey(bytes32)\",\"ArtifactsFactory.setPiece(uint256,(string,string,uint8,string),bytes,bytes32)\",\"ArtifactsFactory.setPrice(uint256)\",\"ArtifactsFactory.setSaleOpen(bool)\",\"ArtifactsFactory.transferOwnership(address)\",\"ArtifactsPiece.approve(address,uint256)\",\"ArtifactsPiece.buy(bytes32)\",\"ArtifactsPiece.cancel()\",\"ArtifactsPiece.delist()\",\"ArtifactsPiece.initialize(address,address,uint256,address,(string,string,uint8,string),address,bytes32,bytes,bool)\",\"ArtifactsPiece.list(uint256)\",\"ArtifactsPiece.postEscrowKey(bytes)\",\"ArtifactsPiece.registerViewingKey(bytes32)\",\"ArtifactsPiece.release()\",\"ArtifactsPiece.rewrapFor(bytes)\",\"ArtifactsPiece.safeTransferFrom(address,address,uint256)\",\"ArtifactsPiece.safeTransferFrom(address,address,uint256,bytes)\",\"ArtifactsPiece.setApprovalForAll(address,bool)\",\"ArtifactsPiece.setRevealed()\",\"ArtifactsPiece.transferFrom(address,address,uint256)\",\"ArtifactsPiece.withdraw()\",\"ArtifactsSource.acceptOwnership()\",\"ArtifactsSource.bid(address)\",\"ArtifactsSource.lockPayload()\",\"ArtifactsSource.resetPayload()\",\"ArtifactsSource.setAuction(uint256,uint64)\",\"ArtifactsSource.settle()\",\"ArtifactsSource.storePayloadChunk(bytes)\",\"ArtifactsSource.transferOwnership(address)\",\"ArtifactsSource.withdrawRefund()\"],\"files\":{\".gitignore\":3,\"README.md\":134,\"foundry.toml\":25,\"reference/PROTOCOL.md\":86,\"reference/RULES.md\":135,\"reference/check-framed.js\":25,\"reference/framed/framed-003.svg\":1,\"reference/framed/framed-MASTER.svg\":1,\"reference/rules.js\":106,\"reference/the-source.payload.b64\":1,\"remappings.txt\":1,\"script/Deploy.s.sol\":36,\"src/ArtCollection.sol\":54,\"src/ArtHouse.sol\":447,\"src/ArtStorage.sol\":82,\"src/ArtifactsFactory.sol\":272,\"src/ArtifactsPiece.sol\":326,\"src/ArtifactsSource.sol\":182,\"src/base/ERC721Lite.sol\":122,\"src/base/ReentrancyGuard.sol\":15,\"src/base/TwoStepOwnable.sol\":40,\"src/interfaces/Interfaces.sol\":68,\"src/libs/Clones.sol\":27,\"src/libs/Encoding.sol\":64,\"src/libs/SSTORE2.sol\":41,\"src/libs/Seal.sol\":80,\"test/ArtHouse.t.sol\":364,\"test/Artifacts.t.sol\":362,\"test/Fixture.sol\":99,\"test/Source.t.sol\":221,\"test/mocks/Mocks.sol\":88,\"test/utils/Base64Decode.sol\":76},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":6184,"exitCode":0,"name":"slither","output":"[high/medium] arbitrary-send-eth at src/ArtHouse.sol:397: ArtHouse._pay(address,address,uint256) (src/ArtHouse.sol#397-404) sends eth to arbitrary user\n[high/medium] reentrancy-balance at src/ArtHouse.sol:343: Reentrancy in ArtHouse._pullIMD(address,uint256) (src/ArtHouse.sol#343-348):\n[medium/medium] divide-before-multiply at src/libs/Encoding.sol:8: Encoding.base64(bytes) (src/libs/Encoding.sol#8-37) performs a multiplication on the result of a division:\n[medium/medium] uninitialized-local at src/ArtHouse.sol:362: ArtHouse._credit(address,address,address,address,uint256).brokerShare (src/ArtHouse.sol#362) is a local variable never initialized\n[medium/medium] unused-return at src/ArtHouse.sol:265: ArtHouse.uploadChunk(address,uint256,uint256,bytes) (src/ArtHouse.sol#265-276) ignores return value by artStorage.write(collection,pieceId,index,data) (src/ArtHouse.sol#275)\n[low/medium] missing-zero-check at src/ArtifactsPiece.sol:85: ArtifactsPiece.initialize(address,address,uint256,address,Traits,address,bytes32,bytes,bool).controller_ (src/ArtifactsPiece.sol#85) lacks a zero-check on :\n[low/medium] missing-zero-check at src/ArtifactsSource.sol:123: ArtifactsSource.bid(address).broker (src/ArtifactsSource.sol#123) lacks a zero-check on :\n[low/medium] missing-zero-check at src/ArtifactsPiece.sol:90: ArtifactsPiece.initialize(address,address,uint256,address,Traits,address,bytes32,bytes,bool).ciphertext_ (src/ArtifactsPiece.sol#90) lacks a zero-check on :\n[low/medium] reentrancy-benign at src/ArtifactsFactory.sol:156: Reentrancy in ArtifactsFactory.buy(address) (src/ArtifactsFactory.sol#156-176):\n[low/medium] reentrancy-benign at src/ArtHouse.sol:178: Reentrancy in ArtHouse.createCollection(string,string,address) (src/ArtHouse.sol#178-188):\n[low/medium] reentrancy-events at src/ArtHouse.sol:279: Reentrancy in ArtHouse.clearPiece(address,uint256) (src/ArtHouse.sol#279-287):\n[low/medium] reentrancy-events at src/ArtHouse.sol:178: Reentrancy in ArtHouse.createCollection(string,string,address) (src/ArtHouse.sol#178-188):\n[low/medium] timestamp at src/ArtifactsPiece.sol:201: ArtifactsPiece.release() (src/ArtifactsPiece.sol#201-220) uses timestamp for comparisons\n[low/medium] timestamp at src/ArtifactsSource.sol:123: ArtifactsSource.bid(address) (src/ArtifactsSource.sol#123-134) uses timestamp for comparisons\n[low/medium] timestamp at src/ArtifactsSource.sol:111: ArtifactsSource.setAuction(uint256,uint64) (src/ArtifactsSource.sol#111-121) uses timestamp for comparisons\n[low/medium] timestamp at src/ArtifactsPiece.sol:234: ArtifactsPiece._beforeTransfer(address,address,uint256) (src/ArtifactsPiece.sol#234-242) uses timestamp for comparisons\n[low/medium] timestamp at src/ArtifactsPiece.sol:155: ArtifactsPiece.delist() (src/ArtifactsPiece.sol#155-160) uses timestamp for comparisons\n[low/medium] timestamp at src/ArtifactsSource.sol:146: ArtifactsSource.settle() (src/ArtifactsSource.sol#146-173) uses timestamp for comparisons","passed":true},{"durationMs":397,"exitCode":0,"name":"aderyn","output":"[high] abi-encode-packed-hash-collision at src/ArtCollection.sol:43: `abi.encodePacked()` Hash Collision (4 places)\n[high] contract-locks-ether at src/ArtifactsFactory.sol:12: Contract locks Ether without a withdraw function\n[high] eth-send-unchecked-address at src/ArtifactsPiece.sol:222: ETH transferred without address checks (2 places)\n[high] reentrancy-state-change at src/ArtHouse.sol:272: Reentrancy: State change after external call (5 places)\n[high] yul-return at src/ArtifactsPiece.sol:323: Yul block contains `return`\n[low] centralization-risk at src/ArtHouse.sol:144: Centralization Risk (25 places)\n[low] empty-block at src/base/ERC721Lite.sol:121: Empty Block\n[low] large-numeric-literal at src/ArtHouse.sol:15: Large Numeric Literal (5 places)\n[low] literal-instead-of-constant at src/libs/Encoding.sol:11: Literal Instead of Constant (13 places)\n[low] require-revert-in-loop at src/ArtHouse.sol:306: Loop Contains `require`/`revert`\n[low] state-change-without-event at src/ArtCollection.sol:20: State Change Without Event (3 places)\n[low] state-no-address-check at src/ArtCollection.sol:22: Address State Variable Set Without Checks (5 places)\n[low] storage-array-length-not-cached at src/ArtifactsSource.sol:95: Storage Array Length not Cached\n[low] unchecked-return at src/ArtCollection.sol:41: Unchecked Return (4 places)\n[low] unsafe-erc20-operation at src/ArtHouse.sol:346: Unsafe ERC20 Operation (2 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"a341628e5ab0b00d507ce6f9ccad84623fb869da8a0a96117476ff8f0039f509","verifiedTreeHash":"efe4d6f9a6942b2181dd9a9a4aba8496f0e735b7","verifierVersion":"0.1.0+7129bf9d"},{"checks":[{"durationMs":46025,"exitCode":0,"name":"build","output":"Compiling 39 files with Solc 0.8.26\nSolc 0.8.26 finished in 45.94s\nCompiler run successful!\n","passed":true},{"durationMs":2071,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 10 tests for test/Artifacts.t.sol:ArtifactsPieceEscrowTest\n[PASS] test_escrowCancelBeforeKey_andDelist() (gas: 479598)\n[PASS] test_escrowHappyPath_buyerConfirms() (gas: 897142)\n[PASS] test_escrowKeyMustNotBeEmpty() (gas: 276284)\n[PASS] test_escrowTimeoutRelease() (gas: 473392)\n[PASS] test_pieceViewingKeyZeroRejected() (gas: 172988)\n[PASS] test_plainTransferClearsKey_previousOwnerRekeys() (gas: 518575)\n[PASS] test_reentrancyOnEscrowWithdrawBlocked() (gas: 819674)\n[PASS] test_rewrapIsOneShotAndNonEmpty() (gas: 296131)\n[PASS] test_sellerCannotOverwriteKeyAfterRelease() (gas: 659532)\n[PASS] test_sourceOnlyReveal() (gas: 37397)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 24.61ms (2.46ms CPU time)\n\nRan 20 tests for test/ArtHouse.t.sol:ArtHouseTest\n[PASS] testFuzz_ethSplitInvariant(uint96,bool,bool) (runs: 64, μ: 1169447, ~: 1153446)\n[PASS] testFuzz_imdSplitInvariant(uint96,bool) (runs: 64, μ: 1272657, ~: 1289942)\n[PASS] test_artistPayoutOnlyArtist_keptOnReapproval() (gas: 183614)\n[PASS] test_collectionMintOnlyHouse() (gas: 76647)\n[PASS] test_curationFlow_andFailures() (gas: 936843)\n[PASS] test_customSplit() (gas: 1116638)\n[PASS] test_deploymentWiring() (gas: 97111)\n[PASS] test_mintFailures() (gas: 1508406)\n[PASS] test_onlyCuratorApproves() (gas: 122771)\n[PASS] test_pieceOver100KBRejected() (gas: 21310020)\n[PASS] test_recordSaleOnlyModules() (gas: 76190)\n[PASS] test_reentrancyOnWithdrawBlocked() (gas: 1823251)\n[PASS] test_revokedArtistLosesCuration() (gas: 581215)\n[PASS] test_sealNeedsEveryPieceFinalized() (gas: 526484)\n[PASS] test_setHouseOnce() (gas: 78294)\n[PASS] test_submissions() (gas: 260284)\n[PASS] test_tokenURIOnChain() (gas: 1181175)\n[PASS] test_twoStepOwnership_noRenounce() (gas: 196660)\n[PASS] test_withdrawNothingReverts_andHouseGoesToCurrentOwner() (gas: 1226246)\n[PASS] test_wrongHashIsCorrectable() (gas: 775847)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 40.02ms (56.04ms CPU time)\n\nRan 6 tests for test/Source.t.sol:ArtifactsSourceTest\n[PASS] test_auctionNeedsLockedPayload() (gas: 521271)\n[PASS] test_auctionReopensWhenNoBids() (gas: 364837)\n[PASS] test_auctionRules() (gas: 5324781)\n[PASS] test_bidReentrancyAndRefundPull() (gas: 680829)\n[PASS] test_revealedTokenURIIsPayloadByteForByte() (gas: 2432265669)\n[PASS] test_tinyBidsMustStrictlyExceed() (gas: 836156)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 1.97s (1.97s CPU time)\n\nRan 10 tests for test/Artifacts.t.sol:ArtifactsFactoryTest\n[PASS] test_buyFailures() (gas: 60790592)\n[PASS] test_buyRecordsSaleAndAssignsEveryEditionOnce() (gas: 149031299)\n[PASS] test_claimFlow_andFailures() (gas: 62192467)\n[PASS] test_publishAndLock_accessAndFailures() (gas: 60769263)\n[PASS] test_rerequestRandomnessAfterDroppedRequest() (gas: 61541896)\n[PASS] test_saleControl() (gas: 60836424)\n[PASS] test_sealTokenURIMatchesR8Frame() (gas: 517423734)\n[PASS] test_viewingKeyZeroRejected() (gas: 32777)\n[PASS] test_vrfCallbackCannotBeReplayed() (gas: 61259813)\n[PASS] test_vrfMustBeConfiguredOnce() (gas: 81788)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 1.97s (847.51ms CPU time)\n\nRan 4 test suites in 1.98s (4.01s CPU time): 46 tests passed, 0 failed, 0 skipped (46 total tests)\n","passed":true},{"durationMs":59,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"ArtCollection.approve(address,uint256)\",\"ArtCollection.initialize(address,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.approveArtist(address,string)\",\"ArtHouse.clearPiece(address,uint256)\",\"ArtHouse.createCollection(string,string,address)\",\"ArtHouse.decideSubmission(uint256,bool)\",\"ArtHouse.finalizePiece(address,uint256)\",\"ArtHouse.mint(address,uint256,address)\",\"ArtHouse.mintWithIMD(address,uint256,address,uint256)\",\"ArtHouse.recordSale(address,address,address)\",\"ArtHouse.registerModuleCollection(address,address)\",\"ArtHouse.registerPiece(address,uint256,bytes32,string,string)\",\"ArtHouse.revokeArtist(address)\",\"ArtHouse.sealCollection(address)\",\"ArtHouse.setBroker(address,bool)\",\"ArtHouse.setPayout(address)\",\"ArtHouse.setPrices(address,uint256,uint256)\",\"ArtHouse.setSaleModule(address,bool)\",\"ArtHouse.setSaleOpen(address,bool)\",\"ArtHouse.setSplit(address,uint16,uint16,uint16)\",\"ArtHouse.submit(bytes32,string)\",\"ArtHouse.transferOwnership(address)\",\"ArtHouse.updatePiece(address,uint256,bytes32,string,string)\",\"ArtHouse.uploadChunk(address,uint256,uint256,bytes)\",\"ArtHouse.withdraw(address)\",\"ArtHouse.withdrawHouse(address)\",\"ArtStorage.acceptOwnership()\",\"ArtStorage.clear(address,uint256)\",\"ArtStorage.setHouse(address)\",\"ArtStorage.transferOwnership(address)\",\"ArtStorage.write(address,uint256,uint256,bytes)\",\"ArtifactsFactory.acceptOwnership()\",\"ArtifactsFactory.buy(address)\",\"ArtifactsFactory.claim(uint256)\",\"ArtifactsFactory.configureVRF(address,bytes32,uint256,bool)\",\"ArtifactsFactory.lock()\",\"ArtifactsFactory.postWrappedKey(uint256,bytes)\",\"ArtifactsFactory.rawFulfillRandomWords(uint256,uint256[])\",\"ArtifactsFactory.registerViewingKey(bytes32)\",\"ArtifactsFactory.rerequestRandomness(uint256)\",\"ArtifactsFactory.setPiece(uint256,(string,string,uint8,string),bytes,bytes32)\",\"ArtifactsFactory.setPrice(uint256)\",\"ArtifactsFactory.setSaleOpen(bool)\",\"ArtifactsFactory.transferOwnership(address)\",\"ArtifactsPiece.approve(address,uint256)\",\"ArtifactsPiece.buy(bytes32)\",\"ArtifactsPiece.cancel()\",\"ArtifactsPiece.delist()\",\"ArtifactsPiece.initialize(address,address,uint256,address,(string,string,uint8,string),address,bytes32,bytes,bool)\",\"ArtifactsPiece.list(uint256)\",\"ArtifactsPiece.postEscrowKey(bytes)\",\"ArtifactsPiece.registerViewingKey(bytes32)\",\"ArtifactsPiece.release()\",\"ArtifactsPiece.rewrapFor(bytes)\",\"ArtifactsPiece.safeTransferFrom(address,address,uint256)\",\"ArtifactsPiece.safeTransferFrom(address,address,uint256,bytes)\",\"ArtifactsPiece.setApprovalForAll(address,bool)\",\"ArtifactsPiece.setRevealed()\",\"ArtifactsPiece.transferFrom(address,address,uint256)\",\"ArtifactsPiece.withdraw()\",\"ArtifactsSource.acceptOwnership()\",\"ArtifactsSource.bid(address)\",\"ArtifactsSource.lockPayload()\",\"ArtifactsSource.resetPayload()\",\"ArtifactsSource.setAuction(uint256,uint64)\",\"ArtifactsSource.settle()\",\"ArtifactsSource.storePayloadChunk(bytes)\",\"ArtifactsSource.transferOwnership(address)\",\"ArtifactsSource.withdrawRefund()\"],\"files\":{\".gitignore\":3,\"README.md\":149,\"foundry.toml\":25,\"launch.json\":40,\"reference/PROTOCOL.md\":86,\"reference/RULES.md\":135,\"reference/check-framed.js\":25,\"reference/framed/framed-003.svg\":1,\"reference/framed/framed-MASTER.svg\":1,\"reference/rules.js\":106,\"reference/the-source.payload.b64\":1,\"remappings.txt\":1,\"script/Deploy.s.sol\":36,\"src/ArtCollection.sol\":54,\"src/ArtHouse.sol\":472,\"src/ArtStorage.sol\":82,\"src/ArtifactsFactory.sol\":297,\"src/ArtifactsPiece.sol\":333,\"src/ArtifactsSource.sol\":183,\"src/base/ERC721Lite.sol\":122,\"src/base/ReentrancyGuard.sol\":15,\"src/base/TwoStepOwnable.sol\":40,\"src/interfaces/Interfaces.sol\":68,\"src/libs/Clones.sol\":27,\"src/libs/Encoding.sol\":64,\"src/libs/SSTORE2.sol\":41,\"src/libs/Seal.sol\":80,\"test/ArtHouse.t.sol\":426,\"test/Artifacts.t.sol\":474,\"test/Fixture.sol\":99,\"test/Source.t.sol\":252,\"test/mocks/Mocks.sol\":88,\"test/utils/Base64Decode.sol\":76},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"aa775f2e28927162c3ad766cc35d7dc1c58976c905a1452643b856932e9542ce","verifiedTreeHash":"ccf7efed4921ff05876b3320da6c9483a45087a0","verifierVersion":"0.1.0+7129bf9d"},{"checks":[{"durationMs":45598,"exitCode":0,"name":"build","output":"Compiling 39 files with Solc 0.8.26\nSolc 0.8.26 finished in 45.53s\nCompiler run successful!\n","passed":true},{"durationMs":1864,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 10 tests for test/Artifacts.t.sol:ArtifactsPieceEscrowTest\n[PASS] test_escrowCancelBeforeKey_andDelist() (gas: 479598)\n[PASS] test_escrowHappyPath_buyerConfirms() (gas: 897142)\n[PASS] test_escrowKeyMustNotBeEmpty() (gas: 276284)\n[PASS] test_escrowTimeoutRelease() (gas: 473392)\n[PASS] test_pieceViewingKeyZeroRejected() (gas: 172988)\n[PASS] test_plainTransferClearsKey_previousOwnerRekeys() (gas: 518575)\n[PASS] test_reentrancyOnEscrowWithdrawBlocked() (gas: 819674)\n[PASS] test_rewrapIsOneShotAndNonEmpty() (gas: 296131)\n[PASS] test_sellerCannotOverwriteKeyAfterRelease() (gas: 659532)\n[PASS] test_sourceOnlyReveal() (gas: 37397)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 17.07ms (2.58ms CPU time)\n\nRan 20 tests for test/ArtHouse.t.sol:ArtHouseTest\n[PASS] testFuzz_ethSplitInvariant(uint96,bool,bool) (runs: 64, μ: 1169447, ~: 1153446)\n[PASS] testFuzz_imdSplitInvariant(uint96,bool) (runs: 64, μ: 1272657, ~: 1289942)\n[PASS] test_artistPayoutOnlyArtist_keptOnReapproval() (gas: 183614)\n[PASS] test_collectionMintOnlyHouse() (gas: 76647)\n[PASS] test_curationFlow_andFailures() (gas: 936843)\n[PASS] test_customSplit() (gas: 1116638)\n[PASS] test_deploymentWiring() (gas: 97111)\n[PASS] test_mintFailures() (gas: 1508406)\n[PASS] test_onlyCuratorApproves() (gas: 122771)\n[PASS] test_pieceOver100KBRejected() (gas: 21310020)\n[PASS] test_recordSaleOnlyModules() (gas: 76190)\n[PASS] test_reentrancyOnWithdrawBlocked() (gas: 1823251)\n[PASS] test_revokedArtistLosesCuration() (gas: 581215)\n[PASS] test_sealNeedsEveryPieceFinalized() (gas: 526484)\n[PASS] test_setHouseOnce() (gas: 78294)\n[PASS] test_submissions() (gas: 260284)\n[PASS] test_tokenURIOnChain() (gas: 1181175)\n[PASS] test_twoStepOwnership_noRenounce() (gas: 196660)\n[PASS] test_withdrawNothingReverts_andHouseGoesToCurrentOwner() (gas: 1226246)\n[PASS] test_wrongHashIsCorrectable() (gas: 775847)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 24.89ms (53.93ms CPU time)\n\nRan 10 tests for test/Artifacts.t.sol:ArtifactsFactoryTest\n[PASS] test_buyFailures() (gas: 60790592)\n[PASS] test_buyRecordsSaleAndAssignsEveryEditionOnce() (gas: 149031299)\n[PASS] test_claimFlow_andFailures() (gas: 62192467)\n[PASS] test_publishAndLock_accessAndFailures() (gas: 60769263)\n[PASS] test_rerequestRandomnessAfterDroppedRequest() (gas: 61541896)\n[PASS] test_saleControl() (gas: 60836424)\n[PASS] test_sealTokenURIMatchesR8Frame() (gas: 517423734)\n[PASS] test_viewingKeyZeroRejected() (gas: 32777)\n[PASS] test_vrfCallbackCannotBeReplayed() (gas: 61259813)\n[PASS] test_vrfMustBeConfiguredOnce() (gas: 81788)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 642.98ms (812.92ms CPU time)\n\nRan 6 tests for test/Source.t.sol:ArtifactsSourceTest\n[PASS] test_auctionNeedsLockedPayload() (gas: 521271)\n[PASS] test_auctionReopensWhenNoBids() (gas: 364837)\n[PASS] test_auctionRules() (gas: 5324781)\n[PASS] test_bidReentrancyAndRefundPull() (gas: 680829)\n[PASS] test_revealedTokenURIIsPayloadByteForByte() (gas: 2432265669)\n[PASS] test_tinyBidsMustStrictlyExceed() (gas: 836156)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 1.78s (1.78s CPU time)\n\nRan 4 test suites in 1.78s (2.47s CPU time): 46 tests passed, 0 failed, 0 skipped (46 total tests)\n","passed":true},{"durationMs":56,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"ArtCollection.approve(address,uint256)\",\"ArtCollection.initialize(address,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.approveArtist(address,string)\",\"ArtHouse.clearPiece(address,uint256)\",\"ArtHouse.createCollection(string,string,address)\",\"ArtHouse.decideSubmission(uint256,bool)\",\"ArtHouse.finalizePiece(address,uint256)\",\"ArtHouse.mint(address,uint256,address)\",\"ArtHouse.mintWithIMD(address,uint256,address,uint256)\",\"ArtHouse.recordSale(address,address,address)\",\"ArtHouse.registerModuleCollection(address,address)\",\"ArtHouse.registerPiece(address,uint256,bytes32,string,string)\",\"ArtHouse.revokeArtist(address)\",\"ArtHouse.sealCollection(address)\",\"ArtHouse.setBroker(address,bool)\",\"ArtHouse.setPayout(address)\",\"ArtHouse.setPrices(address,uint256,uint256)\",\"ArtHouse.setSaleModule(address,bool)\",\"ArtHouse.setSaleOpen(address,bool)\",\"ArtHouse.setSplit(address,uint16,uint16,uint16)\",\"ArtHouse.submit(bytes32,string)\",\"ArtHouse.transferOwnership(address)\",\"ArtHouse.updatePiece(address,uint256,bytes32,string,string)\",\"ArtHouse.uploadChunk(address,uint256,uint256,bytes)\",\"ArtHouse.withdraw(address)\",\"ArtHouse.withdrawHouse(address)\",\"ArtStorage.acceptOwnership()\",\"ArtStorage.clear(address,uint256)\",\"ArtStorage.setHouse(address)\",\"ArtStorage.transferOwnership(address)\",\"ArtStorage.write(address,uint256,uint256,bytes)\",\"ArtifactsFactory.acceptOwnership()\",\"ArtifactsFactory.buy(address)\",\"ArtifactsFactory.claim(uint256)\",\"ArtifactsFactory.configureVRF(address,bytes32,uint256,bool)\",\"ArtifactsFactory.lock()\",\"ArtifactsFactory.postWrappedKey(uint256,bytes)\",\"ArtifactsFactory.rawFulfillRandomWords(uint256,uint256[])\",\"ArtifactsFactory.registerViewingKey(bytes32)\",\"ArtifactsFactory.rerequestRandomness(uint256)\",\"ArtifactsFactory.setPiece(uint256,(string,string,uint8,string),bytes,bytes32)\",\"ArtifactsFactory.setPrice(uint256)\",\"ArtifactsFactory.setSaleOpen(bool)\",\"ArtifactsFactory.transferOwnership(address)\",\"ArtifactsPiece.approve(address,uint256)\",\"ArtifactsPiece.buy(bytes32)\",\"ArtifactsPiece.cancel()\",\"ArtifactsPiece.delist()\",\"ArtifactsPiece.initialize(address,address,uint256,address,(string,string,uint8,string),address,bytes32,bytes,bool)\",\"ArtifactsPiece.list(uint256)\",\"ArtifactsPiece.postEscrowKey(bytes)\",\"ArtifactsPiece.registerViewingKey(bytes32)\",\"ArtifactsPiece.release()\",\"ArtifactsPiece.rewrapFor(bytes)\",\"ArtifactsPiece.safeTransferFrom(address,address,uint256)\",\"ArtifactsPiece.safeTransferFrom(address,address,uint256,bytes)\",\"ArtifactsPiece.setApprovalForAll(address,bool)\",\"ArtifactsPiece.setRevealed()\",\"ArtifactsPiece.transferFrom(address,address,uint256)\",\"ArtifactsPiece.withdraw()\",\"ArtifactsSource.acceptOwnership()\",\"ArtifactsSource.bid(address)\",\"ArtifactsSource.lockPayload()\",\"ArtifactsSource.resetPayload()\",\"ArtifactsSource.setAuction(uint256,uint64)\",\"ArtifactsSource.settle()\",\"ArtifactsSource.storePayloadChunk(bytes)\",\"ArtifactsSource.transferOwnership(address)\",\"ArtifactsSource.withdrawRefund()\"],\"files\":{\".gitignore\":3,\"README.md\":149,\"foundry.toml\":25,\"reference/PROTOCOL.md\":86,\"reference/RULES.md\":135,\"reference/check-framed.js\":25,\"reference/framed/framed-003.svg\":1,\"reference/framed/framed-MASTER.svg\":1,\"reference/rules.js\":106,\"reference/the-source.payload.b64\":1,\"remappings.txt\":1,\"script/Deploy.s.sol\":36,\"src/ArtCollection.sol\":54,\"src/ArtHouse.sol\":472,\"src/ArtStorage.sol\":82,\"src/ArtifactsFactory.sol\":297,\"src/ArtifactsPiece.sol\":333,\"src/ArtifactsSource.sol\":183,\"src/base/ERC721Lite.sol\":122,\"src/base/ReentrancyGuard.sol\":15,\"src/base/TwoStepOwnable.sol\":40,\"src/interfaces/Interfaces.sol\":68,\"src/libs/Clones.sol\":27,\"src/libs/Encoding.sol\":64,\"src/libs/SSTORE2.sol\":41,\"src/libs/Seal.sol\":80,\"test/ArtHouse.t.sol\":426,\"test/Artifacts.t.sol\":474,\"test/Fixture.sol\":99,\"test/Source.t.sol\":252,\"test/mocks/Mocks.sol\":88,\"test/utils/Base64Decode.sol\":76},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":6386,"exitCode":0,"name":"slither","output":"[high/medium] arbitrary-send-eth at src/ArtHouse.sol:422: ArtHouse._pay(address,address,uint256) (src/ArtHouse.sol#422-429) sends eth to arbitrary user\n[high/medium] reentrancy-balance at src/ArtHouse.sol:368: Reentrancy in ArtHouse._pullIMD(address,uint256) (src/ArtHouse.sol#368-373):\n[medium/medium] divide-before-multiply at src/libs/Encoding.sol:8: Encoding.base64(bytes) (src/libs/Encoding.sol#8-37) performs a multiplication on the result of a division:\n[medium/medium] uninitialized-local at src/ArtHouse.sol:387: ArtHouse._credit(address,address,address,address,uint256).brokerShare (src/ArtHouse.sol#387) is a local variable never initialized\n[medium/medium] unused-return at src/ArtHouse.sol:290: ArtHouse.uploadChunk(address,uint256,uint256,bytes) (src/ArtHouse.sol#290-301) ignores return value by artStorage.write(collection,pieceId,index,data) (src/ArtHouse.sol#300)\n[low/medium] missing-zero-check at src/ArtifactsPiece.sol:85: ArtifactsPiece.initialize(address,address,uint256,address,Traits,address,bytes32,bytes,bool).controller_ (src/ArtifactsPiece.sol#85) lacks a zero-check on :\n[low/medium] missing-zero-check at src/ArtifactsSource.sol:123: ArtifactsSource.bid(address).broker (src/ArtifactsSource.sol#123) lacks a zero-check on :\n[low/medium] missing-zero-check at src/ArtifactsPiece.sol:90: ArtifactsPiece.initialize(address,address,uint256,address,Traits,address,bytes32,bytes,bool).ciphertext_ (src/ArtifactsPiece.sol#90) lacks a zero-check on :\n[low/medium] reentrancy-benign at src/ArtifactsFactory.sol:162: Reentrancy in ArtifactsFactory.buy(address) (src/ArtifactsFactory.sol#162-171):\n[low/medium] reentrancy-benign at src/ArtHouse.sol:179: Reentrancy in ArtHouse.createCollection(string,string,address) (src/ArtHouse.sol#179-189):\n[low/medium] reentrancy-benign at src/ArtifactsFactory.sol:187: Reentrancy in ArtifactsFactory._requestRandomness(address) (src/ArtifactsFactory.sol#187-201):\n[low/medium] reentrancy-events at src/ArtHouse.sol:304: Reentrancy in ArtHouse.clearPiece(address,uint256) (src/ArtHouse.sol#304-312):\n[low/medium] reentrancy-events at src/ArtifactsFactory.sol:176: Reentrancy in ArtifactsFactory.rerequestRandomness(uint256) (src/ArtifactsFactory.sol#176-185):\n[low/medium] reentrancy-events at src/ArtHouse.sol:179: Reentrancy in ArtHouse.createCollection(string,string,address) (src/ArtHouse.sol#179-189):\n[low/medium] timestamp at src/ArtifactsPiece.sol:241: ArtifactsPiece._beforeTransfer(address,address,uint256) (src/ArtifactsPiece.sol#241-249) uses timestamp for comparisons\n[low/medium] timestamp at src/ArtifactsSource.sol:111: ArtifactsSource.setAuction(uint256,uint64) (src/ArtifactsSource.sol#111-121) uses timestamp for comparisons\n[low/medium] timestamp at src/ArtifactsSource.sol:147: ArtifactsSource.settle() (src/ArtifactsSource.sol#147-174) uses timestamp for comparisons\n[low/medium] timestamp at src/ArtifactsPiece.sol:207: ArtifactsPiece.release() (src/ArtifactsPiece.sol#207-227) uses timestamp for comparisons\n[low/medium] timestamp at src/ArtifactsSource.sol:123: ArtifactsSource.bid(address) (src/ArtifactsSource.sol#123-135) uses timestamp for comparisons\n[low/medium] timestamp at src/ArtifactsPiece.sol:160: ArtifactsPiece.delist() (src/ArtifactsPiece.sol#160-165) uses timestamp for comparisons","passed":true},{"durationMs":391,"exitCode":0,"name":"aderyn","output":"[high] abi-encode-packed-hash-collision at src/ArtCollection.sol:43: `abi.encodePacked()` Hash Collision (4 places)\n[high] contract-locks-ether at src/ArtifactsFactory.sol:12: Contract locks Ether without a withdraw function\n[high] eth-send-unchecked-address at src/ArtifactsPiece.sol:229: ETH transferred without address checks (2 places)\n[high] reentrancy-state-change at src/ArtHouse.sol:297: Reentrancy: State change after external call (3 places)\n[high] yul-return at src/ArtifactsPiece.sol:330: Yul block contains `return`\n[low] centralization-risk at src/ArtHouse.sol:145: Centralization Risk (26 places)\n[low] empty-block at src/base/ERC721Lite.sol:121: Empty Block\n[low] large-numeric-literal at src/ArtHouse.sol:15: Large Numeric Literal (6 places)\n[low] literal-instead-of-constant at src/ArtifactsSource.sol:128: Literal Instead of Constant (15 places)\n[low] require-revert-in-loop at src/ArtHouse.sol:331: Loop Contains `require`/`revert`\n[low] state-change-without-event at src/ArtCollection.sol:20: State Change Without Event (3 places)\n[low] state-no-address-check at src/ArtCollection.sol:22: Address State Variable Set Without Checks (5 places)\n[low] storage-array-length-not-cached at src/ArtifactsSource.sol:95: Storage Array Length not Cached\n[low] unchecked-return at src/ArtCollection.sol:41: Unchecked Return (4 places)\n[low] unsafe-erc20-operation at src/ArtHouse.sol:371: Unsafe ERC20 Operation (2 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"c0acc08f656c29fe92b69e2d58529093746be8d2ab7373a1848a926992875420","verifiedTreeHash":"570ffc8d9870d7439bb1aa43f2449afb9a97ad4f","verifierVersion":"0.1.0+7129bf9d"},{"checks":[{"durationMs":122485,"exitCode":0,"name":"build","output":"Compiling 45 files with Solc 0.8.26\nSolc 0.8.26 finished in 122.40s\nCompiler run successful!\n","passed":true},{"durationMs":9626,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/Adversarial.t.sol:PieceEdgeTest\n[PASS] test_cancelThenSecondBuyerAndRoyaltyToChangedPayout() (gas: 798863)\n[PASS] test_listingEdges() (gas: 738907)\n[PASS] test_releaseAtExactly72HoursByStranger() (gas: 610430)\n[PASS] test_revealNeedsLockedPayload_andEmptyPayloadURI() (gas: 880566)\n[PASS] test_safeTransferReceivers() (gas: 666148)\n[PASS] test_transferAuthEdges() (gas: 370785)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 20.51ms (1.61ms CPU time)\n\nRan 6 tests for test/Artifacts.t.sol:ArtifactsPieceEscrowTest\n[PASS] test_escrowCancelBeforeKey_andDelist() (gas: 479568)\n[PASS] test_escrowHappyPath_buyerConfirms() (gas: 896900)\n[PASS] test_escrowTimeoutRelease() (gas: 473128)\n[PASS] test_plainTransferClearsKey_previousOwnerRekeys() (gas: 502300)\n[PASS] test_reentrancyOnEscrowWithdrawBlocked() (gas: 819586)\n[PASS] test_sourceOnlyReveal() (gas: 37353)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 30.60ms (2.68ms CPU time)\n\nRan 18 tests for test/ArtHouse.t.sol:ArtHouseTest\n[PASS] testFuzz_ethSplitInvariant(uint96,bool,bool) (runs: 64, μ: 1155463, ~: 1139447)\n[PASS] testFuzz_imdSplitInvariant(uint96,bool) (runs: 64, μ: 1258731, ~: 1275956)\n[PASS] test_artistPayoutOnlyArtist_keptOnReapproval() (gas: 183316)\n[PASS] test_collectionMintOnlyHouse() (gas: 76575)\n[PASS] test_curationFlow_andFailures() (gas: 916261)\n[PASS] test_customSplit() (gas: 1102714)\n[PASS] test_deploymentWiring() (gas: 96667)\n[PASS] test_mintFailures() (gas: 1494452)\n[PASS] test_onlyCuratorApproves() (gas: 122517)\n[PASS] test_pieceOver100KBRejected() (gas: 21294399)\n[PASS] test_recordSaleOnlyModules() (gas: 76106)\n[PASS] test_reentrancyOnWithdrawBlocked() (gas: 1809358)\n[PASS] test_sealNeedsEveryPieceFinalized() (gas: 517350)\n[PASS] test_setHouseOnce() (gas: 78200)\n[PASS] test_submissions() (gas: 260059)\n[PASS] test_tokenURIOnChain() (gas: 1167025)\n[PASS] test_twoStepOwnership_noRenounce() (gas: 196292)\n[PASS] test_withdrawNothingReverts_andHouseGoesToCurrentOwner() (gas: 1212364)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 56.10ms (72.89ms CPU time)\n\nRan 3 tests for test/Adversarial.t.sol:RuntimeTest\n[PASS] test_implementationsAreLocked() (gas: 103776)\n[PASS] test_launchedRuntimesAreBoundedAndHaveNoEscapeOpcodes() (gas: 37545397)\n[PASS] test_noRenounceAnywhere() (gas: 152574)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 62.02ms (52.81ms CPU time)\n\nRan 20 tests for test/Adversarial.t.sol:ArtHouseEdgeTest\n[PASS] testFuzz_everyoneWithdrawsExactlyTheirShare(uint96,uint96,uint96) (runs: 256, μ: 1930392, ~: 1932547)\nLogs:\n  Bound result 22110\n  Bound result 102066948\n  Bound result 2920324621732422699540872995\n\n[PASS] testFuzz_splitConservesAndHouseTakesRounding(uint16,uint16,uint96,uint8) (runs: 512, μ: 177203, ~: 172749)\nLogs:\n  Bound result 6383\n  Bound result 58\n  Bound result 123404\n\n[PASS] test_artistEdges() (gas: 1232979)\n[PASS] test_clearThenReuploadKeepsSizeAccounting() (gas: 40741031)\n[PASS] test_collectionErc721Edges() (gas: 1437099)\n[PASS] test_constructorsDoNotCallDependencies() (gas: 34804)\n[PASS] test_constructorsRejectZeroDependencies() (gas: 4484)\n[PASS] test_curationEdges() (gas: 761505)\n[PASS] test_mintWithIMD_falseReturnRejected() (gas: 1083600)\n[PASS] test_mintWithIMD_feeOnTransferRejected() (gas: 1309215)\n[PASS] test_mintWithIMD_stalePriceRejected() (gas: 1211359)\n[PASS] test_mintZeroPricesReject() (gas: 928284)\n[PASS] test_moduleCollectionsHaveNoPlainCuration() (gas: 331035)\n[PASS] test_pendingOwnerHasNoPowerBeforeAccepting() (gas: 195921)\n[PASS] test_registerModuleCollectionTwiceReverts() (gas: 44679)\n[PASS] test_revokedBrokerKeepsEarnedBalance_butEarnsNoMore() (gas: 1232728)\n[PASS] test_sealEmptyCollectionRevertsAndSealIsOnce() (gas: 647755)\n[PASS] test_splitExtremes() (gas: 1236295)\n[PASS] test_submissionEdges() (gas: 245622)\n[PASS] test_withdrawToRevertingPayoutKeepsBalance() (gas: 1297621)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 63.53ms (99.75ms CPU time)\n\nRan 9 tests for test/Artifacts.t.sol:ArtifactsFactoryTest\n[PASS] test_buyFailures() (gas: 60773432)\n[PASS] test_buyRecordsSaleAndAssignsEveryEditionOnce() (gas: 143287368)\n[PASS] test_claimFlow_andFailures() (gas: 62152804)\n[PASS] test_publishAndLock_accessAndFailures() (gas: 60751769)\n[PASS] test_saleControl() (gas: 60819000)\n[PASS] test_sealTokenURIMatchesR8Frame() (gas: 517384269)\n[PASS] test_viewingKeyZeroRejected() (gas: 32711)\n[PASS] test_vrfCallbackCannotBeReplayed() (gas: 61220414)\n[PASS] test_vrfMustBeConfiguredOnce() (gas: 81590)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 657.04ms (820.95ms CPU time)\n\nRan 6 tests for test/Adversarial.t.sol:SourceEdgeTest\n[PASS] testFuzz_payloadHashIsKeccakOfConcatenation(bytes,bytes,bytes) (runs: 64, μ: 415912, ~: 412474)\n[PASS] test_bidBoundaries() (gas: 800779)\n[PASS] test_payloadChunkLimits() (gas: 5433939)\n[PASS] test_setAuctionEdges() (gas: 373429)\n[PASS] test_settleEdges_brokerSplit() (gas: 5227404)\n[PASS] test_settleWithUnapprovedBrokerPaysHouse() (gas: 4513316)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 657.13ms (11.04ms CPU time)\n\nRan 1 test for test/invariants/SourceAuctionInvariant.t.sol:SourceAuctionInvariantTest\n[PASS]\nSourceAuctionInvariantTest invariants:\n[PASS] invariant_auctionConservation\n[PASS] invariant_highBidMonotone\n[PASS] invariant_settlementIsFinal\n SourceAuctionInvariantTest invariants (runs: 96, calls: 5760, reverts: 0)\n\n╭----------------+--------------------+-------+---------+----------╮\n| Contract       | Selector           | Calls | Reverts | Discards |\n+==================================================================+\n| AuctionHandler | bid                | 816   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| AuctionHandler | payloadStaysLocked | 775   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| AuctionHandler | recordHigh         | 787   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| AuctionHandler | setAuction         | 793   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| AuctionHandler | settle             | 872   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| AuctionHandler | warp               | 893   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| AuctionHandler | withdrawRefund     | 824   | 0       | 0        |\n╰----------------+--------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 1000\n  Bound result 3448\n  Bound result 1060\n  Bound result 24576\n  Bound result 2146\n  Bound result 1028\n  Bound result 2000000000000000256\n  Bound result 1176\n  Bound result 4000000000000000009\n  Bound result 48\n  Bound result 2752\n  Bound result 1235\n  Bound result 4597287390613669607\n  Bound result 6000000000000003590\n  Bound result 2\n  Bound result 3700\n  Bound result 300\n  Bound result 616\n  Bound result 29\n  Bound result 363347540626008517\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 730.94ms (725.98ms CPU time)\n\nRan 1 test for test/invariants/ArtifactsFactoryInvariant.t.sol:ArtifactsFactoryInvariantTest\n[PASS]\nArtifactsFactoryInvariantTest invariants:\n[PASS] invariant_claimedPiecesBelongToBuyers\n[PASS] invariant_counts\n[PASS] invariant_ledgerHoldsEveryPurchase\n[PASS] invariant_lockedAndPriced\n ArtifactsFactoryInvariantTest invariants (runs: 96, calls: 5760, reverts: 0)\n\n╭----------------+----------------+-------+---------+----------╮\n| Contract       | Selector       | Calls | Reverts | Discards |\n+==============================================================+\n| FactoryHandler | buy            | 676   | 0       | 0        |\n|----------------+----------------+-------+---------+----------|\n| FactoryHandler | claim          | 700   | 0       | 0        |\n|----------------+----------------+-------+---------+----------|\n| FactoryHandler | fulfil         | 721   | 0       | 0        |\n|----------------+----------------+-------+---------+----------|\n| FactoryHandler | lockedForever  | 749   | 0       | 0        |\n|----------------+----------------+-------+---------+----------|\n| FactoryHandler | replay         | 744   | 0       | 0        |\n|----------------+----------------+-------+---------+----------|\n| FactoryHandler | setPrice       | 706   | 0       | 0        |\n|----------------+----------------+-------+---------+----------|\n| FactoryHandler | strangerFulfil | 710   | 0       | 0        |\n|----------------+----------------+-------+---------+----------|\n| FactoryHandler | toggleSale     | 754   | 0       | 0        |\n╰----------------+----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 4264337593543950335\n  Bound result 4264337593543950335\n  Bound result 7710\n  Bound result 4264337593543950335\n  Bound result 4264337593543950335\n  Bound result 4264337593543950335\n  Bound result 1787634519125399109\n  Bound result 8355\n  Bound result 4264337593543950335\n  Bound result 4264337593543950335\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 856.00ms (821.13ms CPU time)\n\nRan 1 test for test/invariants/PieceEscrowInvariant.t.sol:PieceEscrowInvariantTest\n[PASS]\nPieceEscrowInvariantTest invariants:\n[PASS] invariant_escrowConservation\n[PASS] invariant_escrowSellerOwns\n[PASS] invariant_royaltyAndIdentity\n[PASS] invariant_singleToken\n PieceEscrowInvariantTest invariants (runs: 96, calls: 5760, reverts: 0)\n\n╭---------------+--------------------+-------+---------+----------╮\n| Contract      | Selector           | Calls | Reverts | Discards |\n+=================================================================+\n| EscrowHandler | approveAndTransfer | 456   | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| EscrowHandler | buy                | 447   | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| EscrowHandler | cancel             | 428   | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| EscrowHandler | delist             | 428   | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| EscrowHandler | list               | 463   | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| EscrowHandler | postKey            | 404   | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| EscrowHandler | registerKey        | 441   | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| EscrowHandler | release            | 481   | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| EscrowHandler | rewrap             | 443   | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| EscrowHandler | transfer           | 434   | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| EscrowHandler | warp               | 449   | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| EscrowHandler | withdraw           | 448   | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| EscrowHandler | withdrawRoyalty    | 438   | 0       | 0        |\n╰---------------+--------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 1085\n  Bound result 64000\n  Bound result 222721\n  Bound result 9540\n  Bound result 990000000000000000\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.25s (1.22s CPU time)\n\nRan 5 tests for test/Source.t.sol:ArtifactsSourceTest\n[PASS] test_auctionNeedsLockedPayload() (gas: 521262)\n[PASS] test_auctionReopensWhenNoBids() (gas: 364838)\n[PASS] test_auctionRules() (gas: 5311676)\n[PASS] test_bidReentrancyAndRefundPull() (gas: 680831)\n[PASS] test_revealedTokenURIIsPayloadByteForByte() (gas: 2432252652)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 2.06s (2.05s CPU time)\n\nRan 1 test for test/invariants/ArtHouseInvariant.t.sol:ArtHouseInvariantTest\n[PASS]\nArtHouseInvariantTest invariants:\n[PASS] invariant_constants\n[PASS] invariant_ethConservation\n[PASS] invariant_imdConservation\n[PASS] invariant_noOverWithdrawal\n[PASS] invariant_supplyAndSeal\n ArtHouseInvariantTest invariants (runs: 128, calls: 5120, reverts: 0)\n\n╭---------------+---------------+-------+---------+----------╮\n| Contract      | Selector      | Calls | Reverts | Discards |\n+============================================================+\n| LedgerHandler | handOver      | 410   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | mintETH       | 423   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | mintIMD       | 432   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | moduleSale    | 414   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | setBroker     | 414   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | setPayout     | 447   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | setPrices     | 430   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | setSaleOpen   | 430   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | setSplit      | 422   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | strangerCalls | 418   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | withdraw      | 422   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LedgerHandler | withdrawHouse | 458   | 0       | 0        |\n╰---------------+---------------+-------+---------+----------╯\n\nLogs:\n  Bound result 255\n  Bound result 244\n  Bound result 1626\n  Bound result 5998\n  Bound result 29\n  Bound result 7\n  Bound result 2000\n  Bound result 846\n  Bound result 10120\n  Bound result 7718\n  Bound result 978\n  Bound result 1101\n  Bound result 18\n  Bound result 4610\n  Bound result 4166\n  Bound result 3267\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.78s (2.77s CPU time)\n\nRan 11 tests for test/Adversarial.t.sol:ArtifactsEdgeTest\n[PASS] testFuzz_anyWordsAssignDistinctEditions(uint256[8]) (runs: 256, μ: 63365579, ~: 63367610)\n[PASS] testFuzz_sealFramesAnyTraits(uint8,uint8,uint8,uint8,uint8) (runs: 16, μ: 513899594, ~: 513897928)\nLogs:\n  Bound result 7\n\n[PASS] test_buyRevertsAtomicallyWhenCoordinatorReverts() (gas: 58867240)\n[PASS] test_buyerAsBrokerEarnsNothing_andOneWeiPriceWorks() (gas: 61122980)\n[PASS] test_callbackWithNoWordsReverts() (gas: 61156148)\n[PASS] test_claimedPieceIsAnEip1167CloneOfTheImplementation() (gas: 61853458)\n[PASS] test_configureVRFRejectsZeroes() (gas: 222611)\n[PASS] test_duplicateRequestIdRejected() (gas: 58969617)\n[PASS] test_nonOwnerCannotRunTheSale() (gas: 60626470)\n[PASS] test_setPieceEdges() (gas: 60855176)\n[PASS] test_wrappedKeyEdges() (gas: 61887966)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 9.53s (10.69s CPU time)\n\nRan 13 test suites in 9.53s (18.75s CPU time): 88 tests passed, 0 failed, 0 skipped (88 total tests)\n","passed":true},{"durationMs":91,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"ArtCollection.approve(address,uint256)\",\"ArtCollection.initialize(address,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.approveArtist(address,string)\",\"ArtHouse.clearPiece(address,uint256)\",\"ArtHouse.createCollection(string,string,address)\",\"ArtHouse.decideSubmission(uint256,bool)\",\"ArtHouse.finalizePiece(address,uint256)\",\"ArtHouse.mint(address,uint256,address)\",\"ArtHouse.mintWithIMD(address,uint256,address,uint256)\",\"ArtHouse.recordSale(address,address,address)\",\"ArtHouse.registerModuleCollection(address,address)\",\"ArtHouse.registerPiece(address,uint256,bytes32,string,string)\",\"ArtHouse.revokeArtist(address)\",\"ArtHouse.sealCollection(address)\",\"ArtHouse.setBroker(address,bool)\",\"ArtHouse.setPayout(address)\",\"ArtHouse.setPrices(address,uint256,uint256)\",\"ArtHouse.setSaleModule(address,bool)\",\"ArtHouse.setSaleOpen(address,bool)\",\"ArtHouse.setSplit(address,uint16,uint16,uint16)\",\"ArtHouse.submit(bytes32,string)\",\"ArtHouse.transferOwnership(address)\",\"ArtHouse.uploadChunk(address,uint256,uint256,bytes)\",\"ArtHouse.withdraw(address)\",\"ArtHouse.withdrawHouse(address)\",\"ArtStorage.acceptOwnership()\",\"ArtStorage.clear(address,uint256)\",\"ArtStorage.setHouse(address)\",\"ArtStorage.transferOwnership(address)\",\"ArtStorage.write(address,uint256,uint256,bytes)\",\"ArtifactsFactory.acceptOwnership()\",\"ArtifactsFactory.buy(address)\",\"ArtifactsFactory.claim(uint256)\",\"ArtifactsFactory.configureVRF(address,bytes32,uint256,bool)\",\"ArtifactsFactory.lock()\",\"ArtifactsFactory.postWrappedKey(uint256,bytes)\",\"ArtifactsFactory.rawFulfillRandomWords(uint256,uint256[])\",\"ArtifactsFactory.registerViewingKey(bytes32)\",\"ArtifactsFactory.setPiece(uint256,(string,string,uint8,string),bytes,bytes32)\",\"ArtifactsFactory.setPrice(uint256)\",\"ArtifactsFactory.setSaleOpen(bool)\",\"ArtifactsFactory.transferOwnership(address)\",\"ArtifactsPiece.approve(address,uint256)\",\"ArtifactsPiece.buy(bytes32)\",\"ArtifactsPiece.cancel()\",\"ArtifactsPiece.delist()\",\"ArtifactsPiece.initialize(address,address,uint256,address,(string,string,uint8,string),address,bytes32,bytes,bool)\",\"ArtifactsPiece.list(uint256)\",\"ArtifactsPiece.postEscrowKey(bytes)\",\"ArtifactsPiece.registerViewingKey(bytes32)\",\"ArtifactsPiece.release()\",\"ArtifactsPiece.rewrapFor(bytes)\",\"ArtifactsPiece.safeTransferFrom(address,address,uint256)\",\"ArtifactsPiece.safeTransferFrom(address,address,uint256,bytes)\",\"ArtifactsPiece.setApprovalForAll(address,bool)\",\"ArtifactsPiece.setRevealed()\",\"ArtifactsPiece.transferFrom(address,address,uint256)\",\"ArtifactsPiece.withdraw()\",\"ArtifactsSource.acceptOwnership()\",\"ArtifactsSource.bid(address)\",\"ArtifactsSource.lockPayload()\",\"ArtifactsSource.resetPayload()\",\"ArtifactsSource.setAuction(uint256,uint64)\",\"ArtifactsSource.settle()\",\"ArtifactsSource.storePayloadChunk(bytes)\",\"ArtifactsSource.transferOwnership(address)\",\"ArtifactsSource.withdrawRefund()\"],\"files\":{\".gitignore\":3,\"README.md\":134,\"foundry.toml\":25,\"reference/PROTOCOL.md\":86,\"reference/RULES.md\":135,\"reference/check-framed.js\":25,\"reference/framed/framed-003.svg\":1,\"reference/framed/framed-MASTER.svg\":1,\"reference/rules.js\":106,\"reference/the-source.payload.b64\":1,\"remappings.txt\":1,\"script/Deploy.s.sol\":36,\"src/ArtCollection.sol\":54,\"src/ArtHouse.sol\":447,\"src/ArtStorage.sol\":82,\"src/ArtifactsFactory.sol\":272,\"src/ArtifactsPiece.sol\":326,\"src/ArtifactsSource.sol\":182,\"src/base/ERC721Lite.sol\":122,\"src/base/ReentrancyGuard.sol\":15,\"src/base/TwoStepOwnable.sol\":40,\"src/interfaces/Interfaces.sol\":68,\"src/libs/Clones.sol\":27,\"src/libs/Encoding.sol\":64,\"src/libs/SSTORE2.sol\":41,\"src/libs/Seal.sol\":80,\"test/Adversarial.t.sol\":1139,\"test/ArtHouse.t.sol\":364,\"test/Artifacts.t.sol\":362,\"test/Fixture.sol\":99,\"test/Source.t.sol\":221,\"test/invariants/ArtHouseInvariant.t.sol\":405,\"test/invariants/ArtifactsFactoryInvariant.t.sol\":231,\"test/invariants/PieceEscrowInvariant.t.sol\":292,\"test/invariants/SourceAuctionInvariant.t.sol\":213,\"test/mocks/AdversarialMocks.sol\":101,\"test/mocks/Mocks.sol\":88,\"test/utils/Base64Decode.sol\":76},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"e22094f85fe3a92ea6ceaec2026c28dfd431c55d1574c2a47c7e5f52b26ddae2","verifiedTreeHash":"b378ba025a7caccf2bd282547b9b12223f52d5ae","verifierVersion":"0.1.0+7129bf9d"}]}