{"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":"f84b106e-ad6d-412e-aaaf-01f96ebd67b1","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"1a7e54aaf251f8b0298065acee0d44e1ffc8b9d12167f38cbc140177dd49e089","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":"8a675cb5ea0f0e4c425ae626e91852bf479b3a61743ab8c4b6bac66c2474f2f6","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":"bbebc07595062b916ab647bf967303bfcdc806be51876f1e4b4671e65fa65e36","dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","state":"waiting"},{"acceptedSubmissionHash":"787113ed64d777ce0b8378bbbdac5a7c4bd4e1a3e8812f43181aba755971778f","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":"08dc474087d1454c3af836abe2bd69245cc95cdfdbbaf1d922cf7d16720d8800","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":"78f35d9503988361a88d7efc6b77e7d7836ae7da15d5731721b3c9a47916b190","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":"3dacaa6e682f965a57d1a74fef8ab4a0e7d19ac687bbb4c3410f4e71e1f2fab2","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"failed"},{"acceptedSubmissionHash":"4fe45278a1bcac43f4b0eb986220d99f76f7a1ff8969ab4343fabb08546a00fe","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","state":"accepted"}],"objective":"A custom token: Plushies In Control (PIC).\nToken name: Plushies In Control\nToken symbol: PIC\nToken supply: 1,000,000,000 with 18 decimals, all minted once to the deployer in the constructor.\nWhat it does: Plushies In Control (PIC) on Ethereum mainnet, paired with FWA. 10% of the supply seeds the PIC/FWA pool, 10% goes to the swarm, and the remaining 80% (800,000,000 PIC) goes to HolderClaim in the launch transaction. No website.\n\nBuild one contract, HolderClaim, for holders of the FWAIR PFPs NFT. It has no owner, no admin functions and no way to withdraw tokens. It must be named exactly HolderClaim, because the launch sends the 80% to it by that name.\n\nConstants:\nPFP = 0x29f1cBC8eccD64B0cE777f5Da45c72C47383a620 (FWAIR PFPs, ERC-721 on Ethereum mainnet).\nMIN_ID = 1, MAX_ID = 111 (fixed supply, all minted in the PFP constructor).\nSHARE = 800,000,000 PIC / 111 per token ID.\nVESTING = 20 days. START = block.timestamp in the constructor.\nThe constructor takes only the PIC token ($token).\n\nVesting: each id unlocks 5% of SHARE per day, linearly per second: vested(id) = SHARE * min(block.timestamp - START, VESTING) / VESTING. After 20 days the full SHARE is vested.\n\nHolder of an id: if PFP.ownerOf(id) == PFP itself, the id is locked in the PFP contract and its holder is PFP.tokenLocks(id).holder; otherwise the holder is PFP.ownerOf(id). Burned ids (ownerOf reverts) have no holder.\n\nclaim(uint256[] ids):\nFor each id: require 1 <= id <= 111 and that the holder of the id (as defined above) == msg.sender.\nPay vested(id) - claimed(id) for each id, record claimed(id) before paying, and pay the total in one transfer. Ids with nothing to pay are skipped; repeating an id in the same array must not pay twice.\nAccounting is per id, not per wallet: whatever an id has vested and not yet claimed goes to whoever holds that id when claim is called, so unclaimed tokens move with the NFT on a sale or transfer. No NFT is burned or transferred by this contract.\n\nShares of burned ids, unclaimed amounts and rounding dust stay locked in the contract forever.\n\nViews: claimed(id), vested(id), holderOf(id), claimable(id) returning vested(id) - claimed(id), and claimableFor(account, ids) returning the","parentJobId":null,"planHash":"975acfc7879119121aa7f8c0fd2bc9196db7a5753a0d2af791e37b2cc55018ef","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"f84b106e-ad6d-412e-aaaf-01f96ebd67b1","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-1525-plushies-in-control"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51139","feedbackHash":"7fbc4f0f0eea072540665d033eac31613d75d99b417bec4df31fd2e14b2daf1a","nodeKey":"audit_economics","submissionHash":"1a7e54aaf251f8b0298065acee0d44e1ffc8b9d12167f38cbc140177dd49e089","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51144","feedbackHash":"b3941118f50e46b2a35a7ef654a5abb069a336fd818a65ad9ccf7b291ced815c","nodeKey":"audit_flow","submissionHash":"8a675cb5ea0f0e4c425ae626e91852bf479b3a61743ab8c4b6bac66c2474f2f6","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50995","feedbackHash":"4e9cfe250221759c384051deac9a28f64e671e79d77b63af9f07e1870ad00fb9","nodeKey":"audit_judge","submissionHash":"bbebc07595062b916ab647bf967303bfcdc806be51876f1e4b4671e65fa65e36","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51483","feedbackHash":"8fb4239c438bc98789ba1b7992f8a63a494309b7b56990ab7ade3a4ff680ecb6","nodeKey":"audit_math","submissionHash":"787113ed64d777ce0b8378bbbdac5a7c4bd4e1a3e8812f43181aba755971778f","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51352","feedbackHash":"ebfd5cb95d08cf46aa9b089d7628a61217c19f21817c12ded474a6d5fd5001c8","nodeKey":"audit_permissions","submissionHash":"08dc474087d1454c3af836abe2bd69245cc95cdfdbbaf1d922cf7d16720d8800","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50956","feedbackHash":"f6146dde576bb06077fd12bae28f2c93b58b61644a8a9c7fb0f765f3c4e4fcb7","nodeKey":"build_contract_project","submissionHash":"78f35d9503988361a88d7efc6b77e7d7836ae7da15d5731721b3c9a47916b190","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51377","feedbackHash":"a82d686ab03e2e3d4a0041f7c6526d8b2235fe8b44510eff94c5bf72c94abecd","nodeKey":"manifest","submissionHash":"3dacaa6e682f965a57d1a74fef8ab4a0e7d19ac687bbb4c3410f4e71e1f2fab2","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51002","feedbackHash":"e1be566fcf7c3126920f04001f2c2fc7858fe5885241f852330017a3a836e635","nodeKey":"write_foundry_tests","submissionHash":"4fe45278a1bcac43f4b0eb986220d99f76f7a1ff8969ab4343fabb08546a00fe","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"147a5f59ecb22282aee4a190a10537a23e62cbd4d76d31c9e95fc82412e42c07","state":"blocked","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"8f08088e7a7f557f","findings":[{"citation":"resolved","description":"Access x economics seam, reported as a trust assumption rather than a defect: the brief says accounting is per id and whatever is vested and unclaimed goes to whoever holds the id when claim is called. The code does exactly that, so there is no checkpoint at transfer. Consequences buyers and custodians must price in: (1) a seller can front-run their own sale with claim(ids) in the same block, so the buyer receives an NFT whose claimable(id) is already zero even though the listing was priced with the accrued PIC; (2) anyone who can hold the NFT for one transaction (an approved operator doing transferFrom to itself, an NFT flash-loan or rental protocol, a marketplace that escrows) can call claim and keep the PIC, and the original owner cannot recover it. Approved operators are correctly refused by holderOf (the fork test confirms), but an operator can move the NFT to itself first. No contract change is required to satisfy the brief; the author may want the README to state the front-run case explicitly for marketplace buyers.","line":94,"path":"src/HolderClaim.sol","reproduction":"Mainnet-fork check on the real PFP (block 26181878): deploy PICToken and HolderClaim, fund 800,000,000 PIC, warp 10 days. owner1 = PFP.ownerOf(1). vm.prank(owner1); claim([1]) pays SHARE*10/20 = 3603603603603603603603603. owner1 then transferFrom(owner1, buyer, 1). Warp 10 more days: buyer claim([1]) pays only the other half; buyer had no way to require the first half. Same sequence with the transfer and the seller claim in one block gives the buyer an NFT with claimable(1) == 0 at purchase. Expected per brief: exactly this (accounting per id). Actual: exactly this. Listed so the judge records the trust assumption, not as a code defect.","severity":"info","snippet":"            address holder = holderOf(id);\n            if (holder == address(0) || holder != msg.sender) revert NotHolder(id);","title":"Claim rights follow NFT custody at call time: a seller or temporary custodian can take the accrued PIC before the buyer receives the NFT (design-inherent, documented)"},{"citation":"resolved","description":"Provenance note only; no behavioural change. Byte comparison against raw files at tag v5.1.0 shows ERC20.sol, IERC20.sol, IERC20Metadata.sol, ReentrancyGuard.sol, Context.sol, Panic.sol, SafeCast.sol, SignedMath.sol, Errors.sol, Strings.sol and the ERC-165/6093/1363 interfaces identical. SafeERC20.sol, utils/Address.sol, utils/math/Math.sol, token/ERC721/ERC721.sol, IERC721Receiver.sol and ERC721Utils.sol differ only by forge fmt line wrapping (parameter lists folded onto one line, `(bool success, )` to `(bool success,)`); the token flow and SafeERC20 logic compiled into PICToken and HolderClaim is upstream. A reviewer verifying the archive hash or diffing files will see a mismatch against the stated claim. The verifier can refresh the files from the archive or amend the note to say they were reformatted.","line":5,"path":"lib/openzeppelin-contracts/VENDORED.md","reproduction":"From the repository root: fetch https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/v5.1.0/contracts/token/ERC20/utils/SafeERC20.sol and diff it against lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol. Expected per VENDORED.md: no difference. Actual: lines 119-125 of upstream (transferFromAndCallRelaxed signature) are reflowed onto lines 119-121; `diff -w` is empty, confirming whitespace only.","severity":"info","snippet":"Vendored the transitive contracts/ subset needed by src/ and test/, plus upstream license files, as ordinary files. Source contents are unmodified.","title":"VENDORED.md says OpenZeppelin sources are unmodified, but six vendored files are whitespace-reformatted relative to v5.1.0"}],"hash":"08dc474087d1454c3af836abe2bd69245cc95cdfdbbaf1d922cf7d16720d8800","nodeId":"0c4a15cc-e601-4f10-8cb4-aa59e56d3be0","outcome":"completed","summary":"Review complete. The findings file is written and no source files were changed.\n\n**Outcome:** no defects found in the Access Control, Trust Gap or Asymmetry areas. The permission model holds as specified, and both entries in `.imd-findings.json` are informational.\n\nWhat I checked:\n- **Access control.** The only state-changing entry point in HolderClaim is `claim`. It refuses a zero holder, requires the live holder to equal the caller, writes `claimed[id]` before the single transfer, and is guarded against reentrancy. There is no owner, admin, withdraw or recovery function in the ABI. The token's three entry points are byte-identical OpenZeppelin v5.1.0 ERC20 with no overrides.\n- **Trust gap with the real PFP.** I fetched the verified FWAIR PFP source from Sourcify. Its transfer hook only lets an NFT enter the PFP through `lock`, which sets the record first, only releases it to the recorded holder, deletes the record on withdraw, and refuses to burn a locked id. So `holderOf` cannot be fooled by a stale lock record or an unrecorded escrow. The PFP owner's powers (start or end a beta, base URI, recover ETH) cannot move an NFT or alter a lock record.\n- **Fork verification.** A scratch test on a mainnet fork against the real PFP bytecode confirmed: an escrow-locked holder (id 2, one of 15 currently locked) claims its full share after 20 days, the PFP address itself is refused, a direct owner claims half at day 10 and a buyer gets only the other half, and a burned id has no holder. All three tests passed. The scratch directory was removed afterwards.\n- **Asymmetry.** `claimableFor` deduplicates and skips non-held ids while `claim` reverts on them; this matches the brief and the README. Duplicate ids in `claim` pay zero on the second pass.\n\nThe two informational entries are the design-inherent seam that claim rights follow custody at call time (a seller can front-run a sale with `claim`, so buyers must price that in), and a provenance note that six vendored OpenZeppelin files are whitespace-reformatted while VENDORED.md says they are unmodified. Neither needs a contract change.\n\nCoverage rows are recorded for all four listed entry points plus three invariant rows, all `holds`. The existing suite of 43 tests also passes.","treeHash":null,"usage":{"cachedInputTokens":1242507,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":21243,"runtime":"claude","turns":32,"wallClockMs":568487}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"8d26cb1a4eaeb75a","findings":[{"citation":"resolved","description":"This is the behaviour the brief asks for (\"whatever an id has vested and not yet claimed goes to whoever holds that id when claim is called\"), so it is not a defect in the code; it is recorded as the one economic risk the design carries so the author and judge see it was examined. Entitlement is per id and is paid to the address that holds the id in the block the claim executes. Nothing is snapshotted at listing, sale or lock time. Consequences: (1) a seller who lists an id on a marketplace can call claim in the same block as, and ahead of, the buyer's fill, so the buyer receives an NFT with claimable(id) == 0 even if the listing was priced on the unclaimed balance; (2) any protocol that temporarily hands custody of a PFP to a third party (NFT flash-loan, rental, collateralised lending that transfers the token) lets that party claim the full unclaimed amount during custody and the beneficial owner cannot recover it; (3) an escrow that holds the token in a contract which cannot call claim (a marketplace escrow, a lending vault) freezes that id's vesting until the token leaves the escrow, although the PFP's own beta escrow is handled correctly through tokenLocks. No funds are lost from the contract itself: total paid per id never exceeds SHARE and accounting is conserved. Buyers must read claimable(id) in the same transaction as the purchase, or price the NFT as if unclaimed vesting is zero.","line":94,"path":"src/HolderClaim.sol","reproduction":"State: HolderClaim deployed and funded with 800,000,000 PIC at START; alice holds id 1; block.timestamp = START + 10 days, so claimable(1) == SHARE/2 == 3603603603603603603603603. Alice lists id 1 for sale. In the block the buyer's fill lands, alice first calls claim([1]) from her address: it pays 3603603603603603603603603 PIC to alice and sets claimed[1] = SHARE/2. The marketplace then transfers id 1 to bob. bob calls claimable(1) -> 0 and claim([1]) -> returns 0, no transfer. Expected by a buyer who priced the listing on the unclaimed balance: SHARE/2 moves with the NFT. Actual: it was paid to the seller in the same block. The same sequence with an NFT flash-loan (borrow id 1, claim, return in one transaction) pays the borrower and leaves the lender with claimed[1] == vested at that moment. Verified locally with test/HolderClaim.t.sol test_claimedAccountingSurvivesSaleAndTransferBack semantics and against the verified FWAIRPFPs source: the contract itself never pays above SHARE per id.","severity":"info","snippet":"            address holder = holderOf(id);\n            if (holder == address(0) || holder != msg.sender) revert NotHolder(id);","title":"Claim rights follow the holder at call time, so a seller can strip vested PIC immediately before a sale and a borrowed or rented PFP lets the borrower take the lender's accrued vesting (by design; eco"},{"citation":"resolved","description":"A reviewer who relies on this provenance note and the archive hash to skip diffing the vendored library would miss that one file was rewritten. The actual change is harmless: contracts/token/ERC20/utils/SafeERC20.sol has the transferFromAndCallRelaxed signature re-wrapped to the repository's forge fmt style (lines 119-125 upstream become three lines); no token, byte or semantic change, and the compiled HolderClaim behaviour is identical. Every other vendored file HolderClaim and PICToken import (ERC20.sol, IERC20.sol, IERC20Metadata.sol, ReentrancyGuard.sol, Context.sol, draft-IERC6093.sol, IERC1363.sol, IERC165.sol, introspection/IERC165.sol) is byte-identical to upstream v5.1.0. Either restore the upstream bytes or amend the note to say the subset was formatted.","line":5,"path":"lib/openzeppelin-contracts/VENDORED.md","reproduction":"curl -s https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/v5.1.0/contracts/token/ERC20/utils/SafeERC20.sol | sha256sum -> d67b0f3d9302b2ff7cec76776fc2c1f3686082287441c15c6871c0086a8a4f74. sha256sum lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol -> 1b841bb806398200a81bdc55ef0a7ed217f4926466477207adb68b927f865639. diff of the two files shows only the transferFromAndCallRelaxed(IERC1363 token, address from, address to, uint256 value, bytes memory data) signature re-wrapped; `diff -w` is empty. Expected per VENDORED.md: identical files. Actual: a formatted copy.","severity":"info","snippet":"Vendored the transitive contracts/ subset needed by src/ and test/, plus upstream license files, as ordinary files. Source contents are unmodified.","title":"VENDORED.md states the OpenZeppelin sources are unmodified, but SafeERC20.sol differs from the v5.1.0 upstream file (whitespace-only reformat of one function signature)"}],"hash":"1a7e54aaf251f8b0298065acee0d44e1ffc8b9d12167f38cbc140177dd49e089","nodeId":"5282fc57-b39e-4c70-b1be-0c533b9c50cc","outcome":"completed","summary":"The findings file is in place and the tree is clean apart from git-excluded paths. The review is complete.\n\n**Outcome: no defects found in HolderClaim or PICToken within the Economic Security, Invariant and Flow Gap areas.** The findings file holds two info-level notes and a coverage record for all four listed entry points plus six invariant and flow rows.\n\n**What I checked**\n\n- Traced `claim` end to end: per-id cumulative accounting, duplicate handling, the single SafeERC20 transfer, reentrancy guard, and rollback on a failed, false-returning or no-code token. Vesting math cannot overflow and repeated claims lose no rounding.\n- Fetched the verified FWAIRPFPs source from Sourcify and compiled it under a scratch test at the real PFP address. HolderClaim's holder resolution matched the genuine contract for escrowed, withdrawn, sold and burned ids. The real contract refuses direct transfers into itself and clears approvals on transfer, so a stale or empty lock record while the PFP owns the token is unreachable.\n- Read live mainnet state: beta 1 ended on 2026-10-08 with 15 PFPs still escrowed, and an escrowed id returns a non-zero lock holder, so the lock branch is live in production today and behaves as specified.\n- Diffed every vendored OpenZeppelin file the contracts import against upstream v5.1.0. All are byte-identical except SafeERC20.sol, which differs by whitespace only.\n- Ran the existing suite: 43 tests pass, including the 128-run stateful invariant campaign.\n\n**Info notes recorded**\n\n1. Entitlement follows the holder at call time, as the brief requires. A seller can claim in the same block ahead of a sale, and a borrowed or rented PFP lets the borrower take the lender's accrued vesting. This is a buyer-side trust assumption rather than a code defect, with a concrete sequence given.\n2. The vendoring note claims the OpenZeppelin sources are unmodified, but SafeERC20.sol was reformatted. Semantically identical; hashes and the diff are in the reproduction.\n\n**Not covered**: launch-side behaviour (whether the factory routes the 80% remainder to HolderClaim by name, and any seed dust forwarded with it) sits outside the code in this tree and could not be verified here. The scratch test directory was removed after use.","treeHash":null,"usage":{"cachedInputTokens":1597691,"inputTokens":546,"model":"claude-fable-5-1","outputTokens":32511,"runtime":"claude","turns":36,"wallClockMs":506718}},{"artifacts":[],"attempt":1,"bundleHash":"e21a0aea9e9129a1eed9341ec14a88cfe56527b10c117eedac76a2e7d09123e8","device":"d2ea4ab6ad35e6a1","findings":[],"hash":"3dacaa6e682f965a57d1a74fef8ab4a0e7d19ac687bbb4c3410f4e71e1f2fab2","nodeId":"c13f56e7-2ab1-4633-970f-cc05fffea9d1","outcome":"completed","summary":"Created [launch.json](/home/imd-worker/.identitymd-1181/work/f84b106e-ad6d-412e-aaaf-01f96ebd67b1/c13f56e7-2ab1-4633-970f-cc05fffea9d1/launch.json), matching the accepted contracts and constructor arguments. No other submitted files changed.\n\n`forge build` succeeded; all 43 tests passed.\n\nRequired economics are preserved verbatim. Notes flag that they leave HolderClaim unfunded, conflicting with the requested 80% allocation.","treeHash":"192c77174f381283e02110b52e9850005ec49dc4","usage":{"cachedInputTokens":417920,"inputTokens":36585,"model":"gpt-6-astra","outputTokens":3491,"runtime":"codex","turns":4,"wallClockMs":208087}},{"artifacts":[],"attempt":1,"bundleHash":"61db9bf080abb534f5397fee8b5d75eac832fe838e57d23ae5323cd5cd8c6610","device":"ea5566a759ea17b3","findings":[],"hash":"4fe45278a1bcac43f4b0eb986220d99f76f7a1ff8969ab4343fabb08546a00fe","nodeId":"f66bf7ce-74d0-415a-aa8d-858e55f3c145","outcome":"completed","summary":"Added failure-path and fuzz tests, plus expanded randomized invariants for PIC and HolderClaim.\n\nOffline `forge build` and `forge test` passed: **53 tests**, including **45,056 invariant calls**.\n\nAll changes are under `test/`. No implementation defects were reproduced.","treeHash":"b5835c748452fb97b4aad9a1eb4ced57757229c1","usage":{"cachedInputTokens":2553728,"inputTokens":132842,"model":"gpt-6-astra","outputTokens":18399,"runtime":"codex","turns":7,"wallClockMs":890902}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"f96a0c89c039c22a","findings":[],"hash":"787113ed64d777ce0b8378bbbdac5a7c4bd4e1a3e8812f43181aba755971778f","nodeId":"d4363b07-33d7-434c-88b1-ce123a243291","outcome":"completed","summary":"I found no math defects in HolderClaim or PICToken, so `.imd-findings.json` has an empty findings list and a coverage record. I read the code and worked the numbers by hand; I wrote no Foundry test and ran neither the project's tests nor `forge`.\n\nWhat I checked against the precision, boundary and numerical-gap guides:\n\n- **Per-id share:** `SHARE` comes out to 7,207,207,207,207,207,207,207,207 wei per id. All 111 shares add up to 23 wei less than 800M PIC, so the contract always holds enough to pay, and those 23 wei stay locked as the spec says.\n- **Vesting curve:** `SHARE * elapsed / VESTING` never goes down over time and stops at exactly `SHARE` once 20 days have passed. Multiplying before dividing keeps the rounding loss under 1 wei per call, and nothing comes close to overflowing.\n- **Claim accounting:** `claimed[id]` is set to the vested amount before the single transfer, so claimed never exceeds vested. A repeated id in the same array pays 0 the second time.\n- **Id bounds:** ids 0 and 112 are rejected, ids 1 and 111 are accepted. In `claimableFor`, the shift `1 << (id - 1)` is at most `1 << 110`, which fits.\n- **Static-analysis leads:**\n  - Slither's strict-equality warning is the `amount == 0` check, which only skips ids with nothing to pay. It is not a bug.\n  - Its timestamp warnings are expected, because the vesting is meant to be time-based.\n  - The rest (unused return, calls in a loop) don't change any amount.\n- **PICToken:** it is the standard OpenZeppelin ERC-20 with a fixed supply of 1,000,000,000 × 10¹⁸ minted once to the deployer. Its `approve`, `transfer` and `transferFrom` contain no custom math.\n\nAll four listed entry points are marked `holds`, plus one extra row for the \"never pays out more than 800M\" invariant.","treeHash":null,"usage":{"cachedInputTokens":116323,"inputTokens":6,"model":"claude-opus-5-5","outputTokens":1582,"runtime":"claude","turns":3,"wallClockMs":18888}},{"artifacts":[],"attempt":1,"bundleHash":"f47cd3953897fe587d8b6b5360cd39775c16115d346b7a4c0260def1a8592529","device":"bb0a3bf63233e5e5","findings":[],"hash":"78f35d9503988361a88d7efc6b77e7d7836ae7da15d5731721b3c9a47916b190","nodeId":"0c241231-b4f2-4d8e-9e55-48466d4b1072","outcome":"completed","summary":"Implemented PIC and ownerless `HolderClaim`, including vesting, locked-NFT holder resolution, duplicate protection, and atomic payouts. Dependencies are vendored; deployment assumptions and responsibilities are documented.\n\nVerified with Solidity 0.8.26:\n\n- `forge build` and offline rebuild pass.\n- `forge test`: 43 passing, including 8,192 invariant actions.\n- `forge fmt --check` passes.\n\nThe truncated `claimableFor` requirement is implemented as the sum for distinct supplied IDs currently held by the account.","treeHash":"a8ccea935b0cbeaad84d7550186fe71f82e01364","usage":{"cachedInputTokens":1059840,"inputTokens":87076,"model":"gpt-6-astra","outputTokens":28340,"runtime":"codex","turns":7,"wallClockMs":908963}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"91067b3439961e55","findings":[{"citation":"resolved","description":"Not a code defect: HolderClaim has no funding hook, by design (no owner, no admin, constructor takes only $token). Its entire usefulness depends on the launch transaction delivering 800,000,000 PIC to it. The README tells the manifest writer to set the remainder recipient to HolderClaim, but the supplied launch reference states economics.remainderTo is copied from the job verbatim and admission refuses any other copy, and that $requester resolves to economics.remainderTo. The task text says the launch sends the 80% to the contract named HolderClaim, so the routing is the launch system's responsibility. Recording this so the manifest/admission stage confirms the routing, since nothing in the code can. If the remainder lands anywhere else, every claim reverts until someone voluntarily transfers PIC to the contract, and START keeps vesting meanwhile.","line":32,"path":"README.md","reproduction":"Deploy PICToken then HolderClaim(token) without transferring 800,000,000e18 PIC to it. vm.warp(START + 20 days). As the holder of id 1 call claim([1]). Expected per spec: SHARE = 7207207207207207207207207 paid. Actual: revert ERC20InsufficientBalance(holderClaim, 0, 7207207207207207207207207) from the single safeTransfer at src/HolderClaim.sol:104; claimed[1] is rolled back. After transferring 800,000,000e18 PIC to HolderClaim the same call pays SHARE. Verified locally with the project's test_insufficientFundingRevertsAllAccountingAndCanRetryAfterFunding.","severity":"info","snippet":"The separate manifest step must resolve the remainder recipient to the deployed **HolderClaim**, and pass `$token` to its constructor. This repository intentionally supplies no `launch.json`, signing key, broadcasting script, pool implementation, or swarm distributor. The launch network supplies those integration pieces and performs deployment after acceptance.","title":"HolderClaim is only operable if the launch routes the 80% remainder to it; README's manifest instruction conflicts with the launch reference's remainderTo rule"}],"hash":"8a675cb5ea0f0e4c425ae626e91852bf479b3a61743ab8c4b6bac66c2474f2f6","nodeId":"f931f542-09af-456f-bd10-df0adb9ec90b","outcome":"completed","summary":"The review is complete and `.imd-findings.json` is written and valid. No code defect was found in HolderClaim or PICToken.\n\n**What I checked, in my area (Execution Trace, Periphery, First Principles)**\n\n- **claim path traced end to end.** Each id is validated, resolved through the PFP, matched to the caller, accounted before the single transfer, and deduplicated by the cumulative `claimed` write. The subtraction cannot underflow because `claimed` only ever holds an earlier value of the monotone vesting floor. Reentrancy is guarded and the PFP reads are staticcalls.\n- **The holder identity assumption was tested against the live PFP logic, not the mock.** I pulled the verified FWAIRPFPs source from Sourcify and built it at the fixed mainnet address in a scratch harness. Its transfer hook refuses any transfer into the PFP except through `lock`, escrow can only leave to the recorded holder, `withdraw` deletes the record, and locked ids cannot be burned. So an id owned by the PFP always has a correct, non-zero lock holder, and burned ids resolve to no holder. Nine scenarios passed, including a 111-id batch.\n- **Periphery integrity.** The vendored OpenZeppelin v5.1.0 and forge-std v1.9.7 files match the upstream archives once formatter whitespace is ignored, and the archive hashes match the VENDORED.md records.\n- **Static-analysis leads** (strict equality, unused `betaId`, calls in loop, timestamp use) were each traced and none produces a reproducible defect.\n\n**What I reported**\n\nOne info-level note, not a code defect. HolderClaim's operability depends entirely on the launch routing the 80% remainder to it. The README tells the manifest step to point the remainder recipient at HolderClaim, while the launch reference says the remainder address is copied from the job verbatim. The task text says the launch sends the share to the contract by name, so this is for the manifest and admission stage to confirm.\n\n**Coverage record**\n\nAll four listed entry points are marked `holds`, plus rows for the holder identity assumption, vesting arithmetic, the views, library integrity, static-analysis leads and the funding dependency.\n\nScratch tests were deleted, and `git status --porcelain` is clean apart from the required findings file.","treeHash":null,"usage":{"cachedInputTokens":1566119,"inputTokens":578,"model":"claude-fable-5-1","outputTokens":27904,"runtime":"claude","turns":36,"wallClockMs":445061}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"b5e3297a04468fd3","findings":[{"citation":"resolved","description":"The brief requires 10% of the supply to seed the PIC/FWA pool, 10% to the swarm and 800,000,000 PIC (80%) to HolderClaim in the launch transaction. The committed manifest's economics, which the reference states are this launch's own and are copied from the job verbatim, give poolBps 8800 and remainderTo 0xa7e99bb7155d7477e0c838e201b16e62b9c0b5ac. Under this deployment model the factory moves 10% to the distributor, 88% to the pool and 2% (20,000,000 PIC) to remainderTo; HolderClaim is not a recipient and has no funding hook, so it starts with a zero PIC balance while START and vesting begin at construction. Nobody will hold 800,000,000 PIC after launch (the requester receives 20,000,000), so the contract cannot be made whole afterwards and the deliverable described in the brief cannot operate. The manifest's own notes acknowledge the conflict but do not resolve it. This is a policy/configuration conflict between the brief and the job's economics, not a Solidity defect: the needed evidence is the job's economics record and a decision on how the 800,000,000 PIC reaches HolderClaim (either economics that route the remainder to the HolderClaim contract, or an accepted scope change). Merged with audit_flow's info finding, elevated because the manifest now confirms the remainder recipient is not HolderClaim. No Foundry proof is attached: the defect is in the launch configuration, and no change to the Solidity code can make a test for it pass.","line":24,"path":"launch.json","reproduction":"State: factory deploys PICToken (mints 1e27 to itself) and HolderClaim($token) with CREATE2, then follows launch.json economics: 1e26 to the MerkleDistributor, 8.8e26 to the pool, 2e25 to 0xa7e99bb7155d7477e0c838e201b16e62b9c0b5ac. HolderClaim balance after the launch transaction: 0. Expected per brief: balanceOf(HolderClaim) == 800000000000000000000000000. Then vm.warp(START + 20 days); vm.prank(PFP.ownerOf(1)); holderClaim.claim([1]). Expected per brief: 7207207207207207207207207 PIC paid. Actual: revert ERC20InsufficientBalance(holderClaim, 0, 7207207207207207207207207) from the safeTransfer at src/HolderClaim.sol:104. The project's test test_insufficientFundingRevertsAllAccountingAndCanRetryAfterFunding (test/HolderClaim.t.sol:379) shows this exact revert on an unfunded contract and that only a direct 800,000,000 PIC transfer repairs it; no party holds that amount under the manifest's economics.","severity":"high","snippet":"    \"poolBps\": 8800,\n    \"initialMarketCapWei\": \"2000000000000000000000000\",\n    \"remainderTo\": \"0xa7e99bb7155d7477e0c838e201b16e62b9c0b5ac\"","title":"Launch economics in launch.json send 88% to the pool and the 2% remainder to the requester, so HolderClaim is never funded and every claim reverts"},{"citation":"resolved","description":"The tree now contains launch.json (added by commit f03144f). README.md line 24 documents the pool share as 10% (poolBps = 1000) and line 32 says the repository supplies no launch.json and that the remainder recipient must be HolderClaim, while launch.json lines 24-26 set poolBps 8800 and remainderTo to the requester address. A launch operator following the README's pre-launch checklist ('verify ... that the remainder routing funds HolderClaim atomically') is told something the committed manifest does not do. The README should describe the manifest actually in the tree and the open allocation conflict, or be corrected once finding 1 is resolved.","line":32,"path":"README.md","reproduction":"grep -n 'intentionally supplies no' README.md -> line 32 says no launch.json is supplied; ls launch.json -> file exists. grep -n poolBps README.md launch.json -> README.md:24 'poolBps = 1000', launch.json:24 '\"poolBps\": 8800'. Expected: documentation and manifest agree. Actual: they contradict each other on the pool share, the remainder recipient and the presence of the manifest.","severity":"low","snippet":"The separate manifest step must resolve the remainder recipient to the deployed **HolderClaim**, and pass `$token` to its constructor. This repository intentionally supplies no `launch.json`, signing key, broadcasting script, pool implementation, or swarm distributor. The launch network supplies those integration pieces and performs deployment after acceptance.","title":"README contradicts the committed launch.json: it says no launch.json is supplied, states poolBps = 1000 and tells the manifest to route the remainder to HolderClaim"},{"citation":"resolved","description":"Merged duplicate of the audit_economics and audit_permissions findings. The brief states that whatever an id has vested and not yet claimed goes to whoever holds that id when claim is called, and the code does exactly that with no snapshot at transfer or lock time. Consequences for market participants: a seller can call claim in the block before the sale fills, so the buyer receives an NFT whose claimable(id) is zero; any party that custodies the NFT for one transaction (an approved operator that first moves the token to itself, an NFT flash loan or rental) can claim and keep the PIC; an NFT held by a contract that cannot call claim earns nothing until it leaves that contract (the PFP's own beta escrow is handled through tokenLocks). The contract never pays more than SHARE per id and accounting is conserved. Not a code defect; recorded so the trust assumption is visible to buyers and to the author's README.","line":94,"path":"src/HolderClaim.sol","reproduction":"HolderClaim funded with 800,000,000 PIC at START; alice holds id 1; vm.warp(START + 10 days): claimable(1) == 3603603603603603603603603. alice calls claim([1]) -> receives 3603603603603603603603603, claimed[1] == 3603603603603603603603603. alice transfers id 1 to bob in the same block. bob: claimable(1) == 0, claim([1]) returns 0 with no transfer. At START + 20 days bob can claim only the remaining 3603603603603603603603604. Expected per brief: exactly this. Verified by the project's test test_claimedAccountingSurvivesSaleAndTransferBack (test/HolderClaim.t.sol:190), which passes.","severity":"info","snippet":"            address holder = holderOf(id);\n            if (holder == address(0) || holder != msg.sender) revert NotHolder(id);","title":"Claim rights follow the NFT's holder at call time: a seller or temporary custodian can take accrued PIC ahead of a buyer (by design, trust assumption)"},{"citation":"resolved","description":"Merged duplicate of the audit_economics and audit_permissions findings. Byte comparison against the raw files at tag v5.1.0 shows contracts/token/ERC20/utils/SafeERC20.sol, utils/Address.sol, utils/math/Math.sol, token/ERC721/ERC721.sol, token/ERC721/IERC721Receiver.sol and token/ERC721/utils/ERC721Utils.sol differ only in forge fmt line wrapping; stripping all whitespace makes each pair identical, and ERC20.sol, IERC20.sol, ReentrancyGuard.sol and the other files PICToken and HolderClaim compile are byte-identical. No behavioural change; the provenance note overstates what a reviewer checking the archive hash will see. Amend the note to say the subset was reformatted, or restore upstream bytes.","line":5,"path":"lib/openzeppelin-contracts/VENDORED.md","reproduction":"curl -s https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/v5.1.0/contracts/token/ERC20/utils/SafeERC20.sol | diff - lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol -> upstream lines 119-125 (transferFromAndCallRelaxed signature) are reflowed onto three lines; the same with whitespace removed (tr -d ' \\n') shows no difference. Same result for utils/Address.sol, utils/math/Math.sol, token/ERC721/ERC721.sol, token/ERC721/IERC721Receiver.sol, token/ERC721/utils/ERC721Utils.sol. Expected per VENDORED.md: identical files. Actual: reformatted copies.","severity":"info","snippet":"Vendored the transitive contracts/ subset needed by src/ and test/, plus upstream license files, as ordinary files. Source contents are unmodified.","title":"VENDORED.md says the OpenZeppelin v5.1.0 sources are unmodified, but six vendored files are whitespace-reformatted"}],"hash":"bbebc07595062b916ab647bf967303bfcdc806be51876f1e4b4671e65fa65e36","nodeId":"9e904757-42cb-4220-99ec-d4c76c163b9c","outcome":"completed","summary":"All four snippets now match the tree, and `git status` shows no tracked files changed (the findings file is gitignored). The review is complete.\n\n**Outcome:** the Solidity code holds. The one serious problem is in the launch configuration, not the contracts.\n\n**What I verified**\n- Built the project and ran the full suite: 53 tests pass, including the stateful invariant campaign.\n- Pulled the verified FWAIRPFPs source from Sourcify. Its `lock` moves the NFT into the PFP contract and records the holder in `tokenLocks`, direct transfers to the PFP are blocked, and burns make `ownerOf` revert. HolderClaim's holder resolution matches this exactly.\n- Traced `claim`: id validation, holder check against the caller, cumulative accounting written before a single guarded transfer, duplicates harmless, underfunding reverts the whole batch. Vesting math cannot overflow or underflow. Both contracts are small and deploy under CREATE2 with no external calls in constructors.\n- PICToken is byte-identical OpenZeppelin v5.1.0 ERC20 with a one-time mint. Its three listed entry points hold.\n\n**Findings written to `.imd-findings.json`**\n1. **High.** The committed launch.json gives the pool 88% and routes the 2% remainder to the requester's address. HolderClaim is not a recipient, so it starts empty while vesting runs, and every claim reverts with an insufficient-balance error. Nobody will hold 800,000,000 PIC after launch to repair it. This merges and elevates the flow specialist's info note now that the manifest confirms the routing. It is a brief-versus-economics conflict that needs a scope decision, so no Foundry proof is attached.\n2. **Low.** README says no launch.json is supplied, states a 10% pool share and tells the manifest to route the remainder to HolderClaim. All three contradict the committed manifest.\n3. **Info.** Claim rights follow custody at call time, so a seller or temporary custodian can take accrued PIC ahead of a buyer. By design per the brief. Merged from the economics and permissions specialists.\n4. **Info.** VENDORED.md says the OpenZeppelin sources are unmodified, but six files are whitespace-reformatted. Confirmed harmless by diffing against upstream. Merged from two specialists.\n\n**Coverage:** all four verifier entry points answered as holds, plus four invariant rows, one of them pointing at finding 1.","treeHash":null,"usage":{"cachedInputTokens":804956,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":14985,"runtime":"claude","turns":13,"wallClockMs":362689}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"a4e285247c6f8aed","findings":[],"hash":"ed96b2cfe934aa7b802a1547ad138ac23ef0d8c1904d3d0122d7ef6398dd28ef","nodeId":"c13f56e7-2ab1-4633-970f-cc05fffea9d1","outcome":"failed","summary":"needs input: The brief requires 10% of the PIC supply to seed the PIC/FWA pool and 800,000,000 PIC (80%) to go to HolderClaim in the launch transaction, but the job's paid economics ({\"poolBps\":8800,\"initialMarketCapWei\":\"2000000000000000000000000\",\"remainderTo\":\"0xa7e99bb7155d7477e0c838e201b16e62b9c0b5ac\"}) send 88% to the pool and the 2% remainder (20,000,000 PIC) to 0xa7e99bb7155d7477e0c838e201b16e62b9c0b5ac. launch.json must copy those economics verbatim, the token must mint its whole supply to the factory, and the factory pays nothing to application contracts, so HolderClaim starts with zero PIC and every claim reverts. Only the requester can change the job's economics or the scope. — How should the 800,000,000 PIC reach HolderClaim: should the job's economics be changed to poolBps 1000 with the remainder routed to the HolderClaim contract (so the launch funds it), or do you accept a scope change in which the launch keeps poolBps 8800 and the 2% remainder goes to 0xa7e99bb7155d7477e0c838e201b1","treeHash":null,"usage":{"cachedInputTokens":696425,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":11440,"runtime":"claude","turns":16,"wallClockMs":263851}}],"verification":[{"checks":[{"durationMs":2011,"exitCode":0,"name":"build","output":"Compiling 52 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.89s\nCompiler run successful!\nwarning[unused-return]: return value of an external call is not used\n   ╭▸ src/HolderClaim.sol:60:9\n   │\n60 │         (holder,) = IFWAIRPFP(PFP).tokenLocks(id);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/HolderClaim.sol:52:13\n   │\n52 │         try IFWAIRPFP(PFP).ownerOf(id) returns (address owner) {\n   │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/HolderClaim.sol:60:21\n   │\n60 │         (holder,) = IFWAIRPFP(PFP).tokenLocks(id);\n   │                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/HolderClaim.sol:114:41\n    │\n114 │         if (id < MIN_ID || id > MAX_ID) revert InvalidId(id);\n    │                                         ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n   ╭▸ src/HolderClaim.sol:95:63\n   │\n95 │             if (holder == address(0) || holder != msg.sender) revert NotHolder(id);\n   │                                                               ━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/HolderClaim.sol:109:13\n    │\n109 │         if (elapsed >= VESTING) return SHARE;\n    │             ━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\n","passed":true},{"durationMs":28473,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 7 tests for test/HolderClaimSecurity.t.sol:HolderClaimSecurityTest\n[PASS] test_falseReturnRollsBackAllAccountingAndTokenState() (gas: 402154)\n[PASS] test_invalidReturnRollsBack() (gas: 241214)\n[PASS] test_noReturnTokenWorksAndOnlyOneTransferIsUsed() (gas: 230496)\n[PASS] test_reentryBlockedAndAllBatchEffectsVisibleBeforeTransfer() (gas: 1192928)\n[PASS] test_revertingTransferBubblesErrorAndRollsBack() (gas: 210614)\n[PASS] test_tokenWithoutCodeCannotConsumeClaim() (gas: 120722)\n[PASS] test_zeroPayoutNeverCallsToken() (gas: 247820)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 2.59ms (1.83ms CPU time)\n\nRan 8 tests for test/PICToken.t.sol:PICTokenTest\n[PASS] testFuzz_transfersConserveFixedSupply(uint256,uint256) (runs: 512, μ: 163690, ~: 165225)\nLogs:\n  Bound result 29427933868\n  Bound result 29427933865\n\n[PASS] test_approvalsTransferFromAndRevocation() (gas: 263554)\n[PASS] test_deployerCannotSpendUnapprovedHolderTokens() (gas: 94885)\n[PASS] test_insufficientBalanceAndInvalidReceiverRevert() (gas: 61886)\n[PASS] test_launchAllocationAndAllTransferFlowsArriveWhole() (gas: 740767)\n[PASS] test_metadataAndWholeSupplyMintedToDeployer() (gas: 73647)\n[PASS] test_noMintBurnPauseSeizureOrUpgradeEntryPoints() (gas: 707655)\n[PASS] test_zeroAndSelfTransfersPreserveSupply() (gas: 83315)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 21.46ms (22.57ms CPU time)\n\nRan 27 tests for test/HolderClaim.t.sol:HolderClaimTest\n[PASS] testFuzz_duplicateBatchMatchesUniquePreview(uint8[],uint32) (runs: 512, μ: 1677105, ~: 1726229)\nLogs:\n  Bound result 10229\n\n[PASS] testFuzz_invalidIdsRevertAcrossViewsAndAtomicClaim(uint256) (runs: 512, μ: 140478, ~: 140428)\n[PASS] testFuzz_splitClaimsAndSaleConserveOneShare(uint32,uint32,uint8) (runs: 512, μ: 325216, ~: 318853)\nLogs:\n  Bound result 109\n  Bound result 1\n  Bound result 4\n\n[PASS] test_approvedOperatorCannotClaim() (gas: 158331)\n[PASS] test_batchPaysOnceAndDuplicatesDoNotDoubleCount() (gas: 334558)\n[PASS] test_burnedAndUnclaimedAllocationsStayLockedAfterVestingEnds() (gas: 3213371)\n[PASS] test_burnedIdsHaveNoHolderAndCannotPayEvenWithStaleLock() (gas: 327083)\n[PASS] test_claimAllLeavesOnlyDivisionDustAndExtraDepositsLocked() (gas: 3985325)\n[PASS] test_claimAtStartIsNoop() (gas: 64233)\n[PASS] test_claimableForFiltersOwnersAndBurnsAndDeduplicates() (gas: 483919)\n[PASS] test_claimedAccountingSurvivesSaleAndTransferBack() (gas: 402506)\n[PASS] test_constantsAndConstructor() (gas: 89737)\n[PASS] test_constructorDoesNotCallExternalContractsOrMoveSupply() (gas: 52601)\n[PASS] test_constructorRejectsZeroToken() (gas: 3774)\n[PASS] test_dailyClaimsPayCumulativeDifferenceWithoutLosingRounding() (gas: 1893784)\n[PASS] test_directOwnerTakesPrecedenceOverStaleLockAndFailingLockLookup() (gas: 199596)\n[PASS] test_emptyBatchIsNoop() (gas: 47401)\n[PASS] test_failedLockLookupRevertsWithoutPayingOrUpdatingAccounting() (gas: 218688)\n[PASS] test_fullyClaimedIdsAreSkippedAlongsideNewClaims() (gas: 193297)\n[PASS] test_insufficientFundingRevertsAllAccountingAndCanRetryAfterFunding() (gas: 348903)\n[PASS] test_lockedNftPaysBeneficialHolderThenUnlockedSaleChangesHolder() (gas: 519638)\n[PASS] test_lockedWithoutHolderIsUnclaimable() (gas: 154815)\n[PASS] test_noAdminOrRecoveryEntryPoints() (gas: 203560)\n[PASS] test_unauthorizedLaterIdRevertsWholeBatch() (gas: 192887)\n[PASS] test_unclaimedVestingMovesWithNftWithoutResettingSchedule() (gas: 249660)\n[PASS] test_vestsPerSecondAndCapsAtTwentyDays() (gas: 163162)\n[PASS] test_wrongHolderRevertsIncludingAtStartAndAfterFullClaim() (gas: 182613)\nSuite result: ok. 27 passed; 0 failed; 0 skipped; finished in 93.96ms (139.06ms CPU time)\n\nRan 1 test for test/HolderClaimInvariant.t.sol:HolderClaimInvariantTest\n[PASS] invariant_scheduleOwnershipAndConservation() (runs: 128, calls: 8192, reverts: 0)\n\n╭--------------+-------------+-------+---------+----------╮\n| Contract     | Selector    | Calls | Reverts | Discards |\n+=========================================================+\n| ClaimHandler | advanceTime | 1353  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| ClaimHandler | burnNft     | 1380  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| ClaimHandler | claimBatch  | 1419  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| ClaimHandler | claimOne    | 1381  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| ClaimHandler | toggleLock  | 1299  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| ClaimHandler | transferNft | 1360  | 0       | 0        |\n╰--------------+-------------+-------+---------+----------╯\n\nLogs:\n  Bound result 0\n  Bound result 4371\n  Bound result 952\n  Bound result 123\n  Bound result 40\n  Bound result 111\n  Bound result 213887\n  Bound result 1618\n  Bound result 86397\n  Bound result 20\n  Bound result 5\n  Bound result 204873\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 28.36s (28.35s CPU time)\n\nRan 4 test suites in 28.36s (28.47s CPU time): 43 tests passed, 0 failed, 0 skipped (43 total tests)\n","passed":true},{"durationMs":70,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"HolderClaim.claim(uint256[])\",\"PICToken.approve(address,uint256)\",\"PICToken.transfer(address,uint256)\",\"PICToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":83,\"docs/FWAIRPFP.abi.json\":45,\"docs/PFP-ABI.md\":14,\"docs/SECURITY.md\":31,\"foundry.toml\":21,\"launch.json\":29,\"remappings.txt\":2,\"src/HolderClaim.sol\":116,\"src/PICToken.sol\":14,\"src/interfaces/IFWAIRPFP.sol\":9,\"test/HolderClaim.t.sol\":489,\"test/HolderClaimInvariant.t.sol\":172,\"test/HolderClaimSecurity.t.sol\":127,\"test/PICToken.t.sol\":155,\"test/mocks/AdversarialToken.sol\":90,\"test/mocks/MockPFP.sol\":58},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"3dacaa6e682f965a57d1a74fef8ab4a0e7d19ac687bbb4c3410f4e71e1f2fab2","verifiedTreeHash":"192c77174f381283e02110b52e9850005ec49dc4","verifierVersion":"0.1.0+327e1e9f"},{"checks":[{"durationMs":2635,"exitCode":0,"name":"build","output":"Compiling 54 files with Solc 0.8.26\nSolc 0.8.26 finished in 2.52s\nCompiler run successful!\nwarning[unused-return]: return value of an external call is not used\n   ╭▸ src/HolderClaim.sol:60:9\n   │\n60 │         (holder,) = IFWAIRPFP(PFP).tokenLocks(id);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/HolderClaim.sol:52:13\n   │\n52 │         try IFWAIRPFP(PFP).ownerOf(id) returns (address owner) {\n   │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/HolderClaim.sol:60:21\n   │\n60 │         (holder,) = IFWAIRPFP(PFP).tokenLocks(id);\n   │                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/HolderClaim.sol:114:41\n    │\n114 │         if (id < MIN_ID || id > MAX_ID) revert InvalidId(id);\n    │                                         ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n   ╭▸ src/HolderClaim.sol:95:63\n   │\n95 │             if (holder == address(0) || holder != msg.sender) revert NotHolder(id);\n   │                                                               ━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/HolderClaim.sol:109:13\n    │\n109 │         if (elapsed >= VESTING) return SHARE;\n    │             ━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\n","passed":true},{"durationMs":58403,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 7 tests for test/HolderClaimSecurity.t.sol:HolderClaimSecurityTest\n[PASS] test_falseReturnRollsBackAllAccountingAndTokenState() (gas: 402154)\n[PASS] test_invalidReturnRollsBack() (gas: 241214)\n[PASS] test_noReturnTokenWorksAndOnlyOneTransferIsUsed() (gas: 230496)\n[PASS] test_reentryBlockedAndAllBatchEffectsVisibleBeforeTransfer() (gas: 1192928)\n[PASS] test_revertingTransferBubblesErrorAndRollsBack() (gas: 210614)\n[PASS] test_tokenWithoutCodeCannotConsumeClaim() (gas: 120722)\n[PASS] test_zeroPayoutNeverCallsToken() (gas: 247820)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 6.42ms (2.36ms CPU time)\n\nRan 8 tests for test/PICToken.t.sol:PICTokenTest\n[PASS] testFuzz_transfersConserveFixedSupply(uint256,uint256) (runs: 512, μ: 163512, ~: 165201)\nLogs:\n  Bound result 372157472299616531195686082\n  Bound result 353542685060429471333868271\n\n[PASS] test_approvalsTransferFromAndRevocation() (gas: 263554)\n[PASS] test_deployerCannotSpendUnapprovedHolderTokens() (gas: 94885)\n[PASS] test_insufficientBalanceAndInvalidReceiverRevert() (gas: 61886)\n[PASS] test_launchAllocationAndAllTransferFlowsArriveWhole() (gas: 740767)\n[PASS] test_metadataAndWholeSupplyMintedToDeployer() (gas: 73647)\n[PASS] test_noMintBurnPauseSeizureOrUpgradeEntryPoints() (gas: 707655)\n[PASS] test_zeroAndSelfTransfersPreserveSupply() (gas: 83315)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 164.82ms (166.47ms CPU time)\n\nRan 27 tests for test/HolderClaim.t.sol:HolderClaimTest\n[PASS] testFuzz_duplicateBatchMatchesUniquePreview(uint8[],uint32) (runs: 512, μ: 1633020, ~: 1624374)\nLogs:\n  Bound result 77\n  Bound result 885\n\n[PASS] testFuzz_invalidIdsRevertAcrossViewsAndAtomicClaim(uint256) (runs: 512, μ: 140490, ~: 140452)\n[PASS] testFuzz_splitClaimsAndSaleConserveOneShare(uint32,uint32,uint8) (runs: 512, μ: 325346, ~: 318575)\nLogs:\n  Bound result 59\n  Bound result 7\n  Bound result 2622747\n\n[PASS] test_approvedOperatorCannotClaim() (gas: 158331)\n[PASS] test_batchPaysOnceAndDuplicatesDoNotDoubleCount() (gas: 334558)\n[PASS] test_burnedAndUnclaimedAllocationsStayLockedAfterVestingEnds() (gas: 3213371)\n[PASS] test_burnedIdsHaveNoHolderAndCannotPayEvenWithStaleLock() (gas: 327083)\n[PASS] test_claimAllLeavesOnlyDivisionDustAndExtraDepositsLocked() (gas: 3985325)\n[PASS] test_claimAtStartIsNoop() (gas: 64233)\n[PASS] test_claimableForFiltersOwnersAndBurnsAndDeduplicates() (gas: 483919)\n[PASS] test_claimedAccountingSurvivesSaleAndTransferBack() (gas: 402506)\n[PASS] test_constantsAndConstructor() (gas: 89737)\n[PASS] test_constructorDoesNotCallExternalContractsOrMoveSupply() (gas: 52601)\n[PASS] test_constructorRejectsZeroToken() (gas: 3774)\n[PASS] test_dailyClaimsPayCumulativeDifferenceWithoutLosingRounding() (gas: 1893784)\n[PASS] test_directOwnerTakesPrecedenceOverStaleLockAndFailingLockLookup() (gas: 199596)\n[PASS] test_emptyBatchIsNoop() (gas: 47401)\n[PASS] test_failedLockLookupRevertsWithoutPayingOrUpdatingAccounting() (gas: 218688)\n[PASS] test_fullyClaimedIdsAreSkippedAlongsideNewClaims() (gas: 193297)\n[PASS] test_insufficientFundingRevertsAllAccountingAndCanRetryAfterFunding() (gas: 348903)\n[PASS] test_lockedNftPaysBeneficialHolderThenUnlockedSaleChangesHolder() (gas: 519638)\n[PASS] test_lockedWithoutHolderIsUnclaimable() (gas: 154815)\n[PASS] test_noAdminOrRecoveryEntryPoints() (gas: 203560)\n[PASS] test_unauthorizedLaterIdRevertsWholeBatch() (gas: 192887)\n[PASS] test_unclaimedVestingMovesWithNftWithoutResettingSchedule() (gas: 249660)\n[PASS] test_vestsPerSecondAndCapsAtTwentyDays() (gas: 163162)\n[PASS] test_wrongHolderRevertsIncludingAtStartAndAfterFullClaim() (gas: 182613)\nSuite result: ok. 27 passed; 0 failed; 0 skipped; finished in 169.65ms (391.46ms CPU time)\n\nRan 6 tests for test/HolderClaimProperties.t.sol:HolderClaimPropertiesTest\n[PASS] testFuzz_batchOrderAndChunkingPreservePayout(uint8[48],uint8) (runs: 1000, μ: 1499227, ~: 1414343)\nLogs:\n  Bound result 16\n\n[PASS] testFuzz_claimFrequencyDoesNotChangeEntitlement(uint32[12],uint32,uint8) (runs: 1000, μ: 1042674, ~: 1086361)\nLogs:\n  Bound result 59\n  Bound result 259\n  Bound result 0\n  Bound result 223\n  Bound result 14\n  Bound result 4\n  Bound result 16\n  Bound result 2\n  Bound result 0\n  Bound result 0\n  Bound result 0\n  Bound result 0\n  Bound result 0\n  Bound result 0\n\n[PASS] testFuzz_vestingSatisfiesFloorBoundsAndDoesNotDependOnOwnership(uint256,uint8) (runs: 1000, μ: 198721, ~: 198401)\nLogs:\n  Bound result 20317\n  Bound result 95\n\n[PASS] test_invalidBoundariesRejectEvenForZeroAccountAndZeroVesting() (gas: 207038)\n[PASS] test_mixedHistoryEventsDescribeOnlyPaidIdsAndOneAggregateTransfer() (gas: 519943)\n[PASS] test_underfundedByOneWeiRollsBackPriorAccountingThenExactFundingWorks() (gas: 485354)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 169.59ms (502.49ms CPU time)\n\nRan 3 tests for test/PICTokenInvariant.t.sol:PICTokenInvariantTest\n[PASS] invariant_balancesAndAllowancesMatchIndependentLedger() (runs: 256, calls: 24576, reverts: 0)\n\n╭-----------------+--------------------+-------+---------+----------╮\n| Contract        | Selector           | Calls | Reverts | Discards |\n+===================================================================+\n| PICTokenHandler | approve            | 4811  | 0       | 0        |\n|-----------------+--------------------+-------+---------+----------|\n| PICTokenHandler | rejectZeroReceiver | 5004  | 0       | 0        |\n|-----------------+--------------------+-------+---------+----------|\n| PICTokenHandler | revokeThenTrySpend | 4830  | 0       | 0        |\n|-----------------+--------------------+-------+---------+----------|\n| PICTokenHandler | spend              | 4957  | 0       | 0        |\n|-----------------+--------------------+-------+---------+----------|\n| PICTokenHandler | transfer           | 4974  | 0       | 0        |\n╰-----------------+--------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 31\n  Bound result 0\n  Bound result 2430\n  Bound result 602\n  Bound result 0\n  Bound result 1\n  Bound result 0\n  Bound result 0\n  Bound result 2854\n  Bound result 0\n  Bound result 0\n  Bound result 0\n  Bound result 312420673011840938011470509\n  Bound result 0\n  Bound result 0\n  Bound result 123\n  Bound result 20\n  Bound result 7773\n  Bound result 0\n  Bound result 0\n  Bound result 0\n  Bound result 0\n  Bound result 0\n  Bound result 0\n  Bound result 0\n  Bound result 635088178121213968385759544\n  Bound result 0\n  Bound result 0\n  Bound result 0\n  Bound result 0\n  Bound result 75000000000000000000\n  Bound result 0\n  Bound result 15000000000000000000\n  Bound result 0\n  Bound result 999999999999999999999999997\n  Bound result 3544\n  Bound result 0\n  Bound result 262\n  Bound result 0\n  Bound result 0\n  Bound result 0\n  Bound result 750\n  Bound result 0\n  Bound result 126\n  Bound result 0\n  Bound result 65\n  Bound result 31\n  Bound result 0\n  Bound result 0\n  Bound result 15000000000000000000\n  Bound result 390566142911724614846797930\n  Bound result 16\n  Bound result 43\n  Bound result 4692\n  Bound result 3055\n  Bound result 28\n  Bound result 4\n  Bound result 0\n  Bound result 646453974764229925599505867\n  Bound result 39\n  Bound result 2\n  Bound result 0\n  Bound result 100000000000000000000000000\n  Bound result 44661832229\n\n[PASS] test_finiteAllowanceIsConsumedBySelfTransferAndZeroSpenderCannotBeApproved() (gas: 877535)\nLogs:\n  Bound result 1\n  Bound result 1\n\n[PASS] test_handlerSequencePinsFullSupplyInfiniteAllowanceAndFailedBalanceRollback() (gas: 1327203)\nLogs:\n  Bound result 1000000000000000000000000000\n  Bound result 1000000000000000000000000000\n  Bound result 999999999999999999999999999\n  Bound result 999999999999999999999999999\n  Bound result 999999999999999999999999999\n  Bound result 1\n\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 4.92s (4.92s CPU time)\n\nRan 2 tests for test/HolderClaimInvariant.t.sol:HolderClaimInvariantTest\n[PASS] invariant_scheduleOwnershipAndConservation() (runs: 256, calls: 20480, reverts: 0)\n\n╭--------------+------------------------+-------+---------+----------╮\n| Contract     | Selector               | Calls | Reverts | Discards |\n+====================================================================+\n| ClaimHandler | advanceTime            | 2071  | 0       | 0        |\n|--------------+------------------------+-------+---------+----------|\n| ClaimHandler | burnNft                | 2088  | 0       | 0        |\n|--------------+------------------------+-------+---------+----------|\n| ClaimHandler | claimAsNonHolder       | 2002  | 0       | 0        |\n|--------------+------------------------+-------+---------+----------|\n| ClaimHandler | claimBatch             | 2121  | 0       | 0        |\n|--------------+------------------------+-------+---------+----------|\n| ClaimHandler | claimOne               | 2078  | 0       | 0        |\n|--------------+------------------------+-------+---------+----------|\n| ClaimHandler | claimWithForeignSuffix | 1974  | 0       | 0        |\n|--------------+------------------------+-------+---------+----------|\n| ClaimHandler | claimWithInvalidSuffix | 2055  | 0       | 0        |\n|--------------+------------------------+-------+---------+----------|\n| ClaimHandler | donate                 | 2026  | 0       | 0        |\n|--------------+------------------------+-------+---------+----------|\n| ClaimHandler | toggleLock             | 2072  | 0       | 0        |\n|--------------+------------------------+-------+---------+----------|\n| ClaimHandler | transferNft            | 1993  | 0       | 0        |\n╰--------------+------------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 11\n  Bound result 70359955739525959532725077882107781\n  Bound result 14\n  Bound result 38\n  Bound result 31\n  Bound result 26\n  Bound result 2266\n  Bound result 35\n  Bound result 14\n  Bound result 114\n  Bound result 41\n  Bound result 48\n  Bound result 101\n  Bound result 109\n  Bound result 10\n  Bound result 1535\n  Bound result 172679\n  Bound result 259200\n  Bound result 109\n  Bound result 5\n  Bound result 20\n  Bound result 7\n  Bound result 67961916719505024649090118984908581389\n  Bound result 30\n  Bound result 63969474578958963540975248221828965752788680817320\n  Bound result 1000000000000000000\n  Bound result 11417\n  Bound result 40\n  Bound result 58\n  Bound result 63023365384589235464705619824629523281965372575342956379525367989878453250379\n  Bound result 8790\n  Bound result 172794\n  Bound result 7\n  Bound result 7000000000000000000\n  Bound result 8458\n  Bound result 10\n  Bound result 9818\n  Bound result 3\n  Bound result 5789\n  Bound result 81\n  Bound result 75\n  Bound result 25\n  Bound result 10249\n  Bound result 35\n  Bound result 7794\n\n[PASS] test_handlerSequenceIncludesFailuresDonationsLocksAndBurns() (gas: 26235630)\nLogs:\n  Bound result 86400\n  Bound result 86400\n  Bound result 1\n  Bound result 1\n  Bound result 115792089237316195423570985008687907853269984665640564039457584007913129639935\n  Bound result 1\n  Bound result 1\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 58.31s (58.35s CPU time)\n\nRan 6 test suites in 58.31s (63.74s CPU time): 53 tests passed, 0 failed, 0 skipped (53 total tests)\n","passed":true},{"durationMs":46,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"HolderClaim.claim(uint256[])\",\"PICToken.approve(address,uint256)\",\"PICToken.transfer(address,uint256)\",\"PICToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":83,\"docs/FWAIRPFP.abi.json\":45,\"docs/PFP-ABI.md\":14,\"docs/SECURITY.md\":31,\"foundry.toml\":21,\"remappings.txt\":2,\"src/HolderClaim.sol\":116,\"src/PICToken.sol\":14,\"src/interfaces/IFWAIRPFP.sol\":9,\"test/HolderClaim.t.sol\":488,\"test/HolderClaimInvariant.t.sol\":296,\"test/HolderClaimProperties.t.sol\":238,\"test/HolderClaimSecurity.t.sol\":127,\"test/PICToken.t.sol\":155,\"test/PICTokenInvariant.t.sol\":174,\"test/TESTING.md\":29,\"test/mocks/AdversarialToken.sol\":90,\"test/mocks/MockPFP.sol\":58},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"4fe45278a1bcac43f4b0eb986220d99f76f7a1ff8969ab4343fabb08546a00fe","verifiedTreeHash":"b5835c748452fb97b4aad9a1eb4ced57757229c1","verifierVersion":"0.1.0+327e1e9f"},{"checks":[{"durationMs":2190,"exitCode":0,"name":"build","output":"Compiling 52 files with Solc 0.8.26\nSolc 0.8.26 finished in 2.06s\nCompiler run successful!\nwarning[unused-return]: return value of an external call is not used\n   ╭▸ src/HolderClaim.sol:60:9\n   │\n60 │         (holder,) = IFWAIRPFP(PFP).tokenLocks(id);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/HolderClaim.sol:52:13\n   │\n52 │         try IFWAIRPFP(PFP).ownerOf(id) returns (address owner) {\n   │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/HolderClaim.sol:60:21\n   │\n60 │         (holder,) = IFWAIRPFP(PFP).tokenLocks(id);\n   │                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/HolderClaim.sol:114:41\n    │\n114 │         if (id < MIN_ID || id > MAX_ID) revert InvalidId(id);\n    │                                         ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n   ╭▸ src/HolderClaim.sol:95:63\n   │\n95 │             if (holder == address(0) || holder != msg.sender) revert NotHolder(id);\n   │                                                               ━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/HolderClaim.sol:109:13\n    │\n109 │         if (elapsed >= VESTING) return SHARE;\n    │             ━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\n","passed":true},{"durationMs":27336,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 8 tests for test/PICToken.t.sol:PICTokenTest\n[PASS] testFuzz_transfersConserveFixedSupply(uint256,uint256) (runs: 512, μ: 163232, ~: 165201)\nLogs:\n  Bound result 167152246414728229607980931\n  Bound result 1010282\n\n[PASS] test_approvalsTransferFromAndRevocation() (gas: 263554)\n[PASS] test_deployerCannotSpendUnapprovedHolderTokens() (gas: 94885)\n[PASS] test_insufficientBalanceAndInvalidReceiverRevert() (gas: 61886)\n[PASS] test_launchAllocationAndAllTransferFlowsArriveWhole() (gas: 740767)\n[PASS] test_metadataAndWholeSupplyMintedToDeployer() (gas: 73647)\n[PASS] test_noMintBurnPauseSeizureOrUpgradeEntryPoints() (gas: 707655)\n[PASS] test_zeroAndSelfTransfersPreserveSupply() (gas: 83315)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 12.98ms (14.39ms CPU time)\n\nRan 7 tests for test/HolderClaimSecurity.t.sol:HolderClaimSecurityTest\n[PASS] test_falseReturnRollsBackAllAccountingAndTokenState() (gas: 402154)\n[PASS] test_invalidReturnRollsBack() (gas: 241214)\n[PASS] test_noReturnTokenWorksAndOnlyOneTransferIsUsed() (gas: 230496)\n[PASS] test_reentryBlockedAndAllBatchEffectsVisibleBeforeTransfer() (gas: 1192928)\n[PASS] test_revertingTransferBubblesErrorAndRollsBack() (gas: 210614)\n[PASS] test_tokenWithoutCodeCannotConsumeClaim() (gas: 120722)\n[PASS] test_zeroPayoutNeverCallsToken() (gas: 247820)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 92.65ms (1.90ms CPU time)\n\nRan 27 tests for test/HolderClaim.t.sol:HolderClaimTest\n[PASS] testFuzz_duplicateBatchMatchesUniquePreview(uint8[],uint32) (runs: 512, μ: 1592603, ~: 1554811)\nLogs:\n  Bound result 164862\n\n[PASS] testFuzz_invalidIdsRevertAcrossViewsAndAtomicClaim(uint256) (runs: 512, μ: 140475, ~: 140428)\n[PASS] testFuzz_splitClaimsAndSaleConserveOneShare(uint32,uint32,uint8) (runs: 512, μ: 325618, ~: 318575)\nLogs:\n  Bound result 5\n  Bound result 399\n  Bound result 10381\n\n[PASS] test_approvedOperatorCannotClaim() (gas: 158331)\n[PASS] test_batchPaysOnceAndDuplicatesDoNotDoubleCount() (gas: 334558)\n[PASS] test_burnedAndUnclaimedAllocationsStayLockedAfterVestingEnds() (gas: 3213371)\n[PASS] test_burnedIdsHaveNoHolderAndCannotPayEvenWithStaleLock() (gas: 327083)\n[PASS] test_claimAllLeavesOnlyDivisionDustAndExtraDepositsLocked() (gas: 3985325)\n[PASS] test_claimAtStartIsNoop() (gas: 64233)\n[PASS] test_claimableForFiltersOwnersAndBurnsAndDeduplicates() (gas: 483919)\n[PASS] test_claimedAccountingSurvivesSaleAndTransferBack() (gas: 402506)\n[PASS] test_constantsAndConstructor() (gas: 89737)\n[PASS] test_constructorDoesNotCallExternalContractsOrMoveSupply() (gas: 52601)\n[PASS] test_constructorRejectsZeroToken() (gas: 3774)\n[PASS] test_dailyClaimsPayCumulativeDifferenceWithoutLosingRounding() (gas: 1893784)\n[PASS] test_directOwnerTakesPrecedenceOverStaleLockAndFailingLockLookup() (gas: 199596)\n[PASS] test_emptyBatchIsNoop() (gas: 47401)\n[PASS] test_failedLockLookupRevertsWithoutPayingOrUpdatingAccounting() (gas: 218688)\n[PASS] test_fullyClaimedIdsAreSkippedAlongsideNewClaims() (gas: 193297)\n[PASS] test_insufficientFundingRevertsAllAccountingAndCanRetryAfterFunding() (gas: 348903)\n[PASS] test_lockedNftPaysBeneficialHolderThenUnlockedSaleChangesHolder() (gas: 519638)\n[PASS] test_lockedWithoutHolderIsUnclaimable() (gas: 154815)\n[PASS] test_noAdminOrRecoveryEntryPoints() (gas: 203560)\n[PASS] test_unauthorizedLaterIdRevertsWholeBatch() (gas: 192887)\n[PASS] test_unclaimedVestingMovesWithNftWithoutResettingSchedule() (gas: 249660)\n[PASS] test_vestsPerSecondAndCapsAtTwentyDays() (gas: 163162)\n[PASS] test_wrongHolderRevertsIncludingAtStartAndAfterFullClaim() (gas: 182613)\nSuite result: ok. 27 passed; 0 failed; 0 skipped; finished in 92.73ms (144.35ms CPU time)\n\nRan 1 test for test/HolderClaimInvariant.t.sol:HolderClaimInvariantTest\n[PASS] invariant_scheduleOwnershipAndConservation() (runs: 128, calls: 8192, reverts: 0)\n\n╭--------------+-------------+-------+---------+----------╮\n| Contract     | Selector    | Calls | Reverts | Discards |\n+=========================================================+\n| ClaimHandler | advanceTime | 1348  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| ClaimHandler | burnNft     | 1317  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| ClaimHandler | claimBatch  | 1366  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| ClaimHandler | claimOne    | 1368  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| ClaimHandler | toggleLock  | 1404  | 0       | 0        |\n|--------------+-------------+-------+---------+----------|\n| ClaimHandler | transferNft | 1389  | 0       | 0        |\n╰--------------+-------------+-------+---------+----------╯\n\nLogs:\n  Bound result 6\n  Bound result 311\n  Bound result 295\n  Bound result 123\n  Bound result 4499\n  Bound result 1262\n  Bound result 75088\n  Bound result 0\n  Bound result 19\n  Bound result 2\n  Bound result 35351\n  Bound result 160\n  Bound result 5\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 27.24s (27.24s CPU time)\n\nRan 4 test suites in 27.24s (27.44s CPU time): 43 tests passed, 0 failed, 0 skipped (43 total tests)\n","passed":true},{"durationMs":51,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"HolderClaim.claim(uint256[])\",\"PICToken.approve(address,uint256)\",\"PICToken.transfer(address,uint256)\",\"PICToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":83,\"docs/FWAIRPFP.abi.json\":45,\"docs/PFP-ABI.md\":14,\"docs/SECURITY.md\":31,\"foundry.toml\":21,\"remappings.txt\":2,\"src/HolderClaim.sol\":116,\"src/PICToken.sol\":14,\"src/interfaces/IFWAIRPFP.sol\":9,\"test/HolderClaim.t.sol\":489,\"test/HolderClaimInvariant.t.sol\":172,\"test/HolderClaimSecurity.t.sol\":127,\"test/PICToken.t.sol\":155,\"test/mocks/AdversarialToken.sol\":90,\"test/mocks/MockPFP.sol\":58},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":647,"exitCode":0,"name":"slither","output":"[medium/high] incorrect-equality at src/HolderClaim.sol:90: HolderClaim.claim(uint256[]) (src/HolderClaim.sol#90-105) uses a dangerous strict equality:\n[medium/medium] unused-return at src/HolderClaim.sol:50: HolderClaim.holderOf(uint256) (src/HolderClaim.sol#50-61) ignores return value by (holder,None) = IFWAIRPFP(PFP).tokenLocks(id) (src/HolderClaim.sol#60)\n[low/medium] calls-loop at src/HolderClaim.sol:50: HolderClaim.holderOf(uint256) (src/HolderClaim.sol#50-61) has external calls inside a loop: owner = IFWAIRPFP(PFP).ownerOf(id) (src/HolderClaim.sol#52-56)\n[low/medium] calls-loop at src/HolderClaim.sol:50: HolderClaim.holderOf(uint256) (src/HolderClaim.sol#50-61) has external calls inside a loop: owner = IFWAIRPFP(PFP).ownerOf(id) (src/HolderClaim.sol#52-56)\n[low/medium] calls-loop at src/HolderClaim.sol:50: HolderClaim.holderOf(uint256) (src/HolderClaim.sol#50-61) has external calls inside a loop: (holder,None) = IFWAIRPFP(PFP).tokenLocks(id) (src/HolderClaim.sol#60)\n[low/medium] calls-loop at src/HolderClaim.sol:50: HolderClaim.holderOf(uint256) (src/HolderClaim.sol#50-61) has external calls inside a loop: (holder,None) = IFWAIRPFP(PFP).tokenLocks(id) (src/HolderClaim.sol#60)\n[low/medium] timestamp at src/HolderClaim.sol:90: HolderClaim.claim(uint256[]) (src/HolderClaim.sol#90-105) uses timestamp for comparisons\n[low/medium] timestamp at src/HolderClaim.sol:107: HolderClaim._vested() (src/HolderClaim.sol#107-111) uses timestamp for comparisons","passed":true},{"durationMs":246,"exitCode":0,"name":"aderyn","output":"[low] costly-loop at src/HolderClaim.sol:92: Costly operations inside loop\n[low] large-numeric-literal at src/HolderClaim.sol:18: Large Numeric Literal (2 places)\n[low] require-revert-in-loop at src/HolderClaim.sol:74: Loop Contains `require`/`revert` (2 places)\n[low] uninitialized-local-variable at src/HolderClaim.sol:74: Uninitialized Local Variable (2 places)\n[low] unused-public-function at src/HolderClaim.sol:65: Public Function Not Used Internally","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"78f35d9503988361a88d7efc6b77e7d7836ae7da15d5731721b3c9a47916b190","verifiedTreeHash":"a8ccea935b0cbeaad84d7550186fe71f82e01364","verifierVersion":"0.1.0+327e1e9f"}]}