{"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":"2d95a791-c71a-48e0-b7c4-68a6fda3273c","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"f46cff4a916c3288d0d655f092e9b1305c12cbe7c56d6ea43d08f472b766a0d0","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":"6e07bf14b5fff375e252a577a548e1365318a590c1bd9e8836102bce87cf7bba","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":"7a21cd9b0c8f1d5c0872806855bf94b1b541a8f13e319934061d922b185c2c4d","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":"e26d4002a29b8e2b1970284d3d359017d55de123b6d90bc1ae1898cecf30da64","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":"2c8f29155f08c0eb672aa0ee576566040518e15b92c4440a9ec653251847e695","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":"f58763ba0d32ad5744bbe0060d1e653496f9f4a72c4be15c52cbc870d3c3821e","dependsOn":[],"execution":{"mustProduce":["src/LaunchToken.sol"],"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":"d370acf32b8ea3363b93a9d57d667b0f0d9e205863525ff1dc6ec05881f34c40","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"a0c2b0538499e2fc5873bba5014d3eff52614a7d01bbe9b6ad38a640cc2d7901","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":"Bankers In Control (BIC) on Robinhood Chain, paired with IMD. 10% of the supply seeds the BIC/IMD\npool, 10% goes to the swarm, and the remaining 80% (800,000,000 BIC) goes to CharterClaim in the\nlaunch transaction. No website.\n\nBuild one contract, CharterClaim, for holders of the Founding Charter NFT. It has no owner, no\nadmin functions and no way to withdraw tokens. It must be named exactly CharterClaim, because the\nlaunch sends the 80% to it by that name.\n\nConstants:\n- CHARTER = 0x69101f92427376FA80476A997d024cf9dC3EaE39 (Founding Charter on Robinhood Chain).\n- MAX_ID = 1032. Do not read nextId(), because new Charters keep being minted.\n- SHARE = 800,000,000 BIC / 1032 per Charter ID. INSTANT = 10% of SHARE, REST = SHARE - INSTANT.\n- The constructor takes only the BIC token ($token).\n\nclaim(uint256[] ids), the 10%:\n- For each id: require 1 <= id <= MAX_ID, CHARTER.ownerOf(id) == msg.sender, and that the id has\n  no claimant yet.\n- Record msg.sender as the id's claimant before paying, then pay INSTANT for each id in one\n  transfer.\n- Repeating an id in the same array must fail.\n\nclaimRest(uint256[] ids), the 90%:\n- Require CHARTER.balanceOf(msg.sender) == 0, meaning the wallet holds no Founding Charter at all.\n- For each id: require msg.sender is the id's recorded claimant and its REST has not been paid.\n- Mark each id paid before paying, then pay REST for each id in one transfer.\n- This still works if the Charter was later revoked or burned.\n\nEach id pays out once in total: INSTANT to whoever claims it while holding it, then REST to that\nsame claimant once they hold no Charter.\n\nShares of IDs that never exist or are never claimed stay locked in the contract forever.\n\nViews: claimant(id), instantClaimed(id), restClaimed(id), and claimable(id, account) returning the\ninstant and rest amounts that account could claim now.\nEvents: Claimed(id, account, amount, bool rest).\n\nToken name: Bankers In Control\nToken symbol: BIC","parentJobId":null,"planHash":"f69b9f355670ea50b12a01011f7e5a76db287aa6a7e7c07b5aa77b5152145153","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"2d95a791-c71a-48e0-b7c4-68a6fda3273c","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-1497-bankers-in-control"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51452","feedbackHash":"28d0ee7b0e7bd886210643262d8392decb6c4736dad71fcb1aeaff4eb3586db4","nodeKey":"audit_economics","submissionHash":"f46cff4a916c3288d0d655f092e9b1305c12cbe7c56d6ea43d08f472b766a0d0","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51534","feedbackHash":"157d9800ff6da6ae6ac67d873db34dbb963a34a79b7edce8572185b97f853707","nodeKey":"audit_flow","submissionHash":"6e07bf14b5fff375e252a577a548e1365318a590c1bd9e8836102bce87cf7bba","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51430","feedbackHash":"fb3b195c7b6c44effc27b8887421237558ea96ddecb679279af064154e7a8f45","nodeKey":"audit_judge","submissionHash":"7a21cd9b0c8f1d5c0872806855bf94b1b541a8f13e319934061d922b185c2c4d","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52681","feedbackHash":"a341179ae1e502b0df9cfec54baeeed0ff83b727c366767375b5a1427b339b07","nodeKey":"audit_math","submissionHash":"e26d4002a29b8e2b1970284d3d359017d55de123b6d90bc1ae1898cecf30da64","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52287","feedbackHash":"6d9c6907ed354fa466fd9c240ac0043ead50b1919125de6d0c57109f8383d13d","nodeKey":"audit_permissions","submissionHash":"2c8f29155f08c0eb672aa0ee576566040518e15b92c4440a9ec653251847e695","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52221","feedbackHash":"be6addc2d9aee3ed05c56b14fbf75f7a840213a5c013508dace5b523b9e44c79","nodeKey":"build_contract_project","submissionHash":"f58763ba0d32ad5744bbe0060d1e653496f9f4a72c4be15c52cbc870d3c3821e","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52022","feedbackHash":"d0867ea8a6b1fd0a2befcc074b4481ebc193f4c2b45f62ff38bb7b6506860274","nodeKey":"manifest","submissionHash":"d370acf32b8ea3363b93a9d57d667b0f0d9e205863525ff1dc6ec05881f34c40","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"50984","feedbackHash":"f2689d74ae6af03dd91a095ed08a1a6c43d705be9a3d269632ecfa3d6c874a04","nodeKey":"write_foundry_tests","submissionHash":"a0c2b0538499e2fc5873bba5014d3eff52614a7d01bbe9b6ad38a640cc2d7901","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"673c45800e09afe88b7e930e4574092cacd4fc5dda62a8b1375b6e1b25327dde","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"99c6d0bcc495ad61","findings":[{"citation":"resolved","description":"Area: Trust Gap (access x economics). CharterClaim's permission model is correct, but every payout is an unreserved transfer from whatever balance the contract happens to hold. The contract has no owner, no funding hook and no way to be topped up except a plain ERC-20 transfer, and the brief's 10/10/80 split conflicts with the generic launch policy quoted in the assignment (which routes the requester's share to the pool and the requester's wallet, and says no application contract receives supply at launch). README.md lines 28-36 already flag this conflict. If the factory funds CharterClaim with less than ALLOCATION, nothing in the contract detects it: entitled claimants are paid in call order until the balance runs out and the remainder revert with ERC20InsufficientBalance, with no way for anyone to correct it other than sending more BIC. This is a launch-integration dependency, not a code bug; it is reported so the judge and the manifest step confirm the funding recipient and amount. Live-chain note: 62 of IDs 1-1032 are already burned on the real Charter (0x69101f92...) and burned IDs cannot be reminted (mint(address) auto-increments nextId, currently 1033), so about 62 * SHARE = 48,062,015 BIC of the 800,000,000 is locked forever by design.","line":60,"path":"src/CharterClaim.sol","reproduction":"Scratch test test/scratch/Review.t.sol::test_unfundedOrShortFundedClaimsRevertForLateClaimants (passes, i.e. demonstrates the behaviour). State: CharterClaim deployed with the BIC token, Charter ids 2 (alice) and 3 (bob) exist. (1) With 0 BIC in the contract, alice calls claim([2]) -> reverts ERC20InsufficientBalance(claims, 0, 77519379844961240310077). Expected under a correct launch: pays INSTANT. (2) Fund the contract with 2*SHARE-1 minor units. alice.claim([2]) and bob.claim([3]) succeed; alice moves id 2 away and claimRest([2]) succeeds; bob moves ids away and calls claimRest([3]) -> reverts ERC20InsufficientBalance(claims, 697674418604651162790697, 697674418604651162790698). Bob is entitled but unpaid; order of calls decided who got paid.","severity":"info","snippet":"        token.safeTransfer(msg.sender, INSTANT * length);","title":"Payouts have no per-ID reservation: if the launch does not deliver exactly 800,000,000 BIC to CharterClaim, claims revert or pay first-come-first-served"},{"citation":"resolved","description":"Area: Access Control / Asymmetry (gate on a value a third party can write). The REST gate reads the claimant's collection-wide balanceOf, which any current Charter holder can increase by calling transferFrom/safeTransferFrom on the Charter. I verified on Robinhood Chain (chain id 4663) that the Founding Charter at 0x69101f92427376FA80476A997d024cf9dC3EaE39 is a standard, freely transferable OpenZeppelin ERC-721 (transferFrom, safeTransferFrom, approve, setApprovalForAll present; no ERC-5192 lock; supportsInterface(0x80ac58cd)=true, (0xb45a3c0e)=false), with ~901 distinct holders and ids 2-1033 existing. So the gate is satisfiable by moving a Charter to a second wallet (REST does not require giving up economic control), and griefable by gifting a Charter to a claimant. The grief is self-limiting: the victim keeps the NFT and can transfer it back out in one transaction (as the scratch test shows), and the griefer permanently gives up a Charter, so there is no loss of funds. This matches the brief's literal requirement ('holds no Founding Charter at all') and README.md line 73 documents it; it is recorded as a trust assumption, not a defect, with an asymmetry note: claimable(account) also reports rest = 0 while the gifted Charter is held, which is consistent with claimRest.","line":68,"path":"src/CharterClaim.sol","reproduction":"Scratch test test/scratch/Review.t.sol::test_thirdPartyTransferBlocksRestUntilMovedOut (passes, i.e. demonstrates the behaviour). State: contract funded with 800,000,000 BIC; alice holds id 2, bob holds id 1033 (above MAX_ID, exists live). alice.claim([2]) pays INSTANT; alice transfers id 2 to bob; claimable(alice) = (0, REST). bob transfers id 1033 to alice. alice.claimRest([2]) -> reverts StillHoldsCharter(); claimable(alice) = (0, 0). alice transfers 1033 back to bob; alice.claimRest([2]) succeeds and alice's BIC balance = SHARE = 775193798449612403100775.","severity":"info","snippet":"        if (IFoundingCharter(CHARTER).balanceOf(msg.sender) != 0) revert StillHoldsCharter();","title":"claimRest can be blocked by any third party transferring a Charter (including ids above MAX_ID, e.g. live id 1033) to the claimant"}],"hash":"2c8f29155f08c0eb672aa0ee576566040518e15b92c4440a9ec653251847e695","nodeId":"f5c477b0-e126-421b-bd15-a6225225b8ff","outcome":"completed","summary":"The review is complete. The findings file is valid JSON with two findings and nine coverage rows, and the quoted snippets match the source lines.\n\n**Result: no defects found in the Access Control, Trust Gap or Asymmetry areas of `CharterClaim`.** Both state-changing entry points implement the brief exactly, and I traced every guard, state write and failure path. The existing 53-test suite passes, and I added three scratch tests under `test/scratch/` that confirm my traces.\n\nWhat I checked and how it held:\n\n- **Permission model.** `claim` gates on `ownerOf(id) == msg.sender` plus an empty claimant slot. `claimRest` gates on collection-wide `balanceOf == 0` plus the recorded claimant and an unpaid flag. Each storage variable has exactly one writer, so there is no weaker parallel guard to exploit. Duplicates in one array fail on the second occurrence in both functions.\n- **Reentrancy lead from aderyn.** The `ownerOf` call before the state write is a STATICCALL, and the only state-changing external call is the final `safeTransfer`, after all effects, under `nonReentrant`. Not reachable.\n- **View-versus-execute symmetry.** `claimable` mirrors both execution paths, including the zero-address sentinel and skipped nonexistent ids. It costs about 5.3M gas for a holder, within normal `eth_call` caps.\n- **Live Founding Charter on Robinhood Chain.** I probed the real contract. It is a standard, transferable OpenZeppelin ERC-721 with owner-only `mint(address)` and `burn`, `nextId()` is 1033, ids 2 through 1033 exist, and 62 ids in range are already burned. Its revert behaviour for nonexistent ids and the zero address matches what the contract assumes.\n\nTwo items are recorded as info, not defects, because they follow from the requested design and the README already documents them:\n\n1. Payouts have no per-id reservation. If the launch delivers less than 800,000,000 BIC, entitled claimants are paid first-come-first-served and the rest revert. The brief's split also conflicts with the generic launch policy text, so the manifest step must confirm the funding recipient.\n2. Any Charter holder can block a claimant's `claimRest` by gifting them a Charter, including live id 1033. The victim simply moves it out again, and the griefer loses the NFT, so there is no fund loss.\n\nSources used for the chain lookup: [QuickNode Robinhood Chain RPC guide](https://www.quicknode.com/builders-guide/best/top-10-robinhood-chain-rpc-providers), [Dwellir Robinhood docs](https://www.dwellir.com/docs/robinhood).","treeHash":null,"usage":{"cachedInputTokens":1161427,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":25625,"runtime":"claude","turns":34,"wallClockMs":393229}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"739094f0e08668ee","findings":[{"citation":"resolved","description":"The code implements the brief's rule exactly (REST requires the caller to hold no Founding Charter), so this is not a deviation from the specification; it is a note on what that rule means against the real dependency, which the project's tests only model with a mock whose holders can freely transfer and burn. Read from Robinhood Chain (chain 4663, rpc.mainnet.chain.robinhood.com) on 2026-10-13: CHARTER 0x69101f92427376FA80476A997d024cf9dC3EaE39 is an OpenZeppelin-v5-style ERC-721 named 'Founding Charter' with nextId() == 1033; transfersEnabled() returns false and transferFrom by the holder reverts TransfersDisabled; burn(uint256) reverts NotCentralBank for the holder, for owner() (0x5c19F925E1e0D54E34681A6CB70d55c90b12A68c) and for a stranger. A holder therefore has no transaction of their own that lowers their Charter balance to zero. Under the current Charter state the 720,000,000 BIC REST pool (1032 x REST) is released only to claimants the CentralBank revokes, or to everyone if the Charter owner later calls enableTransfers() (after which a holder can move the Charter to a second wallet and claim REST immediately; the id cannot be claimed again, so no double payment arises). The same parties can also block a revoked claimant's REST by minting them a new Charter (any id, including above 1032). CharterClaim has no admin, so none of this can be adjusted after launch. If this is the intended design (REST as compensation for revoked Charters) no change is needed; the README should state the live transfer/burn restrictions rather than only 'the collection must report a zero balance'.","line":68,"path":"src/CharterClaim.sol","reproduction":"State: live chain 4663, holder H = 0x6b2269F4792b0e4bAd65EeE80f0F82BDA8D54433 (ownerOf(1032) == H, balanceOf(H) == 1). After launch H calls claim([1032]) and receives INSTANT = 77519379844961240310077. H then calls claimRest([1032]): reverts StillHoldsCharter. H tries to exit: `cast call CHARTER \"transferFrom(address,address,uint256)\" H 0x...dEaD 1032 --from H` reverts TransfersDisabled; `cast call CHARTER \"burn(uint256)\" 1032 --from H` reverts NotCentralBank. Expected by a reader of the brief ('REST to that same claimant once they hold no Charter'): a path the claimant can take. Actual: REST = 697674418604651162790698 for id 1032 stays in CharterClaim until a third party (CentralBank burn or owner enableTransfers) changes the Charter's state.","severity":"info","snippet":"        if (IFoundingCharter(CHARTER).balanceOf(msg.sender) != 0) revert StillHoldsCharter();","title":"On the live Founding Charter a holder cannot reach balanceOf == 0 by their own action, so REST (90% of every share) is payable only after the Charter's CentralBank burns their Charter or the Charter o"}],"hash":"6e07bf14b5fff375e252a577a548e1365318a590c1bd9e8836102bce87cf7bba","nodeId":"e4f34945-10cd-4eec-9aca-a34f5b2710ca","outcome":"completed","summary":"The review is finished: I found no code defects in `CharterClaim` or `LaunchToken` in my area, and recorded one informational note about how the 90% behaves against the live Founding Charter. Findings and coverage are in `.imd-findings.json`; no repository files were changed.\n\n**The one note (info, `src/CharterClaim.sol:68`):** on the live Charter, a holder cannot get their own balance to zero, so they cannot trigger `claimRest` themselves.\n- Read from Robinhood Chain today: transfers are disabled (`transferFrom` reverts `TransfersDisabled`) and `burn` reverts `NotCentralBank` for the holder, the Charter owner and a stranger.\n- So the 720,000,000 BIC of REST is released only when the Charter's CentralBank burns a claimant's Charter, or to everyone if the Charter owner later enables transfers.\n- Those same parties could block a revoked claimant's REST by minting them a new Charter.\n- The code follows the brief's rule exactly, so this is not a deviation. If REST is meant as a payout for revoked Charters, nothing needs changing beyond saying so in the README.\n\n**What I checked and found sound:**\n- **`claim`:** the range, no-claimant and `ownerOf` checks all run before the claimant is recorded, and the claimant is recorded before the single transfer. A repeated id fails on its second pass, and a failed transfer rolls everything back.\n- **`claimRest`:** the zero-balance gate, the claimant match and the paid flag are all set before the single transfer. It never reads `ownerOf`, so burned ids still work.\n- **Reentrancy lead from static analysis:** dismissed. The only call before the state write is a read-only `ownerOf`, the token has no callbacks, and both functions carry a reentrancy guard.\n- **Accounting:** each id pays at most SHARE, and 1032 × SHARE is 200 minor units under the 800,000,000 allocation, so full funding never runs short.\n- **Live Charter interface:** it has code, behaves as a standard ERC-721 for `ownerOf` and `balanceOf`, and `nextId()` is already 1033, which the contract correctly never reads.\n- **`LaunchToken`:** unmodified OpenZeppelin ERC-20 with the right name, symbol and supply.\n\nThe existing suite passes (53 tests), but it uses a mock Charter whose holders can transfer and burn freely, which is why the note above doesn't show up there.\n\n**Not verified:** whether the launch actually sends the 80% to `CharterClaim`. There is no `launch.json` in the tree, and the generic launch reference says the requester's remainder goes to their wallet rather than to a project contract. If the contract is unfunded or underfunded, claims simply revert without corrupting state, but that funding step needs confirming at the manifest stage.","treeHash":null,"usage":{"cachedInputTokens":365779,"inputTokens":16,"model":"claude-opus-5-5","outputTokens":8551,"runtime":"claude","turns":9,"wallClockMs":116351}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"2a5d68f89de314cb","findings":[{"citation":"resolved","description":"The brief lists the views as claimant(id), instantClaimed(id), restClaimed(id) and claimable(id, account), the last 'returning the instant and rest amounts that account could claim now' for one id. The code exposes claimable(address) only, which scans all 1032 ids (each an external ownerOf STATICCALL when the account holds a Charter) and returns aggregate totals. The compiled ABI (forge inspect) has claimable(address) and no claimable(uint256,address). README line 79 says the builder 'interpreted' a 'truncated views line'; the brief as supplied to this review is not truncated. Impact: integrators and the launch frontend calling the specified selector revert, there is no per-id eligibility query, and the aggregate scan costs roughly 1032 external calls per eth_call. No funds are affected; claim and claimRest are unchanged. Fix: add claimable(uint256 id, address account) returning (instant, rest) for that id (instant = INSTANT when id in range, claimant[id]==0 and ownerOf(id)==account, else 0; rest = REST when claimant[id]==account, !restClaimed[id] and balanceOf(account)==0, else 0). The aggregate view may stay as an extra.","line":91,"path":"src/CharterClaim.sol","reproduction":"Scratch test test/scratch/Judge.t.sol::test_claimableIdAccountSelectorMissing (run with forge test --match-path test/scratch/Judge.t.sol; passes, i.e. demonstrates the gap). State: CharterClaim deployed with the BIC token. address(claims).staticcall(abi.encodeWithSignature(\"claimable(uint256,address)\", 2, alice)) returns ok == false (selector 0x... not in runtime, falls through to no fallback and reverts); abi.encodeWithSignature(\"claimable(address)\", alice) returns ok == true. Expected per brief: claimable(2, alice) returns (INSTANT or 0, REST or 0) for id 2. Actual: call reverts.","severity":"low","snippet":"    function claimable(address account) external view returns (uint256 instant, uint256 rest) {","title":"claimable has signature claimable(address), not the brief's claimable(id, account); the per-ID view the brief specifies is absent from the ABI"},{"citation":"resolved","description":"Merged from audit_flow (info), audit_economics #1 (medium) and the gifting notes of audit_permissions #2 and audit_economics #3; one root cause, the collection-wide balance gate the brief requires. The code implements the brief exactly ('Require CHARTER.balanceOf(msg.sender) == 0'; 'This still works if the Charter was later revoked or burned'), so this is a trust assumption on the Charter issuer, not a code deviation, and it is calibrated low rather than the specialist's medium. Verified on Robinhood Chain (chain id 4663, rpc.mainnet.chain.robinhood.com, 2026-10-13): CHARTER 0x69101f92427376FA80476A997d024cf9dC3EaE39 is 'Founding Charter', nextId()==1033, transfersEnabled()==false, owner()==0x5c19F925E1e0D54E34681A6CB70d55c90b12A68c, centralBank()==0x5977B25419f9c0dDcB2F89fE204955622f4F57ea; ownerOf(1) reverts ERC721NonexistentToken(1) (burned), ownerOf(2)==0x6d5d4fb55c61019c5eb9236c3da58c774B8232D1, ownerOf(1033) exists. Consequences: (a) after claiming INSTANT no holder can reach balanceOf==0 by their own transaction, so each id's REST is paid only after the central bank burns that holder's Charter(s) or the owner calls enableTransfers(); if neither happens, 1032*REST stays locked in a contract with no admin or recovery, by design. (b) The central bank can today, and any holder once transfers are enabled, block a revoked claimant's REST by minting/transferring a Charter to them; ids above MAX_ID count too (1033 exists). While transfers are disabled a claimant who is minted a new Charter cannot dispose of it, so the block lasts as long as the issuer wants. audit_permissions #2's statement that the live Charter is freely transferable is wrong (a holder's transferFrom reverts TransfersDisabled) and was not kept. README line 73 ('provided the collection reports a zero balance') and line 106 ('Receiving an unwanted Charter can temporarily block REST') do not state that on the live Charter the holder has no path of their own and that only the issuer's burn (or enabling transfers) releases REST. Fix within the agreed design: state these live-Charter facts in the README so the requester confirms with the issuer that revocation is the intended release path; changing the gate (e.g. to ownerOf(id) reverting, i.e. the id burned) is a brief change and is noted as a tradeoff only.","line":68,"path":"src/CharterClaim.sol","reproduction":"Live chain (cast, chain 4663, 2026-10-13), H = 0x6d5d4fb55c61019c5eb9236c3da58c774B8232D1 = ownerOf(2), balanceOf(H)==1: `cast call CHARTER 'transferFrom(address,address,uint256)' H 0x...dEaD 2 --from H` -> revert TransfersDisabled; `cast call CHARTER 'burn(uint256)' 2 --from H` -> revert NotCentralBank; `cast call CHARTER 'burn(uint256)' 2 --from 0x5977B25419f9c0dDcB2F89fE204955622f4F57ea` -> succeeds (0x); `cast call CHARTER 'mint(address)' H --from 0x5977B2...57ea` -> returns 1034. So after launch H.claim([2]) pays INSTANT and H.claimRest([2]) reverts StillHoldsCharter at line 68 until the central bank acts. Local model of the blocking path: test/scratch/Judge.t.sol::test_giftedCharterBlocksRest (mock ERC-721 etched at CHARTER, contract funded with 800,000,000 BIC): alice holds id 2, bob holds id 1033; alice.claim([2]) pays INSTANT; alice moves id 2 away; claimable(alice) == (0, REST); bob transfers 1033 to alice; alice.claimRest([2]) -> revert StillHoldsCharter(); claimable(alice) == (0, 0); after alice moves 1033 out, claimRest([2]) succeeds and alice's BIC balance == SHARE == 775193798449612403100775. Expected by a reader of the brief: the claimant can eventually collect REST. Actual: only the Charter issuer's actions decide whether and when.","severity":"low","snippet":"        if (IFoundingCharter(CHARTER).balanceOf(msg.sender) != 0) revert StillHoldsCharter();","title":"REST (90%, about 720M BIC) is gated on CHARTER.balanceOf(msg.sender)==0, a value only the Founding Charter's issuer controls on the live contract: transfers are disabled and only its central bank can "},{"citation":"resolved","description":"Merged from audit_economics #2 (low), recalibrated to info: the brief states both behaviours ('Each id pays out once in total: INSTANT to whoever claims it while holding it, then REST to that same claimant once they hold no Charter'; the gate is the wallet's balanceOf). The claimant is bound permanently at line 56 and never re-read from ownerOf, so (a) if the Charter owner calls enableTransfers() (owner-only function present in the live runtime; today transfersEnabled()==false), a claimant can move the Charter to a second wallet, call claimRest, and move it back, keeping membership plus 100% of SHARE; (b) a secondary buyer of an already-claimed id can never claim (AlreadyClaimed) and claimRest requires claimant[id]==msg.sender. No funds are lost or double-paid; each id still pays exactly SHARE once. README line 73 notes reacquisition but not that a transferred Charter carries no claim value after its first claim; adding that sentence is the only change suggested. Making REST require giving up the Charter would be a change to the brief.","line":56,"path":"src/CharterClaim.sol","reproduction":"test/scratch/Judge.t.sol::test_parkingCollectsRestAndBuyerGetsNothing (mock ERC-721 at CHARTER with transfers allowed, contract funded with 800,000,000 BIC): alice holds id 7; alice.claim([7]) pays INSTANT; alice transfers 7 to alice2; alice.claimRest([7]) succeeds; token.balanceOf(alice) == SHARE; alice2 transfers 7 back, charter.balanceOf(alice)==1. Then alice sells 7 to bob: bob.claim([7]) reverts AlreadyClaimed(7); claimable(bob) == (0, 0). Expected under the brief's literal rules: exactly this. Noted so the requester confirms it is the intended economics.","severity":"info","snippet":"            claimant[id] = msg.sender;","title":"Design note: REST requires only a zero wallet balance at call time, so once transfers are enabled a holder collects 100% of SHARE by parking the Charter in a second wallet; a later buyer of a claimed "},{"citation":"resolved","description":"Merged from audit_permissions #1 (info), kept at info. The brief gives no funding check and the constructor may not call the token (the launch floor runs constructors on an empty chain), so the code is correct against the brief; this is a launch-integration dependency. The generic launch text routes the requester's 90% to the pool and the requester's wallet and says no application contract receives supply at launch, while the brief and README lines 28-36 require 80% sent to the contract named CharterClaim. If the factory does not perform that transfer, every claim reverts with ERC20InsufficientBalance; if it sends less than ALLOCATION, entitled claimants are paid in call order until the balance runs out. Also noted for the requester: 62 of ids 1..1032 are already burned on the live Charter (e.g. ownerOf(1) reverts) and OZ-style mint(address) auto-increments nextId (now 1033), so those ids can never be reminted and about 62*SHARE (about 48M BIC) is locked by design. Needed evidence: the manifest/deployment step confirms the recipient and amount of the 80% transfer to the deployed CharterClaim address.","line":60,"path":"src/CharterClaim.sol","reproduction":"test/scratch/Judge.t.sol::test_shortFundingIsFirstComeFirstServed: Charter ids 2 (alice) and 3 (bob) exist. With 0 BIC in the contract alice.claim([2]) reverts (ERC20InsufficientBalance(claims, 0, 77519379844961240310077)). Fund the contract with 2*SHARE-1 minor units: alice.claim([2]) and bob.claim([3]) succeed; alice moves id 2 away and claimRest([2]) succeeds; bob moves his Charters away and claimRest([3]) reverts ERC20InsufficientBalance(claims, 697674418604651162790697, 697674418604651162790698); contract balance left == REST-1. Expected under a correct launch (exactly ALLOCATION delivered): both paid. Actual under short funding: call order decides who is paid.","severity":"info","snippet":"        token.safeTransfer(msg.sender, INSTANT * length);","title":"Payouts are unreserved transfers from the contract's balance: if the launch delivers less than 800,000,000 BIC to CharterClaim, claims are paid first-come-first-served and the rest revert, with no way"}],"hash":"7a21cd9b0c8f1d5c0872806855bf94b1b541a8f13e319934061d922b185c2c4d","nodeId":"a856dbbc-fdca-400a-9cb6-a454bdfe6e5b","outcome":"completed","summary":"The review is complete. Findings and coverage are written to `.imd-findings.json`, which is valid JSON with every snippet matched against the source. No repository files were changed; the only additions are the findings file and my scratch test under `test/scratch/`, which is not kept.\n\n**What I checked**\n\n- Ran the full project suite: 63 tests pass, including the stateful invariant.\n- Read the live Founding Charter on Robinhood Chain. Transfers are disabled, burn and mint are central-bank-only, id 1 is burned, id 1033 exists. This settles a contradiction between specialists: the permissions auditor's claim that the Charter is freely transferable is wrong.\n- Wrote and ran a scratch test reproducing each kept behaviour against the code.\n\n**Findings kept (4, none above low)**\n\n1. **Low, new.** The brief specifies `claimable(id, account)` returning per-id amounts. The code exposes only an aggregate `claimable(address)`, and the brief's selector is absent from the ABI. View-only, no funds affected.\n2. **Low, merged** from four specialist entries (flow, economics, and both gifting notes). REST is gated on the wallet's collection-wide Charter balance, which on the live contract only the issuer can change. Holders have no path of their own to REST, and the issuer can block a claimant by minting them a Charter. The code implements the brief exactly, so this is a trust assumption plus a README gap, recalibrated down from the economics auditor's medium.\n3. **Info, merged.** Once transfers are enabled, parking the Charter in a second wallet collects REST, and a later buyer of a claimed id gets nothing. Both follow the brief's literal rules.\n4. **Info, kept.** Payouts are unreserved, so short funding by the launch pays first-come-first-served. Launch-integration dependency, not a code defect.\n\n**Dropped:** the aderyn reentrancy lead (a STATICCALL view plus a reentrancy guard), the loop and literal lints (by design), and the testing step's empty report had nothing to settle.\n\n**Coverage:** all five entry points answered, plus rows for the view, deployability and manifest, and the pay-once invariant. Both LaunchToken paths and `claim` hold. `claimRest` and `claimable` point at findings 2 and 1.","treeHash":null,"usage":{"cachedInputTokens":742539,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":18795,"runtime":"claude","turns":21,"wallClockMs":576015}},{"artifacts":[],"attempt":1,"bundleHash":"2702f8ea907d666164acb7a24cb6b4b458c2be6888ed34b4cc7ec9fcb0af3f06","device":"726aa3006eb6a1e1","findings":[],"hash":"a0c2b0538499e2fc5873bba5014d3eff52614a7d01bbe9b6ad38a640cc2d7901","nodeId":"25191956-328f-4068-8c68-234424dd93b2","outcome":"completed","summary":"Added 10 tests and strengthened invariants covering batch rollback, previews, caller identity, and rest settlement.\n\n`forge build` passed. All 63 tests passed, including 16,384 randomized invariant calls.\n\nChanges are confined to `test/`. No confirmed defects found. Live Robinhood Chain integration remains unverified.","treeHash":"bae842d30a6c596c363dac832d92944b86982965","usage":{"cachedInputTokens":3221120,"inputTokens":138104,"model":"gpt-6-astra","outputTokens":28554,"runtime":"codex","turns":10,"wallClockMs":1344459}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"2bb6789ad7b554e8","findings":[],"hash":"cd7be00a2379c365982f0e779d3256d1f2135ca69ef670297c2db716af8612f8","nodeId":"5672bff4-4b5b-46aa-ad68-bbdf4745979e","outcome":"failed","summary":"fetch failed","treeHash":null,"usage":{"cachedInputTokens":0,"inputTokens":0,"model":null,"outputTokens":0,"runtime":"codex","turns":0,"wallClockMs":112}},{"artifacts":[],"attempt":2,"bundleHash":"1f81e2cbef5a5a8f1d5efa5381e472558316922433da7883ee6b291ba490d931","device":"30739b66307c87c9","findings":[],"hash":"d370acf32b8ea3363b93a9d57d667b0f0d9e205863525ff1dc6ec05881f34c40","nodeId":"5672bff4-4b5b-46aa-ad68-bbdf4745979e","outcome":"completed","summary":"Created [launch.json](/root/.identitymd/work/2d95a791-c71a-48e0-b7c4-68a6fda3273c/5672bff4-4b5b-46aa-ad68-bbdf4745979e/launch.json) matching the accepted contracts, `$token` constructor argument, and required pool settings.\n\n- Manifest fields and constructor ABIs checked.\n- `forge build` passed.\n- `forge test`: 53 passed, 0 failed.\n- Only `launch.json` changed.","treeHash":"87050753845c68563d43f3ead0642b294e2e8d1d","usage":{"cachedInputTokens":244992,"inputTokens":30786,"model":"gpt-6-astra","outputTokens":2491,"runtime":"codex","turns":3,"wallClockMs":116750}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"1a7606be97f168d4","findings":[],"hash":"e26d4002a29b8e2b1970284d3d359017d55de123b6d90bc1ae1898cecf30da64","nodeId":"abffc916-e3ec-4bda-abec-9e6607bb718f","outcome":"completed","summary":"The review is complete. The findings file is written and the working tree is clean apart from it.\n\n**Result: no defects found in the math, boundary or numerical-gap area.** The findings file holds an empty list and eight coverage rows, one for each of the five listed entry points plus three invariants I checked.\n\nWhat I verified:\n\n- **Constants are exact.** SHARE, INSTANT and REST match the README literals. INSTANT + REST equals SHARE, and 1032 shares total 800,000,000 BIC minus 200 wei, so the contract can never be short regardless of claim order.\n- **No overflow or truncation paths.** The per-call amounts multiply a constant by a batch length that duplicates and range checks bound to at most 1032. There is no division at runtime.\n- **ID boundaries.** 0, 1033 and the maximum uint256 revert with InvalidId. 1 and 1032 pay. Duplicates in one batch revert the whole batch in both functions.\n- **View and write paths agree.** The claimable view uses the same predicates as the two claim functions, so it never reports an amount the write path would refuse.\n- **Live Charter boundary.** I queried the real Founding Charter on Robinhood Chain. It is an OpenZeppelin-style ERC-721 with burn, mint and transferFrom. It reverts ownerOf for nonexistent IDs and its balanceOf matches the ownerOf count, which is exactly what claimRest relies on after a revocation.\n- **Gas at the edge.** A 1032-ID claim batch costs about 29M gas and the claimable view between 2.4M and 5.3M, both documented as off-chain or split-batch concerns in the README.\n\nOne on-chain fact worth knowing, though it is by design and not a defect: 62 of the 1032 IDs are already burned on the live Charter, so about 48 million BIC of the 800 million allocation can never be claimed. The brief explicitly accepts this.\n\nNot covered: the Charter's own source was unreachable because Blockscout sits behind a bot wall, so the burn and revoke authorization rules were inferred from its selectors and live state rather than read.","treeHash":null,"usage":{"cachedInputTokens":1369361,"inputTokens":610,"model":"claude-fable-5-1","outputTokens":21739,"runtime":"claude","turns":20,"wallClockMs":345627}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"d63ea36a2b809080","findings":[{"citation":"resolved","description":"claimRest pays only when CHARTER.balanceOf(msg.sender) == 0, as the brief requires. On Robinhood Chain (chain id 4663, read at block 87197140 via https://rpc.mainnet.chain.robinhood.com) the Founding Charter at 0x69101f92427376FA80476A997d024cf9dC3EaE39 is an OpenZeppelin ERC-721 with Ownable2Step whose state makes that condition impossible for a holder to reach on their own: transfersEnabled() returns false and a simulated transferFrom by the holder of id 2 reverts TransfersDisabled; burn(uint256) and mint(address) revert NotCentralBank for the holder, for a random address and for the owner, and centralBank() is 0x5977B25419f9c0dDcB2F89fE204955622f4F57ea; enableTransfers() succeeds in simulation only from owner() 0x5c19F925E1e0D54E34681A6CB70d55c90b12A68c and no disableTransfers selector exists in the runtime. Consequently, after the launch, every holder can collect INSTANT (10%) but the remaining 1032 * REST = 719,999,999,999,999,999,999,999,936 minor units (~720M BIC) stay in CharterClaim until either the Charter owner enables transfers or the central bank burns each claimant's Charter. CharterClaim has no owner and no recovery path, so if those third parties never act the 90% is locked forever. This is not a bug in the code against the brief; it is the economic trust assumption the brief's wallet-balance rule creates, and it is not stated in the README (which assumes revocation or transfer is available to the holder). The requester should confirm with the Charter issuer that revocation (burn) or transfer enablement is planned before relying on this design, or change the brief's condition (for example to a voluntary burn via the issuer, or to a time-based unlock) if it is not.","line":68,"path":"src/CharterClaim.sol","reproduction":"State: live Founding Charter as read on 2026-10-13 at block 87197140. Steps: (1) holder H of id 2 (0x6d5d4fb55c61019c5eb9236c3da58c774B8232D1) calls claim([2]) -> succeeds, receives INSTANT. (2) H tries to reach balanceOf(H)==0: `cast call CHARTER 'transferFrom(address,address,uint256)' H 0xdead 2 --from H` -> revert TransfersDisabled; `cast call CHARTER 'burn(uint256)' 2 --from H` -> revert NotCentralBank. (3) H calls claimRest([2]) -> revert StillHoldsCharter at line 68 (balanceOf(H)==1). Expected per the brief's intent: the claimant can eventually collect REST. Actual: REST is only reachable after a privileged action by the Charter owner (enableTransfers) or the central bank (burn), neither of which CharterClaim or the holder controls. Same outcome for all 1031 existing ids in range.","severity":"medium","snippet":"        if (IFoundingCharter(CHARTER).balanceOf(msg.sender) != 0) revert StillHoldsCharter();","title":"REST (90%, ~720M BIC) is unreachable for every claimant until the Founding Charter's privileged keys act: transfers are disabled on the live contract and only its central bank can burn"},{"citation":"resolved","description":"The claimant is bound permanently to the first wallet that claims an id (line 56) and REST is gated only by the caller's current Charter balance (line 68). Nothing ties REST to actually relinquishing the Charter. If the Charter owner calls enableTransfers() (an owner-only function present in the live runtime), a holder can claim INSTANT, move the Charter to a second wallet they control, claim REST from the first wallet, and move the Charter back, keeping full membership while having collected 100% of SHARE. The same mechanism means a secondary-market buyer of a Charter whose id was already claimed gets no claim at all: claim([id]) reverts AlreadyClaimed and claimRest requires claimant[id]==msg.sender, so the seller keeps both halves. This follows the brief's wording ('the wallet holds no Founding Charter'), so it is a design note rather than a code defect; it matters because the 10%/90% split reads as an incentive to give up the Charter, which the contract cannot enforce. The README already states the reacquisition limitation but not the buyer-gets-nothing consequence. If the requester wants REST to require giving up the Charter, the brief must change (e.g. require that ownerOf(id) reverts, i.e. the id was burned, instead of balanceOf==0).","line":56,"path":"src/CharterClaim.sol","reproduction":"Precondition: Charter transfers enabled (owner calls enableTransfers()). Local model (test/scratch/EconEdges.t.sol, mock ERC-721 etched at CHARTER): alice holds id 1. alice.claim([1]) -> alice receives INSTANT. alice transfers id 1 to alice2 (her own wallet). alice.claimRest([1]) -> succeeds, alice receives REST; token.balanceOf(alice) == SHARE. alice2 transfers id 1 back; charter.balanceOf(alice)==1 and alice holds the Charter plus 100% of its share. Buyer case: alice.claim([7]); alice transfers id 7 to bob (a sale); alice.claimRest([7]) succeeds; bob.claim([7]) reverts AlreadyClaimed(7); claimable(bob) == (0, 0). Expected under the brief's economic intent: REST paid for giving up the Charter / the Charter carries its unclaimed value. Actual: REST collected with no loss of membership; the NFT carries zero claim value after the first claim.","severity":"low","snippet":"            claimant[id] = msg.sender;","title":"Once Charter transfers are enabled, the 'holds no Charter' condition is satisfiable at zero cost by parking the NFT in a second wallet; a buyer of an already-claimed Charter receives nothing"},{"citation":"resolved","description":"claimRest checks the collection-wide balanceOf(msg.sender), including ids above MAX_ID that have no claim of their own (id 1033 is already minted on the live contract: ownerOf(1033) = 0x681D22c5E615C7a876026ceba3dC36c6dDE5b97d, while ownerOf(1034) reverts ERC721NonexistentToken). Once the Charter owner enables transfers, any holder can transfer a Charter into a claimant's wallet and that claimant's claimRest reverts StillHoldsCharter until they move it out again; claimable(victim) also reports rest = 0. Today, with transfers disabled, only the central bank can create this state (mint(address) to the claimant), which is a privileged action. Cost to a griefer is one Charter per attempt (the victim can forward it elsewhere), so the impact is a recoverable delay, not a loss, and the behaviour follows the brief's requirement. Reported so the requester knows the wallet-balance rule, not CharterClaim, decides this, and so the README's 'receiving an unwanted Charter can temporarily block REST' is understood to include ids outside 1..1032.","line":68,"path":"src/CharterClaim.sol","reproduction":"Precondition: transfers enabled. Local model (test/scratch/EconEdges.t.sol): alice claims id 1 (INSTANT paid), moves id 1 away so balanceOf(alice)==0. bob holds id 5000 (any id, in or out of range) and calls charter.transferFrom(bob, alice, 5000). alice.claimRest([1]) -> revert StillHoldsCharter; claims.claimable(alice) == (0, 0). After alice transfers id 5000 out, claimRest([1]) succeeds. Expected: a claimant who gave up their own Charters can collect REST. Actual: blocked by an unrelated Charter until they dispose of it.","severity":"low","snippet":"        if (IFoundingCharter(CHARTER).balanceOf(msg.sender) != 0) revert StillHoldsCharter();","title":"Any Charter holder can block a claimant's REST by sending them a Charter (including out-of-range id 1033) once transfers are enabled; the central bank can do so today by minting to them"}],"hash":"f46cff4a916c3288d0d655f092e9b1305c12cbe7c56d6ea43d08f472b766a0d0","nodeId":"5cfef244-97d5-4a7d-a25a-4ec7cea337c5","outcome":"completed","summary":"The review is complete and `.imd-findings.json` is written with three findings and nine coverage rows. The working tree contains nothing else.\n\n**Outcome.** CharterClaim's code holds against the brief and against the Economic Security, Invariant and Flow Gap checks. Every payment-affecting invariant I traced is intact: claimant and restClaimed are write-once, effects precede the single transfer, duplicates revert, each ID pays at most SHARE, the view agrees with what the two claim functions would pay, and full completion leaves exactly 200 minor units. The three findings are all economic consequences of the brief's wallet-balance rule, confirmed against the live Founding Charter rather than the mock.\n\n**What the live chain showed.** I read the Founding Charter on Robinhood Chain over the public RPC at block 87197140. It is an OpenZeppelin ERC-721 with Ownable2Step and no proxy. Transfers are disabled, a simulated holder transfer reverts with `TransfersDisabled`, and only the owner can call `enableTransfers`, with no disable path in the runtime. Mint and burn revert with `NotCentralBank` for the holder, the owner and a random caller. ID 1 is already burned and ID 1033 is already minted.\n\n**Findings reported:**\n- **Medium.** No claimant can reach a zero Charter balance today. REST, about 720M BIC, stays locked in an ownerless contract until the Charter owner enables transfers or the central bank burns each claimant's token. This is the design's trust assumption, not a code bug, and the README does not state it.\n- **Low.** Once transfers are enabled, the \"holds no Charter\" condition costs nothing to satisfy by parking the NFT in a second wallet, and a buyer of an already-claimed Charter receives nothing.\n- **Low.** Any Charter holder can block a claimant's REST by sending them a Charter, including the out-of-range ID 1033. The central bank can cause the same state today by minting to the claimant.\n\n**Coverage.** All five verifier entry points have rows. The two claim functions, three LaunchToken functions, and four invariants were traced in full. I measured a 1032-ID claim batch at 29.0M gas and the view at 5.3M gas locally. I could not fetch the Charter's verified source because the explorer sits behind a Cloudflare challenge, so the burn and transfer semantics rest on eth_call simulations and selector scans rather than source.\n\nSources: [QuickNode Robinhood Chain RPC guide](https://www.quicknode.com/builders-guide/best/top-10-robinhood-chain-rpc-providers), [Dwellir Robinhood Chain docs](https://www.dwellir.com/docs/robinhood).","treeHash":null,"usage":{"cachedInputTokens":970887,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":29058,"runtime":"claude","turns":34,"wallClockMs":436902}},{"artifacts":[],"attempt":1,"bundleHash":"e971210766009064b49992d8570be2ae369fd23fd5d579d9b1ab32b048139177","device":"2ca00c67242c649b","findings":[],"hash":"f58763ba0d32ad5744bbe0060d1e653496f9f4a72c4be15c52cbc870d3c3821e","nodeId":"18604857-0012-4a5d-96ad-0ab6abdb2d4e","outcome":"completed","summary":"Implemented CharterClaim and the fixed-supply BIC LaunchToken, with vendored dependencies, deployment documentation, and security review notes.\n\nValidation passed:\n\n- `forge build` using Solidity 0.8.26\n- `forge test`: 53 passed, including reentrancy and accounting invariants\n- `forge fmt --check`\n\nThe README documents the required 10% pool / 10% swarm / 80% CharterClaim funding in the launch transaction. No deployment was broadcast.","treeHash":"e25c4abc955d60115d979eb4a318b6e5a81f6828","usage":{"cachedInputTokens":1096448,"inputTokens":91417,"model":"gpt-6-astra","outputTokens":28733,"runtime":"codex","turns":6,"wallClockMs":849866}}],"verification":[{"checks":[{"durationMs":2301,"exitCode":0,"name":"build","output":"Compiling 40 files with Solc 0.8.26\nSolc 0.8.26 finished in 2.19s\nCompiler run successful!\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/CharterClaim.sol:54:17\n   │\n54 │             if (IFoundingCharter(CHARTER).ownerOf(id) != msg.sender) revert NotCharterOwner(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/CharterClaim.sol:112:37\n    │\n112 │         if (id == 0 || 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/CharterClaim.sol:53:45\n   │\n53 │             if (claimant[id] != address(0)) revert AlreadyClaimed(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/CharterClaim.sol:54:70\n   │\n54 │             if (IFoundingCharter(CHARTER).ownerOf(id) != msg.sender) revert NotCharterOwner(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/CharterClaim.sol:73:45\n   │\n73 │             if (claimant[id] != msg.sender) revert NotClaimant(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/CharterClaim.sol:74:34\n   │\n74 │             if (restClaimed[id]) revert RestAlreadyClaimed(id);\n   │                                  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n   ╭▸ src/CharterClaim.sol:68:13\n   │\n68 │         if (IFoundingCharter(CHARTER).balanceOf(msg.sender) != 0) revert StillHoldsCharter();\n   │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/CharterClaim.sol:99:25\n   │\n99 │                     try IFoundingCharter(CHARTER).ownerOf(id) returns (address holder) {\n   │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n   ╭▸ src/CharterClaim.sol:93:29\n   │\n93 │         bool holdsCharter = IFoundingCharter(CHARTER).balanceOf(account) != 0;\n   │                             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\n","passed":true},{"durationMs":79858,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 8 tests for test/CharterClaimAdversarial.t.sol:CharterClaimAdversarialTest\n[PASS] test_falseReturnRollsBackBothPhasesAndAllowsRetry() (gas: 897366)\n[PASS] test_initialCallbackSeesWholeBatchRecordedAndCannotReenterEitherPhase() (gas: 712086)\n[PASS] test_malformedReturnRollsBackEvenIfTokenMovedBalances() (gas: 939120)\n[PASS] test_missingCharterCodeFailsWithoutPaying() (gas: 96672)\n[PASS] test_restCallbackSeesWholeBatchPaidAndCannotReenterEitherPhase() (gas: 724263)\n[PASS] test_revertingTransferRollsBackBothPhasesAndAllowsRetry() (gas: 895792)\n[PASS] test_tokenWithoutCodeFailsOnFirstPayoutAndLeavesIdUnclaimed() (gas: 150672)\n[PASS] test_tokenWithoutReturnValueIsSupported() (gas: 351130)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 645.82ms (2.62ms CPU time)\n\nRan 5 tests for test/LaunchToken.t.sol:LaunchTokenTest\n[PASS] testFuzz_transferHasNoFeesOrSupplyChange(uint256) (runs: 256, μ: 91939, ~: 92369)\nLogs:\n  Bound result 1519\n\n[PASS] test_approvalAndTransferFrom() (gas: 250552)\n[PASS] test_insufficientBalanceAndZeroRecipientRevert() (gas: 69200)\n[PASS] test_metadataAndSupplyBelongToDeployer() (gas: 53594)\n[PASS] test_noMintOwnerPauseUpgradeOrBurnEntryPoints() (gas: 395336)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 645.89ms (646.28ms CPU time)\n\nRan 34 tests for test/CharterClaim.t.sol:CharterClaimTest\n[PASS] testFuzz_fullLifecyclePaysOneShare(uint256,bool) (runs: 256, μ: 343454, ~: 356628)\nLogs:\n  Bound result 12\n\n[PASS] testFuzz_idsAboveCutoffRejected(uint256) (runs: 256, μ: 125262, ~: 125107)\nLogs:\n  Bound result 10243\n\n[PASS] test_allIdsPayExactlyOneShareAndLeaveOnlyRoundingDust() (gas: 158598954)\n[PASS] test_approvedOperatorCannotClaim() (gas: 178141)\n[PASS] test_claimSinglePaysOwnerAndEmitsEvent() (gas: 245557)\n[PASS] test_claimableSkipsMissingBurnedAndOutOfRangeIds() (gas: 13660801)\n[PASS] test_claimableTracksPartialBatchesAndTransfers() (gas: 16314355)\n[PASS] test_constantsAndInitialState() (gas: 92885)\n[PASS] test_constructorRejectsZeroToken() (gas: 4045)\n[PASS] test_duplicateInitialIdRollsBackWholeBatch() (gas: 273448)\n[PASS] test_duplicateRestIdRollsBackWholeBatch() (gas: 370685)\n[PASS] test_emptyBatchesRevert() (gas: 63253)\n[PASS] test_failedCharterBalanceReadFailsClosed() (gas: 300340)\n[PASS] test_invalidLaterIdRollsBackEarlierIds() (gas: 162130)\n[PASS] test_laterMintWithinCutoffCanClaimButNoOtherSharesRedistribute() (gas: 5887685)\n[PASS] test_mixedClaimantRestBatchRollsBackEarlierIds() (gas: 489992)\n[PASS] test_mixedOwnershipBatchRollsBackEarlierIds() (gas: 247061)\n[PASS] test_nonexistentCharterCannotClaim() (gas: 54207)\n[PASS] test_onlyCurrentOwnerCanClaimBeforeInitialPayment() (gas: 278954)\n[PASS] test_reacquiringCharterBlocksOnlyUnpaidRest() (gas: 8602949)\n[PASS] test_repeatedInitialClaimAndNewOwnerBothFail() (gas: 315593)\n[PASS] test_restBlockedByLaterMintAboveCutoffUntilItLeaves() (gas: 5859056)\n[PASS] test_restBlockedByUnclaimedCharter() (gas: 5720723)\n[PASS] test_restBlockedWhileHoldingClaimedCharter() (gas: 215758)\n[PASS] test_restCannotBeClaimedBeforeInstantOrByAnotherAccount() (gas: 345744)\n[PASS] test_restCannotBePaidTwiceOrResetByRemint() (gas: 441920)\n[PASS] test_restNeverQueriesBurnedOrRevokedIds() (gas: 5320415)\n[PASS] test_restPaidToOriginalClaimantAfterTransfer() (gas: 379188)\n[PASS] test_restRejectsOutOfRangeIds() (gas: 83702)\n[PASS] test_restUsesSingleTransferForBatch() (gas: 492530)\n[PASS] test_underfundedInstantRevertsAndCanBeRetriedAfterFunding() (gas: 475087)\n[PASS] test_underfundedRestRevertsAndPreservesClaimant() (gas: 479596)\n[PASS] test_unsortedBatchIncludesBothBoundariesAndUsesOneTransfer() (gas: 303747)\n[PASS] test_zeroIdRejectedEvenIfNftExists() (gas: 110544)\nSuite result: ok. 34 passed; 0 failed; 0 skipped; finished in 645.93ms (1.33s CPU time)\n\nRan 5 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] test_allocationHandoffCanFundClaimsInSameTransaction() (gas: 538712)\n[PASS] test_charterClaimHasNoAdminRecoveryOrApprovalEntryPoints() (gas: 299245)\n[PASS] test_claimConstructorSucceedsWithoutTokenOrNftCode() (gas: 20827)\n[PASS] test_constructorsLeaveAllSupplyWithFactory() (gas: 456209)\n[PASS] test_runtimeWithinSizeLimitAndNoForbiddenOpcodes() (gas: 3082990)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 646.01ms (23.75ms CPU time)\n\nRan 9 tests for test/CharterClaimProperties.t.sol:CharterClaimPropertiesTest\n[PASS] testFuzz_badInstantMemberIsAtomic(uint256,uint256,uint8) (runs: 1000, μ: 2361820, ~: 2360866)\nLogs:\n  Bound result 12\n  Bound result 4\n\n[PASS] testFuzz_badRestMemberIsAtomic(uint256,uint256,uint8) (runs: 1000, μ: 4004487, ~: 3988720)\nLogs:\n  Bound result 25\n  Bound result 5\n\n[PASS] testFuzz_duplicateAnywhereRollsBackAndOriginalBatchRemainsUsable(uint256,uint256,uint256,bool) (runs: 1000, μ: 2791344, ~: 2526833)\nLogs:\n  Bound result 9\n  Bound result 6\n  Bound result 8\n\n[PASS] testFuzz_partitionAndOrderDoNotChangePayout(uint256,uint256,uint256,uint256) (runs: 1000, μ: 5116773, ~: 4814948)\nLogs:\n  Bound result 14\n  Bound result 935\n  Bound result 2\n\n[PASS] testFuzz_sendingPayoutAwayDoesNotTransferOrResetEntitlement(uint256,bool) (runs: 1000, μ: 7923890, ~: 7932950)\nLogs:\n  Bound result 17798597612469230166055\n\n[PASS] test_contractWalletReceivesBothPhasesInsteadOfTxOrigin() (gas: 3083660)\n[PASS] test_forwarderCannotClaimAnNftOwnedByTxOrigin() (gas: 344508)\n[PASS] test_instantNeedsOnlyOwnerOfAndDoesNotDependOnBalanceRead() (gas: 238340)\n[PASS] test_remintBeforeRestCannotReplaceTheOriginalClaimant() (gas: 8328505)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 646.05ms (3.20s CPU time)\n\nRan 2 tests for test/CharterClaimInvariant.t.sol:CharterClaimInvariantTest\n[PASS] invariant_claimantsAndPaymentsMatchModelAndFundsAreConserved() (runs: 256, calls: 16384, reverts: 0)\n\n╭-------------------------+------------------+-------+---------+----------╮\n| Contract                | Selector         | Calls | Reverts | Discards |\n+=========================================================================+\n| CharterInvariantHandler | burn             | 1996  | 0       | 0        |\n|-------------------------+------------------+-------+---------+----------|\n| CharterInvariantHandler | claimAvailable   | 2051  | 0       | 0        |\n|-------------------------+------------------+-------+---------+----------|\n| CharterInvariantHandler | claimInstant     | 1962  | 0       | 0        |\n|-------------------------+------------------+-------+---------+----------|\n| CharterInvariantHandler | claimRest        | 2086  | 0       | 0        |\n|-------------------------+------------------+-------+---------+----------|\n| CharterInvariantHandler | donate           | 2079  | 0       | 0        |\n|-------------------------+------------------+-------+---------+----------|\n| CharterInvariantHandler | exitAndClaimRest | 2063  | 0       | 0        |\n|-------------------------+------------------+-------+---------+----------|\n| CharterInvariantHandler | moveOrMint       | 2110  | 0       | 0        |\n|-------------------------+------------------+-------+---------+----------|\n| CharterInvariantHandler | transferAll      | 2037  | 0       | 0        |\n╰-------------------------+------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 1\n  Bound result 1\n  Bound result 1\n\n[PASS] test_handlerSequenceIncludesPartialClaimsTransfersRejectionsAndSettlement() (gas: 24481450)\nLogs:\n  Bound result 1\n  Bound result 1\n  Bound result 1\n  Bound result 1\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 79.76s (79.78s CPU time)\n\nRan 6 test suites in 79.76s (82.99s CPU time): 63 tests passed, 0 failed, 0 skipped (63 total tests)\n","passed":true},{"durationMs":36,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"CharterClaim.claim(uint256[])\",\"CharterClaim.claimRest(uint256[])\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":3,\"DEPENDENCIES.md\":15,\"README.md\":110,\"docs/SECURITY_REVIEW.md\":41,\"foundry.toml\":21,\"remappings.txt\":2,\"src/CharterClaim.sol\":114,\"src/LaunchToken.sol\":11,\"src/interfaces/IFoundingCharter.sol\":8,\"test/CharterClaim.t.sol\":459,\"test/CharterClaimAdversarial.t.sol\":152,\"test/CharterClaimInvariant.t.sol\":315,\"test/CharterClaimProperties.t.sol\":309,\"test/Deployment.t.sol\":95,\"test/LaunchToken.t.sol\":80,\"test/TESTING.md\":58,\"test/helpers/AdversarialToken.sol\":82,\"test/helpers/CharterFixture.sol\":63,\"test/helpers/MockCharter.sol\":66},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"a0c2b0538499e2fc5873bba5014d3eff52614a7d01bbe9b6ad38a640cc2d7901","verifiedTreeHash":"bae842d30a6c596c363dac832d92944b86982965","verifierVersion":"0.1.0+327e1e9f"},{"checks":[{"durationMs":2506,"exitCode":0,"name":"build","output":"Compiling 39 files with Solc 0.8.26\nSolc 0.8.26 finished in 2.36s\nCompiler run successful!\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/CharterClaim.sol:54:17\n   │\n54 │             if (IFoundingCharter(CHARTER).ownerOf(id) != msg.sender) revert NotCharterOwner(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/CharterClaim.sol:112:37\n    │\n112 │         if (id == 0 || 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/CharterClaim.sol:53:45\n   │\n53 │             if (claimant[id] != address(0)) revert AlreadyClaimed(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/CharterClaim.sol:54:70\n   │\n54 │             if (IFoundingCharter(CHARTER).ownerOf(id) != msg.sender) revert NotCharterOwner(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/CharterClaim.sol:73:45\n   │\n73 │             if (claimant[id] != msg.sender) revert NotClaimant(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/CharterClaim.sol:74:34\n   │\n74 │             if (restClaimed[id]) revert RestAlreadyClaimed(id);\n   │                                  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n   ╭▸ src/CharterClaim.sol:68:13\n   │\n68 │         if (IFoundingCharter(CHARTER).balanceOf(msg.sender) != 0) revert StillHoldsCharter();\n   │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/CharterClaim.sol:99:25\n   │\n99 │                     try IFoundingCharter(CHARTER).ownerOf(id) returns (address holder) {\n   │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n   ╭▸ src/CharterClaim.sol:93:29\n   │\n93 │         bool holdsCharter = IFoundingCharter(CHARTER).balanceOf(account) != 0;\n   │                             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\n","passed":true},{"durationMs":3508,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 5 tests for test/LaunchToken.t.sol:LaunchTokenTest\n[PASS] testFuzz_transferHasNoFeesOrSupplyChange(uint256) (runs: 256, μ: 92489, ~: 92357)\nLogs:\n  Bound result 1000000000000000000000000000\n\n[PASS] test_approvalAndTransferFrom() (gas: 250552)\n[PASS] test_insufficientBalanceAndZeroRecipientRevert() (gas: 69200)\n[PASS] test_metadataAndSupplyBelongToDeployer() (gas: 53594)\n[PASS] test_noMintOwnerPauseUpgradeOrBurnEntryPoints() (gas: 395336)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 4.19ms (4.52ms CPU time)\n\nRan 8 tests for test/CharterClaimAdversarial.t.sol:CharterClaimAdversarialTest\n[PASS] test_falseReturnRollsBackBothPhasesAndAllowsRetry() (gas: 897366)\n[PASS] test_initialCallbackSeesWholeBatchRecordedAndCannotReenterEitherPhase() (gas: 712086)\n[PASS] test_malformedReturnRollsBackEvenIfTokenMovedBalances() (gas: 939120)\n[PASS] test_missingCharterCodeFailsWithoutPaying() (gas: 96672)\n[PASS] test_restCallbackSeesWholeBatchPaidAndCannotReenterEitherPhase() (gas: 724263)\n[PASS] test_revertingTransferRollsBackBothPhasesAndAllowsRetry() (gas: 895792)\n[PASS] test_tokenWithoutCodeFailsOnFirstPayoutAndLeavesIdUnclaimed() (gas: 150672)\n[PASS] test_tokenWithoutReturnValueIsSupported() (gas: 351130)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 10.06ms (2.54ms CPU time)\n\nRan 5 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] test_allocationHandoffCanFundClaimsInSameTransaction() (gas: 538712)\n[PASS] test_charterClaimHasNoAdminRecoveryOrApprovalEntryPoints() (gas: 299245)\n[PASS] test_claimConstructorSucceedsWithoutTokenOrNftCode() (gas: 20827)\n[PASS] test_constructorsLeaveAllSupplyWithFactory() (gas: 456209)\n[PASS] test_runtimeWithinSizeLimitAndNoForbiddenOpcodes() (gas: 3082990)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 24.48ms (6.63ms CPU time)\n\nRan 34 tests for test/CharterClaim.t.sol:CharterClaimTest\n[PASS] testFuzz_fullLifecyclePaysOneShare(uint256,bool) (runs: 256, μ: 342761, ~: 328844)\nLogs:\n  Bound result 840\n\n[PASS] testFuzz_idsAboveCutoffRejected(uint256) (runs: 256, μ: 125164, ~: 125076)\nLogs:\n  Bound result 115792089237316195423570985008687907853269984665640564039457584007913129639935\n\n[PASS] test_allIdsPayExactlyOneShareAndLeaveOnlyRoundingDust() (gas: 158598954)\n[PASS] test_approvedOperatorCannotClaim() (gas: 178141)\n[PASS] test_claimSinglePaysOwnerAndEmitsEvent() (gas: 245557)\n[PASS] test_claimableSkipsMissingBurnedAndOutOfRangeIds() (gas: 13660801)\n[PASS] test_claimableTracksPartialBatchesAndTransfers() (gas: 16314355)\n[PASS] test_constantsAndInitialState() (gas: 92885)\n[PASS] test_constructorRejectsZeroToken() (gas: 4045)\n[PASS] test_duplicateInitialIdRollsBackWholeBatch() (gas: 273448)\n[PASS] test_duplicateRestIdRollsBackWholeBatch() (gas: 370685)\n[PASS] test_emptyBatchesRevert() (gas: 63253)\n[PASS] test_failedCharterBalanceReadFailsClosed() (gas: 300340)\n[PASS] test_invalidLaterIdRollsBackEarlierIds() (gas: 162130)\n[PASS] test_laterMintWithinCutoffCanClaimButNoOtherSharesRedistribute() (gas: 5887685)\n[PASS] test_mixedClaimantRestBatchRollsBackEarlierIds() (gas: 489992)\n[PASS] test_mixedOwnershipBatchRollsBackEarlierIds() (gas: 247061)\n[PASS] test_nonexistentCharterCannotClaim() (gas: 54207)\n[PASS] test_onlyCurrentOwnerCanClaimBeforeInitialPayment() (gas: 278954)\n[PASS] test_reacquiringCharterBlocksOnlyUnpaidRest() (gas: 8602949)\n[PASS] test_repeatedInitialClaimAndNewOwnerBothFail() (gas: 315593)\n[PASS] test_restBlockedByLaterMintAboveCutoffUntilItLeaves() (gas: 5859056)\n[PASS] test_restBlockedByUnclaimedCharter() (gas: 5720723)\n[PASS] test_restBlockedWhileHoldingClaimedCharter() (gas: 215758)\n[PASS] test_restCannotBeClaimedBeforeInstantOrByAnotherAccount() (gas: 345744)\n[PASS] test_restCannotBePaidTwiceOrResetByRemint() (gas: 441920)\n[PASS] test_restNeverQueriesBurnedOrRevokedIds() (gas: 5320415)\n[PASS] test_restPaidToOriginalClaimantAfterTransfer() (gas: 379188)\n[PASS] test_restRejectsOutOfRangeIds() (gas: 83702)\n[PASS] test_restUsesSingleTransferForBatch() (gas: 492530)\n[PASS] test_underfundedInstantRevertsAndCanBeRetriedAfterFunding() (gas: 475087)\n[PASS] test_underfundedRestRevertsAndPreservesClaimant() (gas: 479596)\n[PASS] test_unsortedBatchIncludesBothBoundariesAndUsesOneTransfer() (gas: 303747)\n[PASS] test_zeroIdRejectedEvenIfNftExists() (gas: 110544)\nSuite result: ok. 34 passed; 0 failed; 0 skipped; finished in 145.63ms (243.19ms CPU time)\n\nRan 1 test for test/CharterClaimInvariant.t.sol:CharterClaimInvariantTest\n[PASS] invariant_claimantsAndPaymentsMatchModelAndFundsAreConserved() (runs: 128, calls: 8192, reverts: 0)\n\n╭-------------------------+------------------+-------+---------+----------╮\n| Contract                | Selector         | Calls | Reverts | Discards |\n+=========================================================================+\n| CharterInvariantHandler | burn             | 1418  | 0       | 0        |\n|-------------------------+------------------+-------+---------+----------|\n| CharterInvariantHandler | claimInstant     | 1378  | 0       | 0        |\n|-------------------------+------------------+-------+---------+----------|\n| CharterInvariantHandler | claimRest        | 1324  | 0       | 0        |\n|-------------------------+------------------+-------+---------+----------|\n| CharterInvariantHandler | donate           | 1360  | 0       | 0        |\n|-------------------------+------------------+-------+---------+----------|\n| CharterInvariantHandler | exitAndClaimRest | 1357  | 0       | 0        |\n|-------------------------+------------------+-------+---------+----------|\n| CharterInvariantHandler | moveOrMint       | 1355  | 0       | 0        |\n╰-------------------------+------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.39s (3.39s CPU time)\n\nRan 5 test suites in 3.39s (3.58s CPU time): 53 tests passed, 0 failed, 0 skipped (53 total tests)\n","passed":true},{"durationMs":45,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"CharterClaim.claim(uint256[])\",\"CharterClaim.claimRest(uint256[])\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":3,\"DEPENDENCIES.md\":15,\"README.md\":110,\"docs/SECURITY_REVIEW.md\":41,\"foundry.toml\":21,\"launch.json\":22,\"remappings.txt\":2,\"src/CharterClaim.sol\":114,\"src/LaunchToken.sol\":11,\"src/interfaces/IFoundingCharter.sol\":8,\"test/CharterClaim.t.sol\":459,\"test/CharterClaimAdversarial.t.sol\":152,\"test/CharterClaimInvariant.t.sol\":202,\"test/Deployment.t.sol\":95,\"test/LaunchToken.t.sol\":80,\"test/helpers/AdversarialToken.sol\":82,\"test/helpers/CharterFixture.sol\":63,\"test/helpers/MockCharter.sol\":66},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"d370acf32b8ea3363b93a9d57d667b0f0d9e205863525ff1dc6ec05881f34c40","verifiedTreeHash":"87050753845c68563d43f3ead0642b294e2e8d1d","verifierVersion":"0.1.0+327e1e9f"},{"checks":[{"durationMs":1925,"exitCode":0,"name":"build","output":"Compiling 39 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.81s\nCompiler run successful!\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/CharterClaim.sol:54:17\n   │\n54 │             if (IFoundingCharter(CHARTER).ownerOf(id) != msg.sender) revert NotCharterOwner(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/CharterClaim.sol:112:37\n    │\n112 │         if (id == 0 || 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/CharterClaim.sol:53:45\n   │\n53 │             if (claimant[id] != address(0)) revert AlreadyClaimed(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/CharterClaim.sol:54:70\n   │\n54 │             if (IFoundingCharter(CHARTER).ownerOf(id) != msg.sender) revert NotCharterOwner(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/CharterClaim.sol:73:45\n   │\n73 │             if (claimant[id] != msg.sender) revert NotClaimant(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/CharterClaim.sol:74:34\n   │\n74 │             if (restClaimed[id]) revert RestAlreadyClaimed(id);\n   │                                  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n   ╭▸ src/CharterClaim.sol:68:13\n   │\n68 │         if (IFoundingCharter(CHARTER).balanceOf(msg.sender) != 0) revert StillHoldsCharter();\n   │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/CharterClaim.sol:99:25\n   │\n99 │                     try IFoundingCharter(CHARTER).ownerOf(id) returns (address holder) {\n   │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n   ╭▸ src/CharterClaim.sol:93:29\n   │\n93 │         bool holdsCharter = IFoundingCharter(CHARTER).balanceOf(account) != 0;\n   │                             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\n","passed":true},{"durationMs":2716,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 5 tests for test/LaunchToken.t.sol:LaunchTokenTest\n[PASS] testFuzz_transferHasNoFeesOrSupplyChange(uint256) (runs: 256, μ: 92475, ~: 92357)\nLogs:\n  Bound result 3\n\n[PASS] test_approvalAndTransferFrom() (gas: 250552)\n[PASS] test_insufficientBalanceAndZeroRecipientRevert() (gas: 69200)\n[PASS] test_metadataAndSupplyBelongToDeployer() (gas: 53594)\n[PASS] test_noMintOwnerPauseUpgradeOrBurnEntryPoints() (gas: 395336)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 14.49ms (5.29ms CPU time)\n\nRan 5 tests for test/Deployment.t.sol:DeploymentTest\n[PASS] test_allocationHandoffCanFundClaimsInSameTransaction() (gas: 538712)\n[PASS] test_charterClaimHasNoAdminRecoveryOrApprovalEntryPoints() (gas: 299245)\n[PASS] test_claimConstructorSucceedsWithoutTokenOrNftCode() (gas: 20827)\n[PASS] test_constructorsLeaveAllSupplyWithFactory() (gas: 456209)\n[PASS] test_runtimeWithinSizeLimitAndNoForbiddenOpcodes() (gas: 3082990)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 14.52ms (9.77ms CPU time)\n\nRan 8 tests for test/CharterClaimAdversarial.t.sol:CharterClaimAdversarialTest\n[PASS] test_falseReturnRollsBackBothPhasesAndAllowsRetry() (gas: 897366)\n[PASS] test_initialCallbackSeesWholeBatchRecordedAndCannotReenterEitherPhase() (gas: 712086)\n[PASS] test_malformedReturnRollsBackEvenIfTokenMovedBalances() (gas: 939120)\n[PASS] test_missingCharterCodeFailsWithoutPaying() (gas: 96672)\n[PASS] test_restCallbackSeesWholeBatchPaidAndCannotReenterEitherPhase() (gas: 724263)\n[PASS] test_revertingTransferRollsBackBothPhasesAndAllowsRetry() (gas: 895792)\n[PASS] test_tokenWithoutCodeFailsOnFirstPayoutAndLeavesIdUnclaimed() (gas: 150672)\n[PASS] test_tokenWithoutReturnValueIsSupported() (gas: 351130)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 5.15ms (2.48ms CPU time)\n\nRan 34 tests for test/CharterClaim.t.sol:CharterClaimTest\n[PASS] testFuzz_fullLifecyclePaysOneShare(uint256,bool) (runs: 256, μ: 342829, ~: 328844)\nLogs:\n  Bound result 637\n\n[PASS] testFuzz_idsAboveCutoffRejected(uint256) (runs: 256, μ: 125248, ~: 125107)\nLogs:\n  Bound result 1036\n\n[PASS] test_allIdsPayExactlyOneShareAndLeaveOnlyRoundingDust() (gas: 158598954)\n[PASS] test_approvedOperatorCannotClaim() (gas: 178141)\n[PASS] test_claimSinglePaysOwnerAndEmitsEvent() (gas: 245557)\n[PASS] test_claimableSkipsMissingBurnedAndOutOfRangeIds() (gas: 13660801)\n[PASS] test_claimableTracksPartialBatchesAndTransfers() (gas: 16314355)\n[PASS] test_constantsAndInitialState() (gas: 92885)\n[PASS] test_constructorRejectsZeroToken() (gas: 4045)\n[PASS] test_duplicateInitialIdRollsBackWholeBatch() (gas: 273448)\n[PASS] test_duplicateRestIdRollsBackWholeBatch() (gas: 370685)\n[PASS] test_emptyBatchesRevert() (gas: 63253)\n[PASS] test_failedCharterBalanceReadFailsClosed() (gas: 300340)\n[PASS] test_invalidLaterIdRollsBackEarlierIds() (gas: 162130)\n[PASS] test_laterMintWithinCutoffCanClaimButNoOtherSharesRedistribute() (gas: 5887685)\n[PASS] test_mixedClaimantRestBatchRollsBackEarlierIds() (gas: 489992)\n[PASS] test_mixedOwnershipBatchRollsBackEarlierIds() (gas: 247061)\n[PASS] test_nonexistentCharterCannotClaim() (gas: 54207)\n[PASS] test_onlyCurrentOwnerCanClaimBeforeInitialPayment() (gas: 278954)\n[PASS] test_reacquiringCharterBlocksOnlyUnpaidRest() (gas: 8602949)\n[PASS] test_repeatedInitialClaimAndNewOwnerBothFail() (gas: 315593)\n[PASS] test_restBlockedByLaterMintAboveCutoffUntilItLeaves() (gas: 5859056)\n[PASS] test_restBlockedByUnclaimedCharter() (gas: 5720723)\n[PASS] test_restBlockedWhileHoldingClaimedCharter() (gas: 215758)\n[PASS] test_restCannotBeClaimedBeforeInstantOrByAnotherAccount() (gas: 345744)\n[PASS] test_restCannotBePaidTwiceOrResetByRemint() (gas: 441920)\n[PASS] test_restNeverQueriesBurnedOrRevokedIds() (gas: 5320415)\n[PASS] test_restPaidToOriginalClaimantAfterTransfer() (gas: 379188)\n[PASS] test_restRejectsOutOfRangeIds() (gas: 83702)\n[PASS] test_restUsesSingleTransferForBatch() (gas: 492530)\n[PASS] test_underfundedInstantRevertsAndCanBeRetriedAfterFunding() (gas: 475087)\n[PASS] test_underfundedRestRevertsAndPreservesClaimant() (gas: 479596)\n[PASS] test_unsortedBatchIncludesBothBoundariesAndUsesOneTransfer() (gas: 303747)\n[PASS] test_zeroIdRejectedEvenIfNftExists() (gas: 110544)\nSuite result: ok. 34 passed; 0 failed; 0 skipped; finished in 93.43ms (175.32ms CPU time)\n\nRan 1 test for test/CharterClaimInvariant.t.sol:CharterClaimInvariantTest\n[PASS] invariant_claimantsAndPaymentsMatchModelAndFundsAreConserved() (runs: 128, calls: 8192, reverts: 0)\n\n╭-------------------------+------------------+-------+---------+----------╮\n| Contract                | Selector         | Calls | Reverts | Discards |\n+=========================================================================+\n| CharterInvariantHandler | burn             | 1335  | 0       | 0        |\n|-------------------------+------------------+-------+---------+----------|\n| CharterInvariantHandler | claimInstant     | 1371  | 0       | 0        |\n|-------------------------+------------------+-------+---------+----------|\n| CharterInvariantHandler | claimRest        | 1369  | 0       | 0        |\n|-------------------------+------------------+-------+---------+----------|\n| CharterInvariantHandler | donate           | 1388  | 0       | 0        |\n|-------------------------+------------------+-------+---------+----------|\n| CharterInvariantHandler | exitAndClaimRest | 1387  | 0       | 0        |\n|-------------------------+------------------+-------+---------+----------|\n| CharterInvariantHandler | moveOrMint       | 1342  | 0       | 0        |\n╰-------------------------+------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.62s (2.62s CPU time)\n\nRan 5 test suites in 2.63s (2.75s CPU time): 53 tests passed, 0 failed, 0 skipped (53 total tests)\n","passed":true},{"durationMs":35,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"CharterClaim.claim(uint256[])\",\"CharterClaim.claimRest(uint256[])\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":3,\"DEPENDENCIES.md\":15,\"README.md\":110,\"docs/SECURITY_REVIEW.md\":41,\"foundry.toml\":21,\"remappings.txt\":2,\"src/CharterClaim.sol\":114,\"src/LaunchToken.sol\":11,\"src/interfaces/IFoundingCharter.sol\":8,\"test/CharterClaim.t.sol\":459,\"test/CharterClaimAdversarial.t.sol\":152,\"test/CharterClaimInvariant.t.sol\":202,\"test/Deployment.t.sol\":95,\"test/LaunchToken.t.sol\":80,\"test/helpers/AdversarialToken.sol\":82,\"test/helpers/CharterFixture.sol\":63,\"test/helpers/MockCharter.sol\":66},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":635,"exitCode":0,"name":"slither","output":"[low/medium] calls-loop at src/CharterClaim.sol:46: CharterClaim.claim(uint256[]) (src/CharterClaim.sol#46-61) has external calls inside a loop: IFoundingCharter(CHARTER).ownerOf(id) != msg.sender (src/CharterClaim.sol#54)\n[low/medium] calls-loop at src/CharterClaim.sol:91: CharterClaim.claimable(address) (src/CharterClaim.sol#91-109) has external calls inside a loop: holder = IFoundingCharter(CHARTER).ownerOf(id) (src/CharterClaim.sol#99-103)","passed":true},{"durationMs":248,"exitCode":0,"name":"aderyn","output":"[high] reentrancy-state-change at src/CharterClaim.sol:54: Reentrancy: State change after external call (2 places)\n[low] costly-loop at src/CharterClaim.sol:50: Costly operations inside loop (2 places)\n[low] large-numeric-literal at src/CharterClaim.sol:16: Large Numeric Literal (2 places)\n[low] require-revert-in-loop at src/CharterClaim.sol:50: Loop Contains `require`/`revert` (2 places)\n[low] uninitialized-local-variable at src/CharterClaim.sol:50: Uninitialized Local Variable (2 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"f58763ba0d32ad5744bbe0060d1e653496f9f4a72c4be15c52cbc870d3c3821e","verifiedTreeHash":"e25c4abc955d60115d979eb4a318b6e5a81f6828","verifierVersion":"0.1.0+327e1e9f"}]}